@servicetitan/navigation 14.2.0 → 14.4.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 (97) hide show
  1. package/dist/components/titan-layout/__tests__/layout-sidebar-links-internal.test.d.ts +2 -0
  2. package/dist/components/titan-layout/__tests__/layout-sidebar-links-internal.test.d.ts.map +1 -0
  3. package/dist/components/titan-layout/__tests__/navigation-catalog-reset.test.d.ts +2 -0
  4. package/dist/components/titan-layout/__tests__/navigation-catalog-reset.test.d.ts.map +1 -0
  5. package/dist/components/titan-layout/interface.d.ts +66 -0
  6. package/dist/components/titan-layout/interface.d.ts.map +1 -1
  7. package/dist/components/titan-layout/interface.js.map +1 -1
  8. package/dist/components/titan-layout/layout-sidebar-links-internal.d.ts +3 -0
  9. package/dist/components/titan-layout/layout-sidebar-links-internal.d.ts.map +1 -1
  10. package/dist/components/titan-layout/layout-sidebar-links-internal.js +58 -13
  11. package/dist/components/titan-layout/layout-sidebar-links-internal.js.map +1 -1
  12. package/dist/components/titan-layout/layout-sidebar.d.ts +14 -0
  13. package/dist/components/titan-layout/layout-sidebar.d.ts.map +1 -1
  14. package/dist/components/titan-layout/layout-sidebar.js +169 -97
  15. package/dist/components/titan-layout/layout-sidebar.js.map +1 -1
  16. package/dist/components/titan-layout/layout-sidebar.module.less +230 -144
  17. package/dist/components/titan-layout/layout-sidebar.module.less.d.ts +16 -5
  18. package/dist/components/titan-layout/navigation-catalog/index.d.ts +3 -0
  19. package/dist/components/titan-layout/navigation-catalog/index.d.ts.map +1 -0
  20. package/dist/components/titan-layout/navigation-catalog/index.js +4 -0
  21. package/dist/components/titan-layout/navigation-catalog/index.js.map +1 -0
  22. package/dist/components/titan-layout/navigation-catalog/navigation-catalog-footer.d.ts +8 -0
  23. package/dist/components/titan-layout/navigation-catalog/navigation-catalog-footer.d.ts.map +1 -0
  24. package/dist/components/titan-layout/navigation-catalog/navigation-catalog-footer.js +88 -0
  25. package/dist/components/titan-layout/navigation-catalog/navigation-catalog-footer.js.map +1 -0
  26. package/dist/components/titan-layout/navigation-catalog/navigation-catalog-trigger.d.ts +21 -0
  27. package/dist/components/titan-layout/navigation-catalog/navigation-catalog-trigger.d.ts.map +1 -0
  28. package/dist/components/titan-layout/navigation-catalog/navigation-catalog-trigger.js +138 -0
  29. package/dist/components/titan-layout/navigation-catalog/navigation-catalog-trigger.js.map +1 -0
  30. package/dist/components/titan-layout/navigation-catalog/navigation-catalog-trigger.module.less +89 -0
  31. package/dist/components/titan-layout/navigation-catalog/navigation-catalog-trigger.module.less.d.ts +9 -0
  32. package/dist/components/titan-layout/navigation-catalog/navigation-catalog.d.ts +14 -0
  33. package/dist/components/titan-layout/navigation-catalog/navigation-catalog.d.ts.map +1 -0
  34. package/dist/components/titan-layout/navigation-catalog/navigation-catalog.js +343 -0
  35. package/dist/components/titan-layout/navigation-catalog/navigation-catalog.js.map +1 -0
  36. package/dist/components/titan-layout/navigation-catalog/navigation-catalog.module.less +379 -0
  37. package/dist/components/titan-layout/navigation-catalog/navigation-catalog.module.less.d.ts +26 -0
  38. package/dist/components/titan-layout/navigation-scroll-indicators.d.ts +12 -0
  39. package/dist/components/titan-layout/navigation-scroll-indicators.d.ts.map +1 -0
  40. package/dist/components/titan-layout/navigation-scroll-indicators.js +48 -0
  41. package/dist/components/titan-layout/navigation-scroll-indicators.js.map +1 -0
  42. package/dist/components/titan-layout/titan-layout.d.ts +29 -1
  43. package/dist/components/titan-layout/titan-layout.d.ts.map +1 -1
  44. package/dist/components/titan-layout/titan-layout.js +46 -6
  45. package/dist/components/titan-layout/titan-layout.js.map +1 -1
  46. package/dist/components/titan-layout/titan-layout.module.less +2 -1
  47. package/dist/components/titan-layout/use-navigation-scroll.d.ts +20 -0
  48. package/dist/components/titan-layout/use-navigation-scroll.d.ts.map +1 -0
  49. package/dist/components/titan-layout/use-navigation-scroll.js +140 -0
  50. package/dist/components/titan-layout/use-navigation-scroll.js.map +1 -0
  51. package/dist/utils/__tests__/catalog-groups.test.d.ts +2 -0
  52. package/dist/utils/__tests__/catalog-groups.test.d.ts.map +1 -0
  53. package/dist/utils/__tests__/navigation-notifications.test.d.ts +2 -0
  54. package/dist/utils/__tests__/navigation-notifications.test.d.ts.map +1 -0
  55. package/dist/utils/__tests__/submenu-state.test.d.ts +2 -0
  56. package/dist/utils/__tests__/submenu-state.test.d.ts.map +1 -0
  57. package/dist/utils/catalog-groups.d.ts +11 -0
  58. package/dist/utils/catalog-groups.d.ts.map +1 -0
  59. package/dist/utils/catalog-groups.js +66 -0
  60. package/dist/utils/catalog-groups.js.map +1 -0
  61. package/dist/utils/navigation-notifications.d.ts +21 -0
  62. package/dist/utils/navigation-notifications.d.ts.map +1 -0
  63. package/dist/utils/navigation-notifications.js +39 -0
  64. package/dist/utils/navigation-notifications.js.map +1 -0
  65. package/dist/utils/side-nav.js +3 -1
  66. package/dist/utils/side-nav.js.map +1 -1
  67. package/dist/utils/submenu-state.d.ts +3 -0
  68. package/dist/utils/submenu-state.d.ts.map +1 -0
  69. package/dist/utils/submenu-state.js +15 -0
  70. package/dist/utils/submenu-state.js.map +1 -0
  71. package/package.json +2 -2
  72. package/src/components/titan-layout/__tests__/layout-sidebar-links-internal.test.tsx +225 -0
  73. package/src/components/titan-layout/__tests__/navigation-catalog-reset.test.tsx +190 -0
  74. package/src/components/titan-layout/interface.ts +73 -0
  75. package/src/components/titan-layout/layout-sidebar-links-internal.tsx +86 -14
  76. package/src/components/titan-layout/layout-sidebar.module.less +230 -144
  77. package/src/components/titan-layout/layout-sidebar.module.less.d.ts +16 -5
  78. package/src/components/titan-layout/layout-sidebar.tsx +193 -117
  79. package/src/components/titan-layout/navigation-catalog/index.ts +2 -0
  80. package/src/components/titan-layout/navigation-catalog/navigation-catalog-footer.tsx +88 -0
  81. package/src/components/titan-layout/navigation-catalog/navigation-catalog-trigger.module.less +89 -0
  82. package/src/components/titan-layout/navigation-catalog/navigation-catalog-trigger.module.less.d.ts +9 -0
  83. package/src/components/titan-layout/navigation-catalog/navigation-catalog-trigger.tsx +170 -0
  84. package/src/components/titan-layout/navigation-catalog/navigation-catalog.module.less +379 -0
  85. package/src/components/titan-layout/navigation-catalog/navigation-catalog.module.less.d.ts +26 -0
  86. package/src/components/titan-layout/navigation-catalog/navigation-catalog.tsx +430 -0
  87. package/src/components/titan-layout/navigation-scroll-indicators.tsx +80 -0
  88. package/src/components/titan-layout/titan-layout.module.less +2 -1
  89. package/src/components/titan-layout/titan-layout.tsx +81 -5
  90. package/src/components/titan-layout/use-navigation-scroll.ts +145 -0
  91. package/src/utils/__tests__/catalog-groups.test.ts +97 -0
  92. package/src/utils/__tests__/navigation-notifications.test.ts +70 -0
  93. package/src/utils/__tests__/submenu-state.test.ts +39 -0
  94. package/src/utils/catalog-groups.ts +57 -0
  95. package/src/utils/navigation-notifications.ts +59 -0
  96. package/src/utils/side-nav.ts +1 -1
  97. package/src/utils/submenu-state.ts +16 -0
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import classNames from 'classnames';
3
3
  import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react';
4
4
  import { DefaultNavLinkComponent } from '../../utils/navigation-context';
5
+ import { updateOpenSubmenus } from '../../utils/submenu-state';
5
6
  import { useTitanBreakpoint } from '../../utils/use-breakpoint';
6
7
  import { LayoutContext, LayoutPlacementContext } from './layout-context';
7
8
  import { LayoutHeader } from './layout-header';
@@ -42,7 +43,7 @@ export const useViewportConfigs = (noResponsive)=>{
42
43
  noResponsive
43
44
  ]);
44
45
  };
45
- function TitanLayoutComponent({ appearance = 'anvil2', navVariant = 'left', id, children, contentOnly, navigationComponent, header, top, profile, expanded, openSubmenus, logo, onExpandedChange, onOpenSubmenuChange, navigationMainItems, navigationOverflowItems, onNavigationLinkClick, extraLinks, extraLinksTop, extraText, minContentWidth, noResponsive, sideTop }) {
46
+ function TitanLayoutComponent({ appearance = 'anvil2', navVariant = 'left', id, children, contentOnly, navigationComponent, header, top, profile, expanded, openSubmenus, logo, onExpandedChange, onOpenSubmenuChange, onOpenSubmenusChange, navigationMainItems, pinnedNavigation, navigationOverflowItems, onNavigationLinkClick, extraLinks, extraLinksTop, extraText, minContentWidth, noResponsive, sideTop, sideTopItems, navigationScrollIndicators = true }) {
46
47
  const breakpoint = useTitanBreakpoint(noResponsive);
47
48
  const context = useMemo(()=>({
48
49
  NavigationComponent: navigationComponent !== null && navigationComponent !== void 0 ? navigationComponent : DefaultNavLinkComponent,
@@ -58,7 +59,7 @@ function TitanLayoutComponent({ appearance = 'anvil2', navVariant = 'left', id,
58
59
  const { hasNotifications, NotificationsContextProvider } = useNotificationsState();
59
60
  const [headerHeight, setHeaderHeight] = useState(0);
60
61
  const isMobile = breakpoint.isMobile;
61
- const hasSideBar = !contentOnly && (navVariant === 'left' || navVariant === 'top' && isMobile) && (!!(navigationMainItems === null || navigationMainItems === void 0 ? void 0 : navigationMainItems.length) || !!(sideTop === null || sideTop === void 0 ? void 0 : sideTop.length));
62
+ const hasSideBar = !contentOnly && (navVariant === 'left' || navVariant === 'top' && isMobile) && (!!(navigationMainItems === null || navigationMainItems === void 0 ? void 0 : navigationMainItems.length) || !!(sideTop === null || sideTop === void 0 ? void 0 : sideTop.length) || !!(sideTopItems === null || sideTopItems === void 0 ? void 0 : sideTopItems.length) || !!pinnedNavigation);
62
63
  const hasTopBar = !contentOnly;
63
64
  useEffect(()=>{
64
65
  if (view.isAnvil1) {
@@ -92,9 +93,29 @@ function TitanLayoutComponent({ appearance = 'anvil2', navVariant = 'left', id,
92
93
  isMobile
93
94
  ]);
94
95
  const onSubmenuOpenChange = useCallback((id, opened)=>{
95
- onOpenSubmenuChange === null || onOpenSubmenuChange === void 0 ? void 0 : onOpenSubmenuChange(id, opened);
96
+ if (onOpenSubmenusChange) {
97
+ onOpenSubmenusChange(updateOpenSubmenus(openSubmenus !== null && openSubmenus !== void 0 ? openSubmenus : [], [
98
+ id
99
+ ], opened));
100
+ } else {
101
+ onOpenSubmenuChange === null || onOpenSubmenuChange === void 0 ? void 0 : onOpenSubmenuChange(id, opened);
102
+ }
103
+ }, [
104
+ onOpenSubmenuChange,
105
+ onOpenSubmenusChange,
106
+ openSubmenus
107
+ ]);
108
+ /*
109
+ * Bulk actions require `onOpenSubmenusChange`, which takes the whole next set at once.
110
+ * They are deliberately not emulated with `onOpenSubmenuChange`: that contract is one
111
+ * call per user action, so a host deriving its next list from the current one would see
112
+ * the same stale list on every call of a loop and keep only the last module. `Expand All`
113
+ * is hidden entirely when a host supplies only the per-module callback.
114
+ */ const onSubmenusOpenChange = useCallback((ids, opened)=>{
115
+ onOpenSubmenusChange === null || onOpenSubmenusChange === void 0 ? void 0 : onOpenSubmenusChange(updateOpenSubmenus(openSubmenus !== null && openSubmenus !== void 0 ? openSubmenus : [], ids, opened));
96
116
  }, [
97
- onOpenSubmenuChange
117
+ onOpenSubmenusChange,
118
+ openSubmenus
98
119
  ]);
99
120
  const sidebarNavItems = useMemo(()=>{
100
121
  if (!hasSideBar) {
@@ -113,10 +134,25 @@ function TitanLayoutComponent({ appearance = 'anvil2', navVariant = 'left', id,
113
134
  navigationOverflowItems,
114
135
  navVariant
115
136
  ]);
137
+ /*
138
+ * The burger badge speaks for the whole drawer, so it has to count what sits above the
139
+ * divider as well. A permanent destination asking for attention would otherwise go unseen
140
+ * while the drawer is closed.
141
+ */ const badgeNavItems = useMemo(/*
142
+ * Gated on `hasSideBar`, not `sidebarNavItems`: on the left variant a rail that exists
143
+ * only for `sideTopItems` still has `sidebarNavItems` (i.e. `navigationMainItems`) undefined.
144
+ */ ()=>hasSideBar ? [
145
+ ...sideTopItems !== null && sideTopItems !== void 0 ? sideTopItems : [],
146
+ ...sidebarNavItems !== null && sidebarNavItems !== void 0 ? sidebarNavItems : []
147
+ ] : undefined, [
148
+ hasSideBar,
149
+ sideTopItems,
150
+ sidebarNavItems
151
+ ]);
116
152
  const hasMenuNotifications = useMemo(()=>{
117
153
  try {
118
154
  var _ref;
119
- return (_ref = sidebarNavItems === null || sidebarNavItems === void 0 ? void 0 : sidebarNavItems.some((item)=>{
155
+ return (_ref = badgeNavItems === null || badgeNavItems === void 0 ? void 0 : badgeNavItems.some((item)=>{
120
156
  var _item_tag;
121
157
  if (item.counter || ((_item_tag = item.tag) === null || _item_tag === void 0 ? void 0 : _item_tag.value)) {
122
158
  return true;
@@ -132,7 +168,7 @@ function TitanLayoutComponent({ appearance = 'anvil2', navVariant = 'left', id,
132
168
  return false;
133
169
  }
134
170
  }, [
135
- sidebarNavItems
171
+ badgeNavItems
136
172
  ]);
137
173
  const limitContentWidth = useMemo(()=>{
138
174
  if (view.isAnvil2 || !minContentWidth) {
@@ -245,9 +281,13 @@ function TitanLayoutComponent({ appearance = 'anvil2', navVariant = 'left', id,
245
281
  onBarExpandChange: onBarExpandChange,
246
282
  submenusOpened: openSubmenus,
247
283
  onSubmenuOpenChange: onSubmenuOpenChange,
284
+ onSubmenusOpenChange: onOpenSubmenusChange ? onSubmenusOpenChange : undefined,
248
285
  drawerOpened: mobileDrawerOpened,
249
286
  onDrawerOpenChange: setMobileDrawerOpened,
250
287
  top: sideTop,
288
+ topItems: sideTopItems,
289
+ scrollIndicators: navigationScrollIndicators,
290
+ pinnedNavigation: pinnedNavigation,
251
291
  mainItems: sidebarNavItems,
252
292
  navigationComponent: context.NavigationComponent,
253
293
  onNavigationLinkClick: onNavigationLinkClick,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/titan-layout/titan-layout.tsx"],"sourcesContent":["import classNames from 'classnames';\nimport {\n CSSProperties,\n ComponentPropsWithoutRef,\n FC,\n Fragment,\n MouseEvent,\n ReactElement,\n ReactNode,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { NavigationItemData } from '../../utils/navigation';\nimport { DefaultNavLinkComponent, NavLinkComponentProps } from '../../utils/navigation-context';\nimport { useTitanBreakpoint } from '../../utils/use-breakpoint';\nimport {\n LayoutContext,\n LayoutPlacementContext,\n TitanLayoutContextType,\n TitanLayoutSidebarContextType,\n} from './layout-context';\nimport { LayoutHeader, LayoutHeaderProps } from './layout-header';\nimport { LayoutHeaderDark } from './layout-header-dark';\nimport { TitanLayoutLogoProps } from './layout-logo';\nimport { LayoutSidebar } from './layout-sidebar';\nimport { InternalSideNavigationTrigger } from './layout-sidebar-links-internal';\nimport { useNotificationsState } from './notifications-context';\nimport { TitanLayoutLink, TitanLayoutTrigger } from './titan-layout-links';\nimport * as Styles from './titan-layout.module.less';\n\nexport type TitanLayoutProps = Omit<ComponentPropsWithoutRef<'div'>, 'children' | 'style'> & {\n /** layout appearance */\n appearance?: 'legacy' | 'anvil1' | 'anvil2';\n\n /** layout navigation variant (left by default) */\n navVariant?: 'left' | 'top';\n\n /** layout's content */\n children?: ReactNode;\n\n /** show only content without side and top bars */\n contentOnly?: boolean;\n\n /** component used for navigation */\n navigationComponent?: FC<NavLinkComponentProps>;\n\n /** data for main navigation links */\n navigationMainItems?: NavigationItemData[];\n\n /** data for overflow navigation links (used only with top variant) */\n navigationOverflowItems?: NavigationItemData[];\n\n /** logo props */\n logo?: TitanLayoutLogoProps;\n\n /** expanded state */\n expanded?: boolean;\n /** expanded state change handler */\n onExpandedChange?: (expanded: boolean) => void;\n\n /** submenus accordion opened state */\n openSubmenus?: string[];\n /** submenu accordion open state change handler */\n onOpenSubmenuChange?: (id: string, opened: boolean) => void;\n\n /** navigation link click handler */\n onNavigationLinkClick?: (itemId: string, submenuItemId?: string) => void;\n\n /** content header content */\n header?: ReactElement;\n\n /** layout header content (center) */\n top?: ReactElement;\n\n /** top links for side navigation */\n sideTop?: ReactElement[];\n\n /**\n * profile element for layout\n * @see ProfileDropdown\n */\n profile?: ReactElement;\n\n /**\n * extra links for layout header\n * shown in side nav footer on mobile\n */\n extraLinks?: ReactElement;\n\n /**\n * fixed extra links for layout header\n * shown in header on mobile as well\n */\n extraLinksTop?: ReactElement;\n\n /**\n * text shown in layout's header\n * shown in side nav footer on mobile\n */\n extraText?: string;\n\n /**\n * minimal width set for content area\n * used for pages that aren't adopted to mobile\n */\n minContentWidth?: number;\n\n /**\n * disable responsive behavior\n * can be used for pages that aren't adopted to mobile\n */\n noResponsive?: boolean;\n};\n\nconst defaultSidebarContext: TitanLayoutSidebarContextType = {\n styles: {\n popoverContent: {\n '--background-color-strong': '#0f1d26',\n 'color': 'var(--color-white)',\n } as CSSProperties,\n },\n};\n\nconst useAppearance = (appearance: TitanLayoutProps['appearance']) =>\n useMemo(() => {\n const isLegacy = appearance === 'legacy';\n const isAnvil1 = appearance === 'anvil1';\n const isAnvil2 = appearance === 'anvil2';\n\n return {\n isLegacy,\n isAnvil1,\n isAnvil2,\n isSequent: isLegacy || isAnvil2,\n };\n }, [appearance]);\n\nexport const useViewportConfigs = (noResponsive?: boolean) => {\n useEffect(() => {\n const meta: HTMLMetaElement | null = document.head.querySelector('meta[name=\"viewport\"]');\n\n if (meta) {\n meta.content = noResponsive\n ? ''\n : 'width=device-width, initial-scale=1, maximum-scale=1';\n }\n }, [noResponsive]);\n};\n\nfunction TitanLayoutComponent({\n appearance = 'anvil2',\n navVariant = 'left',\n id,\n children,\n contentOnly,\n navigationComponent,\n header,\n top,\n profile,\n expanded,\n openSubmenus,\n logo,\n onExpandedChange,\n onOpenSubmenuChange,\n navigationMainItems,\n navigationOverflowItems,\n onNavigationLinkClick,\n extraLinks,\n extraLinksTop,\n extraText,\n minContentWidth,\n noResponsive,\n sideTop,\n}: TitanLayoutProps) {\n const breakpoint = useTitanBreakpoint(noResponsive);\n const context: TitanLayoutContextType = useMemo(\n () => ({\n NavigationComponent: navigationComponent ?? DefaultNavLinkComponent,\n breakpoint,\n isTitanLayout: true,\n sidebar: defaultSidebarContext,\n }),\n [navigationComponent, breakpoint]\n );\n const view = useAppearance(appearance);\n const [mobileDrawerOpened, setMobileDrawerOpened] = useState(false);\n const { hasNotifications, NotificationsContextProvider } = useNotificationsState();\n const [headerHeight, setHeaderHeight] = useState(0);\n\n const isMobile = breakpoint.isMobile;\n const hasSideBar =\n !contentOnly &&\n (navVariant === 'left' || (navVariant === 'top' && isMobile)) &&\n (!!navigationMainItems?.length || !!sideTop?.length);\n const hasTopBar = !contentOnly;\n\n useEffect(() => {\n if (view.isAnvil1) {\n const bodyClassName = 'of-hidden-i';\n document.body.classList.add(bodyClassName);\n return () => document.body.classList.remove(bodyClassName);\n }\n }, [view.isAnvil1]);\n\n const onBurgerClick = useCallback(\n (e: MouseEvent<never>) => {\n if (isMobile) {\n setMobileDrawerOpened(true);\n } else {\n onExpandedChange?.(!expanded);\n }\n\n e.stopPropagation();\n },\n [isMobile, expanded, onExpandedChange]\n );\n\n const onBarExpandChange = useCallback(\n (expanded: boolean) => {\n if (isMobile) {\n setMobileDrawerOpened(false);\n } else {\n onExpandedChange?.(expanded);\n }\n },\n [onExpandedChange, isMobile]\n );\n const onSubmenuOpenChange = useCallback(\n (id: string, opened: boolean) => {\n onOpenSubmenuChange?.(id, opened);\n },\n [onOpenSubmenuChange]\n );\n const sidebarNavItems = useMemo(() => {\n if (!hasSideBar) {\n return undefined;\n }\n\n if (navVariant === 'left') {\n return navigationMainItems;\n }\n\n return [...(navigationMainItems ?? []), ...(navigationOverflowItems ?? [])];\n }, [hasSideBar, navigationMainItems, navigationOverflowItems, navVariant]);\n\n const hasMenuNotifications = useMemo(() => {\n try {\n return (\n sidebarNavItems?.some(item => {\n if (item.counter || item.tag?.value) {\n return true;\n } else if (item.submenu) {\n return item.submenu.groups.some(group =>\n group.links.some(link => !!link.counter || !!link.tag?.value)\n );\n }\n return false;\n }) ?? false\n );\n } catch {\n return false;\n }\n }, [sidebarNavItems]);\n\n const limitContentWidth = useMemo(() => {\n if (view.isAnvil2 || !minContentWidth) {\n return undefined;\n }\n\n if (breakpoint.width < minContentWidth) {\n return minContentWidth;\n }\n }, [view, minContentWidth, breakpoint.width]);\n\n const contentStyles = useMemo(() => {\n if (view.isAnvil2) {\n return {\n '--content-offset-top': `calc(var(--nav-offset-top) + ${headerHeight}px)`,\n } as CSSProperties;\n }\n }, [view, headerHeight]);\n\n const layoutClass = view.isLegacy\n ? Styles.layoutLegacy\n : view.isAnvil1\n ? Styles.layoutAnvil1\n : Styles.layoutAnvil2;\n\n const burgerProps: LayoutHeaderProps['burger'] = useMemo(() => {\n if (!hasTopBar) {\n return undefined;\n }\n\n if (isMobile) {\n return {\n onClick: onBurgerClick,\n tag: { value: hasNotifications || hasMenuNotifications },\n };\n }\n\n // !hasTopBar always false - we hide burger on desktop for now\n if (navVariant === 'left' && !hasTopBar) {\n return hasSideBar\n ? {\n 'onClick': onBurgerClick,\n 'tooltip': expanded ? 'Collapse' : 'Expand',\n 'data-pendo': 'navigation-left-options',\n 'data-cy': 'navigation-left-options',\n }\n : undefined;\n }\n\n return undefined;\n }, [\n isMobile,\n hasTopBar,\n navVariant,\n expanded,\n onBurgerClick,\n hasNotifications,\n hasMenuNotifications,\n hasSideBar,\n ]);\n\n return (\n <LayoutContext.Provider value={context}>\n <LayoutPlacementContext.Provider value=\"unset\">\n <div\n id={id}\n className={classNames(\n Styles.layout,\n isMobile ? Styles.layoutMobile : Styles.layoutDesktop,\n noResponsive && Styles.layoutNoResponsive,\n hasTopBar &&\n (navVariant === 'left' || !top\n ? Styles.layoutTopLight\n : Styles.layoutTopNav),\n {\n [Styles.layoutNavSlim]: !isMobile && hasSideBar && !expanded,\n [Styles.layoutNavWide]: !isMobile && hasSideBar && expanded,\n },\n layoutClass\n )}\n style={contentStyles}\n >\n <div className={Styles.topPlaceholder} />\n {hasTopBar &&\n (navVariant === 'left' ? (\n <LayoutHeader\n className={Styles.top}\n variant=\"light\"\n logo={logo}\n profile={isMobile ? undefined : profile}\n center={top}\n rightText={isMobile ? undefined : extraText}\n right={\n <Fragment>\n {extraLinksTop}\n {!isMobile && extraLinks}\n </Fragment>\n }\n isMobile={isMobile}\n burger={burgerProps}\n />\n ) : (\n <LayoutHeaderDark\n className={Styles.top}\n logo={logo}\n profile={isMobile ? undefined : profile}\n center={top}\n rightText={isMobile ? undefined : extraText}\n right={\n <Fragment>\n {extraLinksTop}\n {!isMobile && extraLinks}\n </Fragment>\n }\n isMobile={isMobile}\n burger={burgerProps}\n navigationMainItems={navigationMainItems}\n navigationOverflowItems={navigationOverflowItems}\n />\n ))}\n\n {hasSideBar && (\n <NotificationsContextProvider>\n <LayoutSidebar\n className={Styles.side}\n mobile={breakpoint.isMobile}\n touchDevice={breakpoint.isTouchDevice}\n barExpanded={!!expanded}\n onBarExpandChange={onBarExpandChange}\n submenusOpened={openSubmenus}\n onSubmenuOpenChange={onSubmenuOpenChange}\n drawerOpened={mobileDrawerOpened}\n onDrawerOpenChange={setMobileDrawerOpened}\n top={sideTop}\n mainItems={sidebarNavItems}\n navigationComponent={context.NavigationComponent}\n onNavigationLinkClick={onNavigationLinkClick}\n bottom={\n isMobile ? (\n <Fragment>\n {profile}\n {extraLinks}\n {!!extraText && (\n <InternalSideNavigationTrigger\n id=\"--extra-text\"\n title={extraText}\n isActive={undefined}\n icon={undefined}\n iconActive={undefined}\n tag={undefined}\n className={undefined}\n />\n )}\n </Fragment>\n ) : undefined\n }\n />\n </NotificationsContextProvider>\n )}\n\n {view.isAnvil2 ? (\n <TitanLayoutHeader>\n <ObservedHeight heightChange={setHeaderHeight}>{header}</ObservedHeight>\n </TitanLayoutHeader>\n ) : view.isLegacy ? (\n <TitanLayoutHeader>{header}</TitanLayoutHeader>\n ) : null}\n {view.isAnvil1 ? (\n <LayoutContentAnvil1 header={header} minWidth={limitContentWidth}>\n {children}\n </LayoutContentAnvil1>\n ) : view.isLegacy ? (\n <LayoutContentLegacy minWidth={limitContentWidth}>\n {children}\n </LayoutContentLegacy>\n ) : (\n children\n )}\n </div>\n </LayoutPlacementContext.Provider>\n </LayoutContext.Provider>\n );\n}\n\nconst ObservedHeight: FC<{\n children: ReactNode;\n heightChange?(value: number): void;\n}> = ({ children, heightChange }) => {\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n if (ref.current) {\n const updatePosition = () => {\n if (ref.current && heightChange) {\n const pos = ref.current.getBoundingClientRect();\n heightChange(pos.height);\n }\n };\n\n const observer = new ResizeObserver(updatePosition);\n observer.observe(ref.current);\n\n updatePosition();\n return () => observer.disconnect();\n }\n }, [heightChange]);\n\n useEffect(() => {\n return () => {\n heightChange?.(0);\n };\n }, [heightChange]);\n return <div ref={ref}>{children}</div>;\n};\nconst TitanLayoutHeader: FC<{ children: ReactNode }> = ({ children }) => {\n return (\n <div className={Styles.contentHeader} data-cy=\"layout-content-header\">\n {children}\n </div>\n );\n};\n\nconst LayoutContentAnvil1: FC<{\n children: ReactNode;\n header?: ReactNode;\n minWidth?: number;\n}> = ({ children, header, minWidth }) => {\n const innerContentStyles: CSSProperties = useMemo(\n () => ({\n ...(minWidth ? { minWidth: `${minWidth}px`, overflowX: 'auto' } : {}),\n }),\n [minWidth]\n );\n\n return (\n <Fragment>\n <TitanLayoutHeader>{header}</TitanLayoutHeader>\n <div\n className={classNames(Styles.content, { 'of-x-auto-i': !!minWidth })}\n data-cy=\"layout-content\"\n >\n <div\n className=\"position-relative d-f flex-grow-1 flex-basis-0 of-hidden\"\n style={innerContentStyles}\n >\n {children}\n </div>\n </div>\n </Fragment>\n );\n};\n\nconst LayoutContentLegacy: FC<{\n children: ReactNode;\n minWidth: number | undefined;\n}> = ({ children, minWidth }) => {\n const innerContentStyles: CSSProperties = useMemo(\n () => ({\n position: 'relative',\n minWidth: `${minWidth}px`,\n }),\n [minWidth]\n );\n\n return <div style={minWidth ? innerContentStyles : undefined}>{children}</div>;\n};\n\nexport const TitanLayout = Object.assign(TitanLayoutComponent, {\n Link: TitanLayoutLink,\n Trigger: TitanLayoutTrigger,\n});\n"],"names":["classNames","Fragment","useCallback","useEffect","useMemo","useRef","useState","DefaultNavLinkComponent","useTitanBreakpoint","LayoutContext","LayoutPlacementContext","LayoutHeader","LayoutHeaderDark","LayoutSidebar","InternalSideNavigationTrigger","useNotificationsState","TitanLayoutLink","TitanLayoutTrigger","Styles","defaultSidebarContext","styles","popoverContent","useAppearance","appearance","isLegacy","isAnvil1","isAnvil2","isSequent","useViewportConfigs","noResponsive","meta","document","head","querySelector","content","TitanLayoutComponent","navVariant","id","children","contentOnly","navigationComponent","header","top","profile","expanded","openSubmenus","logo","onExpandedChange","onOpenSubmenuChange","navigationMainItems","navigationOverflowItems","onNavigationLinkClick","extraLinks","extraLinksTop","extraText","minContentWidth","sideTop","breakpoint","context","NavigationComponent","isTitanLayout","sidebar","view","mobileDrawerOpened","setMobileDrawerOpened","hasNotifications","NotificationsContextProvider","headerHeight","setHeaderHeight","isMobile","hasSideBar","length","hasTopBar","bodyClassName","body","classList","add","remove","onBurgerClick","e","stopPropagation","onBarExpandChange","onSubmenuOpenChange","opened","sidebarNavItems","undefined","hasMenuNotifications","some","item","counter","tag","value","submenu","groups","group","links","link","limitContentWidth","width","contentStyles","layoutClass","layoutLegacy","layoutAnvil1","layoutAnvil2","burgerProps","onClick","Provider","div","className","layout","layoutMobile","layoutDesktop","layoutNoResponsive","layoutTopLight","layoutTopNav","layoutNavSlim","layoutNavWide","style","topPlaceholder","variant","center","rightText","right","burger","side","mobile","touchDevice","isTouchDevice","barExpanded","submenusOpened","drawerOpened","onDrawerOpenChange","mainItems","bottom","title","isActive","icon","iconActive","TitanLayoutHeader","ObservedHeight","heightChange","LayoutContentAnvil1","minWidth","LayoutContentLegacy","ref","current","updatePosition","pos","getBoundingClientRect","height","observer","ResizeObserver","observe","disconnect","contentHeader","data-cy","innerContentStyles","overflowX","position","TitanLayout","Object","assign","Link","Trigger"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/components/titan-layout/titan-layout.tsx"],"sourcesContent":["import classNames from 'classnames';\nimport {\n CSSProperties,\n ComponentPropsWithoutRef,\n FC,\n Fragment,\n MouseEvent,\n ReactElement,\n ReactNode,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { NavigationItemData } from '../../utils/navigation';\nimport { DefaultNavLinkComponent, NavLinkComponentProps } from '../../utils/navigation-context';\nimport { updateOpenSubmenus } from '../../utils/submenu-state';\nimport { useTitanBreakpoint } from '../../utils/use-breakpoint';\nimport { PinnedNavigationConfig } from './interface';\nimport {\n LayoutContext,\n LayoutPlacementContext,\n TitanLayoutContextType,\n TitanLayoutSidebarContextType,\n} from './layout-context';\nimport { LayoutHeader, LayoutHeaderProps } from './layout-header';\nimport { LayoutHeaderDark } from './layout-header-dark';\nimport { TitanLayoutLogoProps } from './layout-logo';\nimport { LayoutSidebar } from './layout-sidebar';\nimport { InternalSideNavigationTrigger } from './layout-sidebar-links-internal';\nimport { useNotificationsState } from './notifications-context';\nimport { TitanLayoutLink, TitanLayoutTrigger } from './titan-layout-links';\nimport * as Styles from './titan-layout.module.less';\n\nexport type TitanLayoutProps = Omit<ComponentPropsWithoutRef<'div'>, 'children' | 'style'> & {\n /** layout appearance */\n appearance?: 'legacy' | 'anvil1' | 'anvil2';\n\n /** layout navigation variant (left by default) */\n navVariant?: 'left' | 'top';\n\n /** layout's content */\n children?: ReactNode;\n\n /** show only content without side and top bars */\n contentOnly?: boolean;\n\n /** component used for navigation */\n navigationComponent?: FC<NavLinkComponentProps>;\n\n /** data for main navigation links */\n navigationMainItems?: NavigationItemData[];\n\n /**\n * Turns the navigation catalog on: renders a trigger in the sidebar and the dialog behind it.\n * Omit it and the navigation is unchanged.\n */\n pinnedNavigation?: PinnedNavigationConfig;\n\n /** data for overflow navigation links (used only with top variant) */\n navigationOverflowItems?: NavigationItemData[];\n\n /** logo props */\n logo?: TitanLayoutLogoProps;\n\n /** expanded state */\n expanded?: boolean;\n /** expanded state change handler */\n onExpandedChange?: (expanded: boolean) => void;\n\n /** submenus accordion opened state */\n openSubmenus?: string[];\n /** submenu accordion open state change handler */\n onOpenSubmenuChange?: (id: string, opened: boolean) => void;\n /**\n * Receives the complete next set of open modules for individual and bulk actions.\n * Preferred over onOpenSubmenuChange when provided, so bulk updates are atomic.\n * Required for the rail's `Expand All` / `Collapse All` control, which is hidden\n * for hosts that supply only the per-module `onOpenSubmenuChange`.\n */\n onOpenSubmenusChange?: (ids: string[]) => void;\n\n /** navigation link click handler */\n onNavigationLinkClick?: (itemId: string, submenuItemId?: string) => void;\n\n /** content header content */\n header?: ReactElement;\n\n /** layout header content (center) */\n top?: ReactElement;\n\n /** top links for side navigation */\n sideTop?: ReactElement[];\n\n /**\n * Destinations pinned above the sidebar's divider, ahead of `sideTop`.\n *\n * Rendered with the same machinery as `navigationMainItems`, so one with pages beneath it is\n * an expandable group rather than a bare link. `sideTop` is unchanged and still takes elements\n * the host builds itself; the two can be used together.\n */\n sideTopItems?: NavigationItemData[];\n\n /**\n * Whether the sidebar shows the scroll and overflow-notification indicators.\n *\n * Defaults on, so a host that says nothing keeps them. Navigation versions that predate the\n * indicators pass `false`.\n */\n navigationScrollIndicators?: boolean;\n\n /**\n * profile element for layout\n * @see ProfileDropdown\n */\n profile?: ReactElement;\n\n /**\n * extra links for layout header\n * shown in side nav footer on mobile\n */\n extraLinks?: ReactElement;\n\n /**\n * fixed extra links for layout header\n * shown in header on mobile as well\n */\n extraLinksTop?: ReactElement;\n\n /**\n * text shown in layout's header\n * shown in side nav footer on mobile\n */\n extraText?: string;\n\n /**\n * minimal width set for content area\n * used for pages that aren't adopted to mobile\n */\n minContentWidth?: number;\n\n /**\n * disable responsive behavior\n * can be used for pages that aren't adopted to mobile\n */\n noResponsive?: boolean;\n};\n\nconst defaultSidebarContext: TitanLayoutSidebarContextType = {\n styles: {\n popoverContent: {\n '--background-color-strong': '#0f1d26',\n 'color': 'var(--color-white)',\n } as CSSProperties,\n },\n};\n\nconst useAppearance = (appearance: TitanLayoutProps['appearance']) =>\n useMemo(() => {\n const isLegacy = appearance === 'legacy';\n const isAnvil1 = appearance === 'anvil1';\n const isAnvil2 = appearance === 'anvil2';\n\n return {\n isLegacy,\n isAnvil1,\n isAnvil2,\n isSequent: isLegacy || isAnvil2,\n };\n }, [appearance]);\n\nexport const useViewportConfigs = (noResponsive?: boolean) => {\n useEffect(() => {\n const meta: HTMLMetaElement | null = document.head.querySelector('meta[name=\"viewport\"]');\n\n if (meta) {\n meta.content = noResponsive\n ? ''\n : 'width=device-width, initial-scale=1, maximum-scale=1';\n }\n }, [noResponsive]);\n};\n\nfunction TitanLayoutComponent({\n appearance = 'anvil2',\n navVariant = 'left',\n id,\n children,\n contentOnly,\n navigationComponent,\n header,\n top,\n profile,\n expanded,\n openSubmenus,\n logo,\n onExpandedChange,\n onOpenSubmenuChange,\n onOpenSubmenusChange,\n navigationMainItems,\n pinnedNavigation,\n navigationOverflowItems,\n onNavigationLinkClick,\n extraLinks,\n extraLinksTop,\n extraText,\n minContentWidth,\n noResponsive,\n sideTop,\n sideTopItems,\n navigationScrollIndicators = true,\n}: TitanLayoutProps) {\n const breakpoint = useTitanBreakpoint(noResponsive);\n const context: TitanLayoutContextType = useMemo(\n () => ({\n NavigationComponent: navigationComponent ?? DefaultNavLinkComponent,\n breakpoint,\n isTitanLayout: true,\n sidebar: defaultSidebarContext,\n }),\n [navigationComponent, breakpoint]\n );\n const view = useAppearance(appearance);\n const [mobileDrawerOpened, setMobileDrawerOpened] = useState(false);\n const { hasNotifications, NotificationsContextProvider } = useNotificationsState();\n const [headerHeight, setHeaderHeight] = useState(0);\n\n const isMobile = breakpoint.isMobile;\n const hasSideBar =\n !contentOnly &&\n (navVariant === 'left' || (navVariant === 'top' && isMobile)) &&\n (!!navigationMainItems?.length ||\n !!sideTop?.length ||\n !!sideTopItems?.length ||\n !!pinnedNavigation);\n const hasTopBar = !contentOnly;\n\n useEffect(() => {\n if (view.isAnvil1) {\n const bodyClassName = 'of-hidden-i';\n document.body.classList.add(bodyClassName);\n return () => document.body.classList.remove(bodyClassName);\n }\n }, [view.isAnvil1]);\n\n const onBurgerClick = useCallback(\n (e: MouseEvent<never>) => {\n if (isMobile) {\n setMobileDrawerOpened(true);\n } else {\n onExpandedChange?.(!expanded);\n }\n\n e.stopPropagation();\n },\n [isMobile, expanded, onExpandedChange]\n );\n\n const onBarExpandChange = useCallback(\n (expanded: boolean) => {\n if (isMobile) {\n setMobileDrawerOpened(false);\n } else {\n onExpandedChange?.(expanded);\n }\n },\n [onExpandedChange, isMobile]\n );\n const onSubmenuOpenChange = useCallback(\n (id: string, opened: boolean) => {\n if (onOpenSubmenusChange) {\n onOpenSubmenusChange(updateOpenSubmenus(openSubmenus ?? [], [id], opened));\n } else {\n onOpenSubmenuChange?.(id, opened);\n }\n },\n [onOpenSubmenuChange, onOpenSubmenusChange, openSubmenus]\n );\n /*\n * Bulk actions require `onOpenSubmenusChange`, which takes the whole next set at once.\n * They are deliberately not emulated with `onOpenSubmenuChange`: that contract is one\n * call per user action, so a host deriving its next list from the current one would see\n * the same stale list on every call of a loop and keep only the last module. `Expand All`\n * is hidden entirely when a host supplies only the per-module callback.\n */\n const onSubmenusOpenChange = useCallback(\n (ids: string[], opened: boolean) => {\n onOpenSubmenusChange?.(updateOpenSubmenus(openSubmenus ?? [], ids, opened));\n },\n [onOpenSubmenusChange, openSubmenus]\n );\n const sidebarNavItems = useMemo(() => {\n if (!hasSideBar) {\n return undefined;\n }\n\n if (navVariant === 'left') {\n return navigationMainItems;\n }\n\n return [...(navigationMainItems ?? []), ...(navigationOverflowItems ?? [])];\n }, [hasSideBar, navigationMainItems, navigationOverflowItems, navVariant]);\n\n /*\n * The burger badge speaks for the whole drawer, so it has to count what sits above the\n * divider as well. A permanent destination asking for attention would otherwise go unseen\n * while the drawer is closed.\n */\n const badgeNavItems = useMemo(\n /*\n * Gated on `hasSideBar`, not `sidebarNavItems`: on the left variant a rail that exists\n * only for `sideTopItems` still has `sidebarNavItems` (i.e. `navigationMainItems`) undefined.\n */\n () => (hasSideBar ? [...(sideTopItems ?? []), ...(sidebarNavItems ?? [])] : undefined),\n [hasSideBar, sideTopItems, sidebarNavItems]\n );\n\n const hasMenuNotifications = useMemo(() => {\n try {\n return (\n badgeNavItems?.some(item => {\n if (item.counter || item.tag?.value) {\n return true;\n } else if (item.submenu) {\n return item.submenu.groups.some(group =>\n group.links.some(link => !!link.counter || !!link.tag?.value)\n );\n }\n return false;\n }) ?? false\n );\n } catch {\n return false;\n }\n }, [badgeNavItems]);\n\n const limitContentWidth = useMemo(() => {\n if (view.isAnvil2 || !minContentWidth) {\n return undefined;\n }\n\n if (breakpoint.width < minContentWidth) {\n return minContentWidth;\n }\n }, [view, minContentWidth, breakpoint.width]);\n\n const contentStyles = useMemo(() => {\n if (view.isAnvil2) {\n return {\n '--content-offset-top': `calc(var(--nav-offset-top) + ${headerHeight}px)`,\n } as CSSProperties;\n }\n }, [view, headerHeight]);\n\n const layoutClass = view.isLegacy\n ? Styles.layoutLegacy\n : view.isAnvil1\n ? Styles.layoutAnvil1\n : Styles.layoutAnvil2;\n\n const burgerProps: LayoutHeaderProps['burger'] = useMemo(() => {\n if (!hasTopBar) {\n return undefined;\n }\n\n if (isMobile) {\n return {\n onClick: onBurgerClick,\n tag: { value: hasNotifications || hasMenuNotifications },\n };\n }\n\n // !hasTopBar always false - we hide burger on desktop for now\n if (navVariant === 'left' && !hasTopBar) {\n return hasSideBar\n ? {\n 'onClick': onBurgerClick,\n 'tooltip': expanded ? 'Collapse' : 'Expand',\n 'data-pendo': 'navigation-left-options',\n 'data-cy': 'navigation-left-options',\n }\n : undefined;\n }\n\n return undefined;\n }, [\n isMobile,\n hasTopBar,\n navVariant,\n expanded,\n onBurgerClick,\n hasNotifications,\n hasMenuNotifications,\n hasSideBar,\n ]);\n\n return (\n <LayoutContext.Provider value={context}>\n <LayoutPlacementContext.Provider value=\"unset\">\n <div\n id={id}\n className={classNames(\n Styles.layout,\n isMobile ? Styles.layoutMobile : Styles.layoutDesktop,\n noResponsive && Styles.layoutNoResponsive,\n hasTopBar &&\n (navVariant === 'left' || !top\n ? Styles.layoutTopLight\n : Styles.layoutTopNav),\n {\n [Styles.layoutNavSlim]: !isMobile && hasSideBar && !expanded,\n [Styles.layoutNavWide]: !isMobile && hasSideBar && expanded,\n },\n layoutClass\n )}\n style={contentStyles}\n >\n <div className={Styles.topPlaceholder} />\n {hasTopBar &&\n (navVariant === 'left' ? (\n <LayoutHeader\n className={Styles.top}\n variant=\"light\"\n logo={logo}\n profile={isMobile ? undefined : profile}\n center={top}\n rightText={isMobile ? undefined : extraText}\n right={\n <Fragment>\n {extraLinksTop}\n {!isMobile && extraLinks}\n </Fragment>\n }\n isMobile={isMobile}\n burger={burgerProps}\n />\n ) : (\n <LayoutHeaderDark\n className={Styles.top}\n logo={logo}\n profile={isMobile ? undefined : profile}\n center={top}\n rightText={isMobile ? undefined : extraText}\n right={\n <Fragment>\n {extraLinksTop}\n {!isMobile && extraLinks}\n </Fragment>\n }\n isMobile={isMobile}\n burger={burgerProps}\n navigationMainItems={navigationMainItems}\n navigationOverflowItems={navigationOverflowItems}\n />\n ))}\n\n {hasSideBar && (\n <NotificationsContextProvider>\n <LayoutSidebar\n className={Styles.side}\n mobile={breakpoint.isMobile}\n touchDevice={breakpoint.isTouchDevice}\n barExpanded={!!expanded}\n onBarExpandChange={onBarExpandChange}\n submenusOpened={openSubmenus}\n onSubmenuOpenChange={onSubmenuOpenChange}\n onSubmenusOpenChange={\n onOpenSubmenusChange ? onSubmenusOpenChange : undefined\n }\n drawerOpened={mobileDrawerOpened}\n onDrawerOpenChange={setMobileDrawerOpened}\n top={sideTop}\n topItems={sideTopItems}\n scrollIndicators={navigationScrollIndicators}\n pinnedNavigation={pinnedNavigation}\n mainItems={sidebarNavItems}\n navigationComponent={context.NavigationComponent}\n onNavigationLinkClick={onNavigationLinkClick}\n bottom={\n isMobile ? (\n <Fragment>\n {profile}\n {extraLinks}\n {!!extraText && (\n <InternalSideNavigationTrigger\n id=\"--extra-text\"\n title={extraText}\n isActive={undefined}\n icon={undefined}\n iconActive={undefined}\n tag={undefined}\n className={undefined}\n />\n )}\n </Fragment>\n ) : undefined\n }\n />\n </NotificationsContextProvider>\n )}\n\n {view.isAnvil2 ? (\n <TitanLayoutHeader>\n <ObservedHeight heightChange={setHeaderHeight}>{header}</ObservedHeight>\n </TitanLayoutHeader>\n ) : view.isLegacy ? (\n <TitanLayoutHeader>{header}</TitanLayoutHeader>\n ) : null}\n {view.isAnvil1 ? (\n <LayoutContentAnvil1 header={header} minWidth={limitContentWidth}>\n {children}\n </LayoutContentAnvil1>\n ) : view.isLegacy ? (\n <LayoutContentLegacy minWidth={limitContentWidth}>\n {children}\n </LayoutContentLegacy>\n ) : (\n children\n )}\n </div>\n </LayoutPlacementContext.Provider>\n </LayoutContext.Provider>\n );\n}\n\nconst ObservedHeight: FC<{\n children: ReactNode;\n heightChange?(value: number): void;\n}> = ({ children, heightChange }) => {\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n if (ref.current) {\n const updatePosition = () => {\n if (ref.current && heightChange) {\n const pos = ref.current.getBoundingClientRect();\n heightChange(pos.height);\n }\n };\n\n const observer = new ResizeObserver(updatePosition);\n observer.observe(ref.current);\n\n updatePosition();\n return () => observer.disconnect();\n }\n }, [heightChange]);\n\n useEffect(() => {\n return () => {\n heightChange?.(0);\n };\n }, [heightChange]);\n return <div ref={ref}>{children}</div>;\n};\nconst TitanLayoutHeader: FC<{ children: ReactNode }> = ({ children }) => {\n return (\n <div className={Styles.contentHeader} data-cy=\"layout-content-header\">\n {children}\n </div>\n );\n};\n\nconst LayoutContentAnvil1: FC<{\n children: ReactNode;\n header?: ReactNode;\n minWidth?: number;\n}> = ({ children, header, minWidth }) => {\n const innerContentStyles: CSSProperties = useMemo(\n () => ({\n ...(minWidth ? { minWidth: `${minWidth}px`, overflowX: 'auto' } : {}),\n }),\n [minWidth]\n );\n\n return (\n <Fragment>\n <TitanLayoutHeader>{header}</TitanLayoutHeader>\n <div\n className={classNames(Styles.content, { 'of-x-auto-i': !!minWidth })}\n data-cy=\"layout-content\"\n >\n <div\n className=\"position-relative d-f flex-grow-1 flex-basis-0 of-hidden\"\n style={innerContentStyles}\n >\n {children}\n </div>\n </div>\n </Fragment>\n );\n};\n\nconst LayoutContentLegacy: FC<{\n children: ReactNode;\n minWidth: number | undefined;\n}> = ({ children, minWidth }) => {\n const innerContentStyles: CSSProperties = useMemo(\n () => ({\n position: 'relative',\n minWidth: `${minWidth}px`,\n }),\n [minWidth]\n );\n\n return <div style={minWidth ? innerContentStyles : undefined}>{children}</div>;\n};\n\nexport const TitanLayout = Object.assign(TitanLayoutComponent, {\n Link: TitanLayoutLink,\n Trigger: TitanLayoutTrigger,\n});\n"],"names":["classNames","Fragment","useCallback","useEffect","useMemo","useRef","useState","DefaultNavLinkComponent","updateOpenSubmenus","useTitanBreakpoint","LayoutContext","LayoutPlacementContext","LayoutHeader","LayoutHeaderDark","LayoutSidebar","InternalSideNavigationTrigger","useNotificationsState","TitanLayoutLink","TitanLayoutTrigger","Styles","defaultSidebarContext","styles","popoverContent","useAppearance","appearance","isLegacy","isAnvil1","isAnvil2","isSequent","useViewportConfigs","noResponsive","meta","document","head","querySelector","content","TitanLayoutComponent","navVariant","id","children","contentOnly","navigationComponent","header","top","profile","expanded","openSubmenus","logo","onExpandedChange","onOpenSubmenuChange","onOpenSubmenusChange","navigationMainItems","pinnedNavigation","navigationOverflowItems","onNavigationLinkClick","extraLinks","extraLinksTop","extraText","minContentWidth","sideTop","sideTopItems","navigationScrollIndicators","breakpoint","context","NavigationComponent","isTitanLayout","sidebar","view","mobileDrawerOpened","setMobileDrawerOpened","hasNotifications","NotificationsContextProvider","headerHeight","setHeaderHeight","isMobile","hasSideBar","length","hasTopBar","bodyClassName","body","classList","add","remove","onBurgerClick","e","stopPropagation","onBarExpandChange","onSubmenuOpenChange","opened","onSubmenusOpenChange","ids","sidebarNavItems","undefined","badgeNavItems","hasMenuNotifications","some","item","counter","tag","value","submenu","groups","group","links","link","limitContentWidth","width","contentStyles","layoutClass","layoutLegacy","layoutAnvil1","layoutAnvil2","burgerProps","onClick","Provider","div","className","layout","layoutMobile","layoutDesktop","layoutNoResponsive","layoutTopLight","layoutTopNav","layoutNavSlim","layoutNavWide","style","topPlaceholder","variant","center","rightText","right","burger","side","mobile","touchDevice","isTouchDevice","barExpanded","submenusOpened","drawerOpened","onDrawerOpenChange","topItems","scrollIndicators","mainItems","bottom","title","isActive","icon","iconActive","TitanLayoutHeader","ObservedHeight","heightChange","LayoutContentAnvil1","minWidth","LayoutContentLegacy","ref","current","updatePosition","pos","getBoundingClientRect","height","observer","ResizeObserver","observe","disconnect","contentHeader","data-cy","innerContentStyles","overflowX","position","TitanLayout","Object","assign","Link","Trigger"],"mappings":"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"}
@@ -82,7 +82,8 @@
82
82
  }
83
83
 
84
84
  &.layout-desktop.layout-nav-wide {
85
- --nav-offset-left: 212px;
85
+ // Figma 042. Pantheon: the expanded rail is 220 wide (a 204 row column inset 8 each side).
86
+ --nav-offset-left: 220px;
86
87
  }
87
88
 
88
89
  .top {
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Measures what the rail's scroller is hiding, for the overflow notification pills.
3
+ *
4
+ * `enabled` is false on navigation versions that do not show the indicators. The refs are still
5
+ * handed back so the caller's markup is unchanged, but nothing is observed and no state is kept —
6
+ * this effect measures on every scroll and resize, which is not work to do for a feature that is
7
+ * switched off.
8
+ */
9
+ export declare function useNavigationScroll(enabled?: boolean): {
10
+ revealNotification: (direction: "above" | "below") => void;
11
+ above: number;
12
+ below: number;
13
+ hasContentAbove: boolean;
14
+ hasContentBelow: boolean;
15
+ regionRef: import("react").RefObject<HTMLDivElement>;
16
+ scrollRef: import("react").RefObject<HTMLDivElement>;
17
+ aboveIndicatorRef: import("react").RefObject<HTMLDivElement>;
18
+ belowIndicatorRef: import("react").RefObject<HTMLDivElement>;
19
+ };
20
+ //# sourceMappingURL=use-navigation-scroll.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-navigation-scroll.d.ts","sourceRoot":"","sources":["../../../src/components/titan-layout/use-navigation-scroll.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,UAAO;oCAoG9B,OAAO,GAAG,OAAO;;;;;;;;;EAiCpC"}
@@ -0,0 +1,140 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
+ import { getOverflowNotifications } from '../../utils/navigation-notifications';
3
+ /**
4
+ * Measures what the rail's scroller is hiding, for the overflow notification pills.
5
+ *
6
+ * `enabled` is false on navigation versions that do not show the indicators. The refs are still
7
+ * handed back so the caller's markup is unchanged, but nothing is observed and no state is kept —
8
+ * this effect measures on every scroll and resize, which is not work to do for a feature that is
9
+ * switched off.
10
+ */ export function useNavigationScroll(enabled = true) {
11
+ const regionRef = useRef(null);
12
+ const scrollRef = useRef(null);
13
+ const aboveIndicatorRef = useRef(null);
14
+ const belowIndicatorRef = useRef(null);
15
+ const targets = useRef({});
16
+ const [overflow, setOverflow] = useState({
17
+ above: 0,
18
+ below: 0,
19
+ hasContentAbove: false,
20
+ hasContentBelow: false
21
+ });
22
+ /*
23
+ * The counted area, inset from the scrolling box by whichever notification pills are on
24
+ * screen. The pills float over the rail's own edges, so a row one covers is hidden even
25
+ * though it still sits inside the scroller — without the inset the rail can report a count
26
+ * of zero while a pill is physically covering the row it should be counting. Only the pills
27
+ * inset: the scroll shadow is a separate element precisely so its 12px gradient, which
28
+ * hides nothing, does not shrink the area.
29
+ */ const getCountedViewport = useCallback((scroller)=>{
30
+ var _ref, _ref1;
31
+ var _aboveIndicatorRef_current, _belowIndicatorRef_current;
32
+ const { top, bottom } = scroller.getBoundingClientRect();
33
+ return {
34
+ top: top + ((_ref = (_aboveIndicatorRef_current = aboveIndicatorRef.current) === null || _aboveIndicatorRef_current === void 0 ? void 0 : _aboveIndicatorRef_current.offsetHeight) !== null && _ref !== void 0 ? _ref : 0),
35
+ bottom: bottom - ((_ref1 = (_belowIndicatorRef_current = belowIndicatorRef.current) === null || _belowIndicatorRef_current === void 0 ? void 0 : _belowIndicatorRef_current.offsetHeight) !== null && _ref1 !== void 0 ? _ref1 : 0)
36
+ };
37
+ }, []);
38
+ useEffect(()=>{
39
+ const scroller = scrollRef.current;
40
+ const region = regionRef.current;
41
+ if (!enabled || !scroller || !region) {
42
+ return;
43
+ }
44
+ let frame = 0;
45
+ const measure = ()=>{
46
+ frame = 0;
47
+ const viewport = getCountedViewport(scroller);
48
+ const rows = Array.from(scroller.querySelectorAll('[data-navigation-notification-count]'));
49
+ const { above, below, aboveIndex, belowIndex } = getOverflowNotifications(rows.map((row)=>{
50
+ const { top, bottom } = row.getBoundingClientRect();
51
+ return {
52
+ top,
53
+ bottom,
54
+ count: Number(row.dataset.navigationNotificationCount) || 0
55
+ };
56
+ }), viewport);
57
+ targets.current = {
58
+ above: rows[aboveIndex],
59
+ below: rows[belowIndex]
60
+ };
61
+ const hasContentAbove = scroller.scrollTop > 1;
62
+ const hasContentBelow = scroller.scrollHeight - scroller.clientHeight - scroller.scrollTop > 1;
63
+ setOverflow((previous)=>previous.above === above && previous.below === below && previous.hasContentAbove === hasContentAbove && previous.hasContentBelow === hasContentBelow ? previous : {
64
+ above,
65
+ below,
66
+ hasContentAbove,
67
+ hasContentBelow
68
+ });
69
+ };
70
+ const scheduleMeasure = ()=>{
71
+ if (!frame) {
72
+ frame = requestAnimationFrame(measure);
73
+ }
74
+ };
75
+ const resizeObserver = new ResizeObserver(scheduleMeasure);
76
+ resizeObserver.observe(scroller);
77
+ if (scroller.firstElementChild) {
78
+ resizeObserver.observe(scroller.firstElementChild);
79
+ }
80
+ const mutationObserver = new MutationObserver(scheduleMeasure);
81
+ /*
82
+ * The whole region rather than just the scroller: the pills mount and unmount as siblings
83
+ * of the scrolling element, and their height feeds back into the counted area above. The
84
+ * pass this schedules settles immediately, since a pill's height does not change with the
85
+ * number it shows (the label cannot wrap).
86
+ */ mutationObserver.observe(region, {
87
+ childList: true,
88
+ subtree: true,
89
+ attributes: true,
90
+ attributeFilter: [
91
+ 'data-navigation-notification-count',
92
+ 'class',
93
+ 'style'
94
+ ]
95
+ });
96
+ scroller.addEventListener('scroll', scheduleMeasure, {
97
+ passive: true
98
+ });
99
+ scheduleMeasure();
100
+ return ()=>{
101
+ cancelAnimationFrame(frame);
102
+ resizeObserver.disconnect();
103
+ mutationObserver.disconnect();
104
+ scroller.removeEventListener('scroll', scheduleMeasure);
105
+ };
106
+ }, [
107
+ enabled,
108
+ getCountedViewport
109
+ ]);
110
+ const revealNotification = useCallback((direction)=>{
111
+ const scroller = scrollRef.current;
112
+ const target = targets.current[direction];
113
+ if (!scroller || !target) {
114
+ return;
115
+ }
116
+ // Centred within the counted area, so the row lands clear of both pills.
117
+ const viewport = getCountedViewport(scroller);
118
+ const row = target.getBoundingClientRect();
119
+ // Reveal the item without activating its link or changing the selected destination.
120
+ target.focus({
121
+ preventScroll: true
122
+ });
123
+ scroller.scrollTo({
124
+ top: scroller.scrollTop + row.top - viewport.top - (viewport.bottom - viewport.top - row.height) / 2,
125
+ behavior: window.matchMedia('(prefers-reduced-motion: reduce)').matches ? 'auto' : 'smooth'
126
+ });
127
+ }, [
128
+ getCountedViewport
129
+ ]);
130
+ return {
131
+ regionRef,
132
+ scrollRef,
133
+ aboveIndicatorRef,
134
+ belowIndicatorRef,
135
+ ...overflow,
136
+ revealNotification
137
+ };
138
+ }
139
+
140
+ //# sourceMappingURL=use-navigation-scroll.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/titan-layout/use-navigation-scroll.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\nimport { getOverflowNotifications } from '../../utils/navigation-notifications';\n\n/**\n * Measures what the rail's scroller is hiding, for the overflow notification pills.\n *\n * `enabled` is false on navigation versions that do not show the indicators. The refs are still\n * handed back so the caller's markup is unchanged, but nothing is observed and no state is kept —\n * this effect measures on every scroll and resize, which is not work to do for a feature that is\n * switched off.\n */\nexport function useNavigationScroll(enabled = true) {\n const regionRef = useRef<HTMLDivElement>(null);\n const scrollRef = useRef<HTMLDivElement>(null);\n const aboveIndicatorRef = useRef<HTMLDivElement>(null);\n const belowIndicatorRef = useRef<HTMLDivElement>(null);\n const targets = useRef<{ above?: HTMLElement; below?: HTMLElement }>({});\n const [overflow, setOverflow] = useState({\n above: 0,\n below: 0,\n hasContentAbove: false,\n hasContentBelow: false,\n });\n\n /*\n * The counted area, inset from the scrolling box by whichever notification pills are on\n * screen. The pills float over the rail's own edges, so a row one covers is hidden even\n * though it still sits inside the scroller — without the inset the rail can report a count\n * of zero while a pill is physically covering the row it should be counting. Only the pills\n * inset: the scroll shadow is a separate element precisely so its 12px gradient, which\n * hides nothing, does not shrink the area.\n */\n const getCountedViewport = useCallback((scroller: HTMLElement) => {\n const { top, bottom } = scroller.getBoundingClientRect();\n return {\n top: top + (aboveIndicatorRef.current?.offsetHeight ?? 0),\n bottom: bottom - (belowIndicatorRef.current?.offsetHeight ?? 0),\n };\n }, []);\n\n useEffect(() => {\n const scroller = scrollRef.current;\n const region = regionRef.current;\n if (!enabled || !scroller || !region) {\n return;\n }\n let frame = 0;\n const measure = () => {\n frame = 0;\n const viewport = getCountedViewport(scroller);\n const rows = Array.from(\n scroller.querySelectorAll<HTMLElement>('[data-navigation-notification-count]')\n );\n const { above, below, aboveIndex, belowIndex } = getOverflowNotifications(\n rows.map(row => {\n const { top, bottom } = row.getBoundingClientRect();\n return {\n top,\n bottom,\n count: Number(row.dataset.navigationNotificationCount) || 0,\n };\n }),\n viewport\n );\n targets.current = { above: rows[aboveIndex], below: rows[belowIndex] };\n const hasContentAbove = scroller.scrollTop > 1;\n const hasContentBelow =\n scroller.scrollHeight - scroller.clientHeight - scroller.scrollTop > 1;\n setOverflow(previous =>\n previous.above === above &&\n previous.below === below &&\n previous.hasContentAbove === hasContentAbove &&\n previous.hasContentBelow === hasContentBelow\n ? previous\n : { above, below, hasContentAbove, hasContentBelow }\n );\n };\n const scheduleMeasure = () => {\n if (!frame) {\n frame = requestAnimationFrame(measure);\n }\n };\n const resizeObserver = new ResizeObserver(scheduleMeasure);\n resizeObserver.observe(scroller);\n if (scroller.firstElementChild) {\n resizeObserver.observe(scroller.firstElementChild);\n }\n const mutationObserver = new MutationObserver(scheduleMeasure);\n /*\n * The whole region rather than just the scroller: the pills mount and unmount as siblings\n * of the scrolling element, and their height feeds back into the counted area above. The\n * pass this schedules settles immediately, since a pill's height does not change with the\n * number it shows (the label cannot wrap).\n */\n mutationObserver.observe(region, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['data-navigation-notification-count', 'class', 'style'],\n });\n scroller.addEventListener('scroll', scheduleMeasure, { passive: true });\n scheduleMeasure();\n return () => {\n cancelAnimationFrame(frame);\n resizeObserver.disconnect();\n mutationObserver.disconnect();\n scroller.removeEventListener('scroll', scheduleMeasure);\n };\n }, [enabled, getCountedViewport]);\n\n const revealNotification = useCallback(\n (direction: 'above' | 'below') => {\n const scroller = scrollRef.current;\n const target = targets.current[direction];\n if (!scroller || !target) {\n return;\n }\n // Centred within the counted area, so the row lands clear of both pills.\n const viewport = getCountedViewport(scroller);\n const row = target.getBoundingClientRect();\n // Reveal the item without activating its link or changing the selected destination.\n target.focus({ preventScroll: true });\n scroller.scrollTo({\n top:\n scroller.scrollTop +\n row.top -\n viewport.top -\n (viewport.bottom - viewport.top - row.height) / 2,\n behavior: window.matchMedia('(prefers-reduced-motion: reduce)').matches\n ? 'auto'\n : 'smooth',\n });\n },\n [getCountedViewport]\n );\n\n return {\n regionRef,\n scrollRef,\n aboveIndicatorRef,\n belowIndicatorRef,\n ...overflow,\n revealNotification,\n };\n}\n"],"names":["useCallback","useEffect","useRef","useState","getOverflowNotifications","useNavigationScroll","enabled","regionRef","scrollRef","aboveIndicatorRef","belowIndicatorRef","targets","overflow","setOverflow","above","below","hasContentAbove","hasContentBelow","getCountedViewport","scroller","top","bottom","getBoundingClientRect","current","offsetHeight","region","frame","measure","viewport","rows","Array","from","querySelectorAll","aboveIndex","belowIndex","map","row","count","Number","dataset","navigationNotificationCount","scrollTop","scrollHeight","clientHeight","previous","scheduleMeasure","requestAnimationFrame","resizeObserver","ResizeObserver","observe","firstElementChild","mutationObserver","MutationObserver","childList","subtree","attributes","attributeFilter","addEventListener","passive","cancelAnimationFrame","disconnect","removeEventListener","revealNotification","direction","target","focus","preventScroll","scrollTo","height","behavior","window","matchMedia","matches"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=catalog-groups.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-groups.test.d.ts","sourceRoot":"","sources":["../../../src/utils/__tests__/catalog-groups.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=navigation-notifications.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"navigation-notifications.test.d.ts","sourceRoot":"","sources":["../../../src/utils/__tests__/navigation-notifications.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=submenu-state.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"submenu-state.test.d.ts","sourceRoot":"","sources":["../../../src/utils/__tests__/submenu-state.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,11 @@
1
+ import { NavigationCatalogGroup } from '../components/titan-layout/interface';
2
+ import { NavigationItemData } from './navigation';
3
+ /** Preserve submenu headings while offering each page only once within its module. */
4
+ export declare const getCatalogSubmenuGroups: (item: NavigationItemData) => {
5
+ title: string;
6
+ links: import("./navigation").NavigationSubmenuItemData[];
7
+ }[];
8
+ export declare const getCatalogChildren: (item: NavigationItemData) => import("./navigation").NavigationSubmenuItemData[];
9
+ /** Match destination titles literally, retaining the headings of matching child pages. */
10
+ export declare const filterCatalogGroups: (groups: NavigationCatalogGroup[], query: string) => NavigationCatalogGroup[];
11
+ //# sourceMappingURL=catalog-groups.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-groups.d.ts","sourceRoot":"","sources":["../../src/utils/catalog-groups.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AAC9E,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAElD,sFAAsF;AACtF,eAAO,MAAM,uBAAuB,GAAI,MAAM,kBAAkB;;;GAe/D,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,MAAM,kBAAkB,uDACI,CAAC;AAEhE,0FAA0F;AAC1F,eAAO,MAAM,mBAAmB,GAAI,QAAQ,sBAAsB,EAAE,EAAE,OAAO,MAAM,6BA+BlF,CAAC"}
@@ -0,0 +1,66 @@
1
+ /** Preserve submenu headings while offering each page only once within its module. */ export const getCatalogSubmenuGroups = (item)=>{
2
+ var _ref;
3
+ var _item_submenu;
4
+ const seen = new Set();
5
+ return ((_ref = (_item_submenu = item.submenu) === null || _item_submenu === void 0 ? void 0 : _item_submenu.groups) !== null && _ref !== void 0 ? _ref : []).flatMap((group)=>{
6
+ var _group_links, _ref;
7
+ var _group_title;
8
+ const links = ((_group_links = group.links) !== null && _group_links !== void 0 ? _group_links : []).filter((link)=>{
9
+ if (seen.has(link.id)) {
10
+ return false;
11
+ }
12
+ seen.add(link.id);
13
+ return true;
14
+ });
15
+ return links.length ? [
16
+ {
17
+ ...group,
18
+ title: (_ref = (_group_title = group.title) === null || _group_title === void 0 ? void 0 : _group_title.trim()) !== null && _ref !== void 0 ? _ref : '',
19
+ links
20
+ }
21
+ ] : [];
22
+ });
23
+ };
24
+ export const getCatalogChildren = (item)=>getCatalogSubmenuGroups(item).flatMap((group)=>group.links);
25
+ /** Match destination titles literally, retaining the headings of matching child pages. */ export const filterCatalogGroups = (groups, query)=>{
26
+ const needle = query.trim().toLowerCase();
27
+ if (!needle) {
28
+ return groups;
29
+ }
30
+ return groups.reduce((out, group)=>{
31
+ const items = group.items.reduce((kept, item)=>{
32
+ const matchingGroups = getCatalogSubmenuGroups(item).flatMap((submenuGroup)=>{
33
+ const links = submenuGroup.links.filter((child)=>child.title.toLowerCase().includes(needle));
34
+ return links.length ? [
35
+ {
36
+ ...submenuGroup,
37
+ links
38
+ }
39
+ ] : [];
40
+ });
41
+ if (matchingGroups.length > 0) {
42
+ kept.push({
43
+ ...item,
44
+ submenu: {
45
+ groups: matchingGroups
46
+ }
47
+ });
48
+ } else if (item.title.toLowerCase().includes(needle)) {
49
+ kept.push({
50
+ ...item,
51
+ submenu: undefined
52
+ });
53
+ }
54
+ return kept;
55
+ }, []);
56
+ if (items.length > 0) {
57
+ out.push({
58
+ ...group,
59
+ items
60
+ });
61
+ }
62
+ return out;
63
+ }, []);
64
+ };
65
+
66
+ //# sourceMappingURL=catalog-groups.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/catalog-groups.ts"],"sourcesContent":["import { NavigationCatalogGroup } from '../components/titan-layout/interface';\nimport { NavigationItemData } from './navigation';\n\n/** Preserve submenu headings while offering each page only once within its module. */\nexport const getCatalogSubmenuGroups = (item: NavigationItemData) => {\n const seen = new Set<string>();\n\n return (item.submenu?.groups ?? []).flatMap(group => {\n const links = (group.links ?? []).filter(link => {\n if (seen.has(link.id)) {\n return false;\n }\n\n seen.add(link.id);\n return true;\n });\n\n return links.length ? [{ ...group, title: group.title?.trim() ?? '', links }] : [];\n });\n};\n\nexport const getCatalogChildren = (item: NavigationItemData) =>\n getCatalogSubmenuGroups(item).flatMap(group => group.links);\n\n/** Match destination titles literally, retaining the headings of matching child pages. */\nexport const filterCatalogGroups = (groups: NavigationCatalogGroup[], query: string) => {\n const needle = query.trim().toLowerCase();\n\n if (!needle) {\n return groups;\n }\n\n return groups.reduce<NavigationCatalogGroup[]>((out, group) => {\n const items = group.items.reduce<NavigationItemData[]>((kept, item) => {\n const matchingGroups = getCatalogSubmenuGroups(item).flatMap(submenuGroup => {\n const links = submenuGroup.links.filter(child =>\n child.title.toLowerCase().includes(needle)\n );\n return links.length ? [{ ...submenuGroup, links }] : [];\n });\n\n if (matchingGroups.length > 0) {\n kept.push({ ...item, submenu: { groups: matchingGroups } });\n } else if (item.title.toLowerCase().includes(needle)) {\n kept.push({ ...item, submenu: undefined });\n }\n\n return kept;\n }, []);\n\n if (items.length > 0) {\n out.push({ ...group, items });\n }\n\n return out;\n }, []);\n};\n"],"names":["getCatalogSubmenuGroups","item","seen","Set","submenu","groups","flatMap","group","links","filter","link","has","id","add","length","title","trim","getCatalogChildren","filterCatalogGroups","query","needle","toLowerCase","reduce","out","items","kept","matchingGroups","submenuGroup","child","includes","push","undefined"],"mappings":"AAGA,oFAAoF,GACpF,OAAO,MAAMA,0BAA0B,CAACC;;QAG5BA;IAFR,MAAMC,OAAO,IAAIC;IAEjB,OAAO,UAACF,gBAAAA,KAAKG,OAAO,cAAZH,oCAAAA,cAAcI,MAAM,uCAAI,EAAE,EAAEC,OAAO,CAACC,CAAAA;YACzBA;YAS2BA;QAT1C,MAAMC,QAAQ,EAACD,eAAAA,MAAMC,KAAK,cAAXD,0BAAAA,eAAe,EAAE,EAAEE,MAAM,CAACC,CAAAA;YACrC,IAAIR,KAAKS,GAAG,CAACD,KAAKE,EAAE,GAAG;gBACnB,OAAO;YACX;YAEAV,KAAKW,GAAG,CAACH,KAAKE,EAAE;YAChB,OAAO;QACX;QAEA,OAAOJ,MAAMM,MAAM,GAAG;YAAC;gBAAE,GAAGP,KAAK;gBAAEQ,KAAK,WAAER,eAAAA,MAAMQ,KAAK,cAAXR,mCAAAA,aAAaS,IAAI,yCAAM;gBAAIR;YAAM;SAAE,GAAG,EAAE;IACtF;AACJ,EAAE;AAEF,OAAO,MAAMS,qBAAqB,CAAChB,OAC/BD,wBAAwBC,MAAMK,OAAO,CAACC,CAAAA,QAASA,MAAMC,KAAK,EAAE;AAEhE,wFAAwF,GACxF,OAAO,MAAMU,sBAAsB,CAACb,QAAkCc;IAClE,MAAMC,SAASD,MAAMH,IAAI,GAAGK,WAAW;IAEvC,IAAI,CAACD,QAAQ;QACT,OAAOf;IACX;IAEA,OAAOA,OAAOiB,MAAM,CAA2B,CAACC,KAAKhB;QACjD,MAAMiB,QAAQjB,MAAMiB,KAAK,CAACF,MAAM,CAAuB,CAACG,MAAMxB;YAC1D,MAAMyB,iBAAiB1B,wBAAwBC,MAAMK,OAAO,CAACqB,CAAAA;gBACzD,MAAMnB,QAAQmB,aAAanB,KAAK,CAACC,MAAM,CAACmB,CAAAA,QACpCA,MAAMb,KAAK,CAACM,WAAW,GAAGQ,QAAQ,CAACT;gBAEvC,OAAOZ,MAAMM,MAAM,GAAG;oBAAC;wBAAE,GAAGa,YAAY;wBAAEnB;oBAAM;iBAAE,GAAG,EAAE;YAC3D;YAEA,IAAIkB,eAAeZ,MAAM,GAAG,GAAG;gBAC3BW,KAAKK,IAAI,CAAC;oBAAE,GAAG7B,IAAI;oBAAEG,SAAS;wBAAEC,QAAQqB;oBAAe;gBAAE;YAC7D,OAAO,IAAIzB,KAAKc,KAAK,CAACM,WAAW,GAAGQ,QAAQ,CAACT,SAAS;gBAClDK,KAAKK,IAAI,CAAC;oBAAE,GAAG7B,IAAI;oBAAEG,SAAS2B;gBAAU;YAC5C;YAEA,OAAON;QACX,GAAG,EAAE;QAEL,IAAID,MAAMV,MAAM,GAAG,GAAG;YAClBS,IAAIO,IAAI,CAAC;gBAAE,GAAGvB,KAAK;gBAAEiB;YAAM;QAC/B;QAEA,OAAOD;IACX,GAAG,EAAE;AACT,EAAE"}
@@ -0,0 +1,21 @@
1
+ import { CounterTagValue } from './counter-tag';
2
+ import { NavigationItemData } from './navigation';
3
+ export declare const getNotificationCount: (value: CounterTagValue | undefined) => number;
4
+ /** A closed module summarizes its pages; an open module lets each page count itself. */
5
+ export declare function getModuleNotificationCount(item: NavigationItemData, submenuOpened: boolean): number;
6
+ export interface NotificationRowBounds {
7
+ top: number;
8
+ bottom: number;
9
+ count: number;
10
+ }
11
+ /** Only fully offscreen rows contribute; partially visible notifications are still discoverable. */
12
+ export declare function getOverflowNotifications(rows: NotificationRowBounds[], viewport: {
13
+ top: number;
14
+ bottom: number;
15
+ }): {
16
+ above: number;
17
+ below: number;
18
+ aboveIndex: number;
19
+ belowIndex: number;
20
+ };
21
+ //# sourceMappingURL=navigation-notifications.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"navigation-notifications.d.ts","sourceRoot":"","sources":["../../src/utils/navigation-notifications.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAElD,eAAO,MAAM,oBAAoB,GAAI,OAAO,eAAe,GAAG,SAAS,KAAG,MAK7D,CAAC;AAEd,wFAAwF;AACxF,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,kBAAkB,EAAE,aAAa,EAAE,OAAO,UAe1F;AAED,MAAM,WAAW,qBAAqB;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACjB;AAED,oGAAoG;AACpG,wBAAgB,wBAAwB,CACpC,IAAI,EAAE,qBAAqB,EAAE,EAC7B,QAAQ,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;;;;;EAqB5C"}