@brillout/docpress 0.9.8 → 0.10.1

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 (63) hide show
  1. package/Layout.tsx +102 -40
  2. package/{navigation → MenuModal}/Collapsible.css +7 -0
  3. package/MenuModal/NavigationWithColumnLayout.css +11 -0
  4. package/MenuModal/NavigationWithColumnLayout.tsx +253 -0
  5. package/MenuModal/toggleMenuModal.ts +132 -0
  6. package/MenuModal.tsx +68 -79
  7. package/{navigation/Navigation.css → NavItemComponent.css} +1 -23
  8. package/NavItemComponent.tsx +149 -0
  9. package/components/Note.css +0 -1
  10. package/config/resolveHeadingsData.ts +1 -1
  11. package/css/code/diff.css +10 -5
  12. package/css/code.css +1 -1
  13. package/css/colorize-on-hover.css +6 -7
  14. package/css/heading.css +9 -3
  15. package/css/index.css +1 -0
  16. package/dist/NavItemComponent.d.ts +39 -0
  17. package/dist/NavItemComponent.js +109 -0
  18. package/dist/config/resolveHeadingsData.d.ts +1 -1
  19. package/dist/config/resolvePageContext.d.ts +2 -2
  20. package/dist/renderer/determineNavItemsColumnLayout.d.ts +1 -1
  21. package/docsearch/SearchLink.tsx +7 -3
  22. package/icons/books.svg +46 -0
  23. package/icons/gear.svg +35 -0
  24. package/icons/index.ts +5 -0
  25. package/icons/magnifying-glass.svg +31 -0
  26. package/icons/seedling.svg +24 -0
  27. package/index.ts +2 -0
  28. package/initKeyBindings.ts +1 -1
  29. package/package.json +1 -1
  30. package/renderer/determineNavItemsColumnLayout.ts +1 -1
  31. package/renderer/initOnNavigation.ts +1 -1
  32. package/renderer/onRenderClient.tsx +1 -1
  33. package/utils/css.ts +0 -6
  34. package/dist/Layout.d.ts +0 -15
  35. package/dist/Layout.js +0 -321
  36. package/dist/MenuModal.d.ts +0 -13
  37. package/dist/MenuModal.js +0 -124
  38. package/dist/NavSecondaryContent.d.ts +0 -6
  39. package/dist/NavSecondaryContent.js +0 -57
  40. package/dist/autoScrollNav.d.ts +0 -3
  41. package/dist/autoScrollNav.js +0 -35
  42. package/dist/components/EditPageNote.d.ts +0 -7
  43. package/dist/components/EditPageNote.js +0 -11
  44. package/dist/docsearch/SearchLink.d.ts +0 -4
  45. package/dist/docsearch/SearchLink.js +0 -25
  46. package/dist/docsearch/toggleDocsearchModal.d.ts +0 -4
  47. package/dist/docsearch/toggleDocsearchModal.js +0 -26
  48. package/dist/navigation/Collapsible.d.ts +0 -10
  49. package/dist/navigation/Collapsible.js +0 -35
  50. package/dist/navigation/Navigation.d.ts +0 -21
  51. package/dist/navigation/Navigation.js +0 -255
  52. package/dist/utils/PassTrough.d.ts +0 -3
  53. package/dist/utils/PassTrough.js +0 -6
  54. package/dist/utils/Style.d.ts +0 -5
  55. package/dist/utils/Style.js +0 -6
  56. package/dist/utils/css.d.ts +0 -1
  57. package/dist/utils/css.js +0 -27
  58. package/dist/utils/getViewportWidth.d.ts +0 -1
  59. package/dist/utils/getViewportWidth.js +0 -4
  60. package/dist/utils/throttle.d.ts +0 -1
  61. package/dist/utils/throttle.js +0 -14
  62. package/navigation/Navigation.tsx +0 -382
  63. /package/{navigation → MenuModal}/Collapsible.tsx +0 -0
package/Layout.tsx CHANGED
@@ -1,17 +1,19 @@
1
1
  export { Layout }
2
+ export { MenuToggle }
2
3
  export { containerQueryMobileLayout }
3
4
  export { containerQueryMobileMenu }
4
5
  export { navLeftWidthMin }
5
6
  export { navLeftWidthMax }
6
7
  export { unexpandNav }
8
+ export { blockMargin }
7
9
 
8
10
  import React from 'react'
9
- import { NavigationContent } from './navigation/Navigation'
11
+ import { getNavItemsWithComputed, NavItem, NavItemComponent } from './NavItemComponent'
10
12
  import { EditPageNote } from './components/EditPageNote'
11
13
  import { parseTitle } from './parseTitle'
12
14
  import { usePageContext, usePageContext2 } from './renderer/usePageContext'
13
15
  import { NavSecondaryContent } from './NavSecondaryContent'
14
- import { closeMenuModalWithDelay, openMenuModal, toggleMenuModal } from './MenuModal'
16
+ import { closeMenuOnMouseLeave, openMenuModal, toggleMenuModal } from './MenuModal/toggleMenuModal'
15
17
  import { MenuModal } from './MenuModal'
16
18
  import { autoScrollNav_SSR } from './autoScrollNav'
17
19
  import { SearchLink } from './docsearch/SearchLink'
@@ -20,6 +22,7 @@ import { css } from './utils/css'
20
22
  import { PassThrough } from './utils/PassTrough'
21
23
  import { Style } from './utils/Style'
22
24
  import { cls } from './utils/cls'
25
+ import { iconBooks } from './icons'
23
26
 
24
27
  const blockMargin = 3
25
28
  const mainViewPadding = 20
@@ -242,6 +245,29 @@ function NavLeft() {
242
245
  </>
243
246
  )
244
247
  }
248
+ function NavigationContent(props: {
249
+ navItems: NavItem[]
250
+ showOnlyRelevant?: true
251
+ }) {
252
+ const pageContext = usePageContext()
253
+ const navItemsWithComputed = getNavItemsWithComputed(props.navItems, pageContext.urlPathname)
254
+
255
+ let navItemsRelevant = navItemsWithComputed
256
+ if (props.showOnlyRelevant) navItemsRelevant = navItemsRelevant.filter((navItemGroup) => navItemGroup.isRelevant)
257
+ const navContent = navItemsRelevant.map((navItem, i) => <NavItemComponent navItem={navItem} key={i} />)
258
+
259
+ return (
260
+ <div className="navigation-content" style={{ marginTop: 10 }}>
261
+ {navContent}
262
+ </div>
263
+ )
264
+ }
265
+
266
+ const menuLinkStyle: React.CSSProperties = {
267
+ height: '100%',
268
+ padding: '0 var(--padding-side)',
269
+ justifyContent: 'center',
270
+ }
245
271
 
246
272
  function NavHead({ isNavLeft }: { isNavLeft?: true }) {
247
273
  const pageContext = usePageContext()
@@ -250,12 +276,6 @@ function NavHead({ isNavLeft }: { isNavLeft?: true }) {
250
276
  const { isLandingPage } = pageContext
251
277
  const { navMaxWidth } = pageContext.config
252
278
 
253
- const linkStyle: React.CSSProperties = {
254
- height: '100%',
255
- padding: '0 var(--padding-side)',
256
- justifyContent: 'center',
257
- }
258
-
259
279
  const TopNavigation = pageContext2.config.TopNavigation || PassThrough
260
280
  const navSecondaryContent = (
261
281
  <div
@@ -270,7 +290,6 @@ function NavHead({ isNavLeft }: { isNavLeft?: true }) {
270
290
  position: 'absolute',
271
291
  left: '100%',
272
292
  top: 0,
273
- paddingLeft: 'var(--block-margin)',
274
293
  '--padding-side': '20px',
275
294
  width: mainViewMax, // guaranteed real estate
276
295
  }),
@@ -325,8 +344,8 @@ function NavHead({ isNavLeft }: { isNavLeft?: true }) {
325
344
  >
326
345
  <NavLogo className="mobile-grow-half" />
327
346
  {!isNavLeft && <div className="desktop-grow" />}
328
- <SearchLink className="mobile-grow-half" style={linkStyle} />
329
- <MenuLink className="mobile-grow-full" style={linkStyle} />
347
+ <SearchLink className="mobile-grow-half" style={menuLinkStyle} />
348
+ <MenuToggleMain className="mobile-grow-full" style={menuLinkStyle} />
330
349
  {navSecondaryContent}
331
350
  </div>
332
351
  </div>
@@ -353,12 +372,17 @@ function NavHead({ isNavLeft }: { isNavLeft?: true }) {
353
372
  }
354
373
  @container container-nav-head (min-width: 501px) {
355
374
  .nav-head-content {
356
- --padding-side: 35px;
375
+ --padding-side: 24px;
357
376
  }
358
377
  .nav-logo {
359
378
  padding: 0 var(--padding-side);
360
379
  }
361
380
  }
381
+ @container container-nav-head (min-width: ${containerQueryMobileMenu + 100}px) {
382
+ .nav-head-content {
383
+ --padding-side: 35px;
384
+ }
385
+ }
362
386
  @media(max-width: ${containerQueryMobileMenu}px) {
363
387
  .hide-on-shrink {
364
388
  display: none !important;
@@ -372,7 +396,7 @@ function NavHead({ isNavLeft }: { isNavLeft?: true }) {
372
396
  flex-grow: 1;
373
397
  }
374
398
  .desktop-fade {
375
- transition: opacity 0.3s ease-in-out !important;
399
+ transition: opacity 0.4s ease-in-out;
376
400
  }
377
401
  html:not(.menu-modal-show) .nav-head-top:not(:hover) .desktop-fade {
378
402
  transition: opacity 0.3s ease-in-out !important;
@@ -491,7 +515,31 @@ function isProjectNameShort(projectName: string) {
491
515
 
492
516
  let onMouseIgnore: ReturnType<typeof setTimeout> | undefined
493
517
  type PropsDiv = React.HTMLProps<HTMLDivElement>
494
- function MenuLink(props: PropsDiv) {
518
+ function MenuToggleMain(props: PropsDiv) {
519
+ return (
520
+ <MenuToggle menuId={0} {...props}>
521
+ <span className="text-docs" style={{ display: 'flex' }}>
522
+ <DocsIcon /> Docs
523
+ </span>
524
+ <span className="text-menu">
525
+ <MenuIcon /> Menu
526
+ </span>
527
+ <Style>{css`
528
+ @media(max-width: ${containerQueryMobileMenu}px) {
529
+ .text-docs {
530
+ display: none !important;
531
+ }
532
+ }
533
+ @media(min-width: ${containerQueryMobileMenu + 1}px) {
534
+ .text-menu {
535
+ display: none;
536
+ }
537
+ }
538
+ `}</Style>
539
+ </MenuToggle>
540
+ )
541
+ }
542
+ function MenuToggle({ menuId, ...props }: PropsDiv & { menuId: number }) {
495
543
  return (
496
544
  <div
497
545
  {...props}
@@ -499,22 +547,23 @@ function MenuLink(props: PropsDiv) {
499
547
  height: '100%',
500
548
  display: 'flex',
501
549
  alignItems: 'center',
502
- cursor: 'default',
550
+ cursor: 'pointer',
503
551
  userSelect: 'none',
552
+ ...menuLinkStyle,
504
553
  ...props.style,
505
554
  }}
506
- className={['colorize-on-hover menu-toggle', props.className].filter(Boolean).join(' ')}
555
+ className={[`colorize-on-hover menu-toggle menu-toggle-${menuId}`, props.className].filter(Boolean).join(' ')}
507
556
  onClick={(ev) => {
508
557
  ev.preventDefault()
509
- toggleMenuModal()
558
+ toggleMenuModal(menuId)
510
559
  }}
511
- onMouseOver={() => {
560
+ onMouseEnter={() => {
512
561
  if (onMouseIgnore) return
513
- openMenuModal()
562
+ openMenuModal(menuId)
514
563
  }}
515
564
  onMouseLeave={() => {
516
565
  if (onMouseIgnore) return
517
- closeMenuModalWithDelay(100)
566
+ closeMenuOnMouseLeave()
518
567
  }}
519
568
  onTouchStart={() => {
520
569
  onMouseIgnore = setTimeout(() => {
@@ -522,32 +571,45 @@ function MenuLink(props: PropsDiv) {
522
571
  }, 1000)
523
572
  }}
524
573
  >
525
- <span className="text-docs">
526
- <DocsIcon /> Docs
527
- </span>
528
- <span className="text-menu">
529
- <MenuIcon /> Menu
530
- </span>
531
- <Style>{css`
532
- @media(max-width: ${containerQueryMobileMenu}px) {
533
- .text-docs {
534
- display: none;
574
+ <Style>{getAnimation()}</Style>
575
+ {props.children}
576
+ </div>
577
+ )
578
+
579
+ function getAnimation() {
580
+ return css`
581
+ .menu-toggle {
582
+ position: relative;
583
+ overflow: hidden;
584
+ z-index: 0;
585
+ @media (hover: hover) and (pointer: fine) {
586
+ .link-hover-animation &:hover::before {
587
+ top: 0;
588
+ }
535
589
  }
536
- }
537
- @media(min-width: ${containerQueryMobileMenu + 1}px) {
538
- .text-menu {
539
- display: none;
590
+ &::before {
591
+ position: absolute;
592
+ content: '';
593
+ height: 100%;
594
+ width: 100%;
595
+ top: var(--nav-head-height);
596
+ background-color: var(--active-color);
597
+ transition-property: top !important;
598
+ transition: top 0.4s ease !important;
599
+ z-index: -1;
540
600
  }
541
601
  }
542
- `}</Style>
543
- </div>
544
- )
602
+ `
603
+ }
545
604
  }
546
605
  function DocsIcon() {
547
606
  return (
548
- <span style={{ marginRight: 'calc(var(--icon-text-padding) + 2px)' }} className="decolorize-6 desktop-fade">
549
- 📚
550
- </span>
607
+ <img
608
+ src={iconBooks}
609
+ width={21}
610
+ style={{ marginRight: 'calc(var(--icon-text-padding) + 2px)' }}
611
+ className="decolorize-5 desktop-fade"
612
+ />
551
613
  )
552
614
  }
553
615
  function MenuIcon() {
@@ -1,5 +1,12 @@
1
1
  .collapsible-icon {
2
2
  display: none;
3
+ position: absolute;
4
+ top: calc(100% / 2 - 6px);
5
+ margin-left: 9px;
6
+ vertical-align: middle;
7
+ }
8
+ :has(> .collapsible-icon) {
9
+ position: relative;
3
10
  }
4
11
  .collapsible-expanded .collapsible-icon,
5
12
  .collapsible-collapsed .collapsible-icon {
@@ -0,0 +1,11 @@
1
+ :has(> .category-border) {
2
+ position: relative;
3
+ }
4
+ .category-border {
5
+ position: absolute;
6
+ top: 0;
7
+ left: 0;
8
+ height: 100%;
9
+ width: 3px;
10
+ z-index: 99;
11
+ }
@@ -0,0 +1,253 @@
1
+ export { NavigationWithColumnLayout }
2
+
3
+ import React, { useEffect, useState } from 'react'
4
+ import { assert } from '../utils/server'
5
+ import { getViewportWidth } from '../utils/getViewportWidth'
6
+ import { containerQueryMobileMenu, navLeftWidthMax, navLeftWidthMin } from '../Layout'
7
+ import { throttle } from '../utils/throttle'
8
+ import { Collapsible } from './Collapsible'
9
+ import { ColumnMap, getNavItemsWithComputed, NavItem, NavItemComponent, NavItemComputed } from '../NavItemComponent'
10
+ import { usePageContext } from '../renderer/usePageContext'
11
+ import './NavigationWithColumnLayout.css'
12
+ import { Style } from '../utils/Style'
13
+ import { css } from '../utils/css'
14
+
15
+ const marginBottomOnExpand = 30
16
+ function NavigationWithColumnLayout(props: { navItems: NavItem[] }) {
17
+ const pageContext = usePageContext()
18
+ const navItemsWithComputed = getNavItemsWithComputed(props.navItems, pageContext.urlPathname)
19
+ let [viewportWidth, setViewportWidth] = useState<number | undefined>()
20
+ const updateviewportwidth = () => setViewportWidth(getViewportWidth())
21
+ useEffect(() => {
22
+ updateviewportwidth()
23
+ window.addEventListener('resize', throttle(updateviewportwidth, 300), { passive: true })
24
+ })
25
+ const navItemsByColumnLayouts = getNavItemsByColumnLayouts(navItemsWithComputed, viewportWidth)
26
+ return (
27
+ <>
28
+ <Style>{getStyle()}</Style>
29
+ <div className="navigation-content" style={{ paddingTop: 10 }}>
30
+ {navItemsByColumnLayouts.map((columnLayout, i) => (
31
+ <div id={`menu-navigation-${i}`} className="menu-navigation" key={i}>
32
+ {columnLayout.isFullWidthCategory ? (
33
+ <div style={{ marginTop: 0 }}>
34
+ <ColumnsWrapper numberOfColumns={columnLayout.columns.length}>
35
+ <Collapsible
36
+ head={(onClick) => <NavItemComponent navItem={columnLayout.navItemLevel1} onClick={onClick} />}
37
+ disabled={columnLayout.columns.length > 1}
38
+ collapsedInit={!columnLayout.navItemLevel1.isRelevant}
39
+ marginBottomOnExpand={marginBottomOnExpand}
40
+ >
41
+ <ColumnsLayout className="collapsible">
42
+ {columnLayout.columns.map((column, j) => (
43
+ <Column key={j}>
44
+ {column.navItems.map((navItem, k) => (
45
+ <NavItemComponent key={k} navItem={navItem} />
46
+ ))}
47
+ </Column>
48
+ ))}
49
+ <CategoryBorder navItemLevel1={columnLayout.navItemLevel1} />
50
+ </ColumnsLayout>
51
+ </Collapsible>
52
+ </ColumnsWrapper>
53
+ </div>
54
+ ) : (
55
+ <ColumnsWrapper numberOfColumns={columnLayout.columns.length}>
56
+ <ColumnsLayout>
57
+ {columnLayout.columns.map((column, j) => (
58
+ <Column key={j}>
59
+ {column.categories.map((category, k) => (
60
+ <div key={k} style={{ marginBottom: 0 }}>
61
+ <Collapsible
62
+ head={(onClick) => <NavItemComponent navItem={category.navItemLevel1} onClick={onClick} />}
63
+ disabled={columnLayout.columns.length > 1}
64
+ collapsedInit={!category.navItemLevel1.isRelevant}
65
+ marginBottomOnExpand={marginBottomOnExpand}
66
+ >
67
+ {category.navItems.map((navItem, l) => (
68
+ <NavItemComponent key={l} navItem={navItem} />
69
+ ))}
70
+ <CategoryBorder navItemLevel1={category.navItemLevel1} />
71
+ </Collapsible>
72
+ </div>
73
+ ))}
74
+ </Column>
75
+ ))}
76
+ </ColumnsLayout>
77
+ </ColumnsWrapper>
78
+ )}
79
+ </div>
80
+ ))}
81
+ </div>
82
+ </>
83
+ )
84
+
85
+ function getStyle() {
86
+ const style = css`
87
+ @media(min-width: ${containerQueryMobileMenu + 1}px) {
88
+ ${navItemsByColumnLayouts
89
+ .map(
90
+ (_, i) =>
91
+ css`
92
+ html:not(.menu-modal-show-${i}) #menu-navigation-${i} {
93
+ display: none;
94
+ }
95
+ html.menu-modal-show.menu-modal-show-${i} {
96
+ .menu-toggle-${i} {
97
+ color: black !important;
98
+ cursor: default !important;
99
+ [class^='decolorize-'],
100
+ [class*=' decolorize-'] {
101
+ filter: grayscale(0) opacity(1) !important;
102
+ }
103
+ &::before {
104
+ top: 0;
105
+ }
106
+ }
107
+ }
108
+ `,
109
+ )
110
+ .join('')}
111
+ }
112
+ `
113
+ return style
114
+ }
115
+ }
116
+ function Column({ children }: { children: React.ReactNode }) {
117
+ return (
118
+ <div
119
+ style={{
120
+ flexGrow: 1,
121
+ maxWidth: navLeftWidthMax,
122
+ display: 'flex',
123
+ flexDirection: 'column',
124
+ }}
125
+ >
126
+ {children}
127
+ </div>
128
+ )
129
+ }
130
+ function ColumnsWrapper({ children, numberOfColumns }: { children: React.ReactNode; numberOfColumns: number }) {
131
+ return (
132
+ <div
133
+ style={{
134
+ width: numberOfColumns * (navLeftWidthMax + 20),
135
+ maxWidth: '100%',
136
+ margin: 'auto',
137
+ }}
138
+ >
139
+ {children}
140
+ </div>
141
+ )
142
+ }
143
+ function ColumnsLayout({ children, className }: { children: React.ReactNode; className?: string }) {
144
+ return (
145
+ <div
146
+ className={className}
147
+ style={{
148
+ display: 'flex',
149
+ justifyContent: 'space-between',
150
+ }}
151
+ >
152
+ {children}
153
+ </div>
154
+ )
155
+ }
156
+ function CategoryBorder({ navItemLevel1 }: { navItemLevel1: NavItemComputed }) {
157
+ assert(navItemLevel1.level === 1)
158
+ return <div className="category-border" style={{ background: navItemLevel1.color! }} />
159
+ }
160
+
161
+ type NavItemsByColumnLayout =
162
+ | {
163
+ columns: {
164
+ categories: {
165
+ navItemLevel1: NavItemComputed
166
+ navItems: NavItemComputed[]
167
+ }[]
168
+ }[]
169
+ isFullWidthCategory: false
170
+ }
171
+ | {
172
+ navItemLevel1: NavItemComputed
173
+ columns: { navItems: NavItemComputed[] }[]
174
+ isFullWidthCategory: true
175
+ }
176
+ function getNavItemsByColumnLayouts(navItems: NavItemComputed[], viewportWidth: number = 0): NavItemsByColumnLayout[] {
177
+ const navItemsByColumnEntries = getNavItemsByColumnEntries(navItems)
178
+ const numberOfColumnsMax = Math.floor(viewportWidth / navLeftWidthMin) || 1
179
+ const navItemsByColumnLayouts: NavItemsByColumnLayout[] = navItemsByColumnEntries.map(
180
+ ({ columnEntries, isFullWidthCategory }) => {
181
+ const numberOfColumns = Math.min(numberOfColumnsMax, columnEntries.length)
182
+ if (!isFullWidthCategory) {
183
+ const columns: {
184
+ categories: {
185
+ navItemLevel1: NavItemComputed
186
+ navItems: NavItemComputed[]
187
+ }[]
188
+ }[] = []
189
+ columnEntries.forEach((columnEntry) => {
190
+ const idx = numberOfColumns === 1 ? 0 : columnEntry.columnMap[numberOfColumns]!
191
+ assert(idx >= 0)
192
+ columns[idx] ??= { categories: [] }
193
+ const navItemLevel1 = columnEntry.navItems[0]
194
+ const navItems = columnEntry.navItems.slice(1)
195
+ columns[idx].categories.push({ navItemLevel1, navItems })
196
+ })
197
+ const navItemsByColumnLayout: NavItemsByColumnLayout = { columns, isFullWidthCategory }
198
+ return navItemsByColumnLayout
199
+ } else {
200
+ let navItemLevel1: NavItemComputed
201
+ const columns: { navItems: NavItemComputed[] }[] = []
202
+ columnEntries.forEach((columnEntry, i) => {
203
+ const idx = numberOfColumns === 1 ? 0 : columnEntry.columnMap[numberOfColumns]!
204
+ assert(idx >= 0)
205
+ columns[idx] ??= { navItems: [] }
206
+ let { navItems } = columnEntry
207
+ if (i === 0) {
208
+ navItemLevel1 = navItems[0]
209
+ navItems = navItems.slice(1)
210
+ }
211
+ columns[idx].navItems.push(...navItems)
212
+ })
213
+ const navItemsByColumnLayout: NavItemsByColumnLayout = {
214
+ columns,
215
+ navItemLevel1: navItemLevel1!,
216
+ isFullWidthCategory,
217
+ }
218
+ return navItemsByColumnLayout
219
+ }
220
+ },
221
+ )
222
+ return navItemsByColumnLayouts
223
+ }
224
+ type NavItemsByColumnEntries = { columnEntries: ColumnEntry[]; isFullWidthCategory: boolean }[]
225
+ type ColumnEntry = { navItems: NavItemComputed[]; columnMap: ColumnMap }
226
+ function getNavItemsByColumnEntries(navItems: NavItemComputed[]): NavItemsByColumnEntries {
227
+ const navItemsByColumnEntries: NavItemsByColumnEntries = []
228
+ let columnEntries: ColumnEntry[] = []
229
+ let columnEntry: ColumnEntry
230
+ let isFullWidthCategory: boolean | undefined
231
+ navItems.forEach((navItem) => {
232
+ if (navItem.level === 1) {
233
+ const isFullWidthCategoryPrevious = isFullWidthCategory
234
+ isFullWidthCategory = !!navItem.menuModalFullWidth
235
+ if (isFullWidthCategoryPrevious !== undefined && isFullWidthCategoryPrevious !== isFullWidthCategory) {
236
+ navItemsByColumnEntries.push({ columnEntries, isFullWidthCategory: isFullWidthCategoryPrevious })
237
+ columnEntries = []
238
+ }
239
+ }
240
+ assert(isFullWidthCategory !== undefined)
241
+ if (navItem.isColumnEntry) {
242
+ assert(navItem.level === 1 || navItem.level === 4)
243
+ columnEntry = { navItems: [navItem], columnMap: navItem.isColumnEntry }
244
+ columnEntries.push(columnEntry)
245
+ } else {
246
+ assert(navItem.level !== 1)
247
+ columnEntry.navItems.push(navItem)
248
+ }
249
+ })
250
+ assert(isFullWidthCategory !== undefined)
251
+ navItemsByColumnEntries.push({ columnEntries, isFullWidthCategory })
252
+ return navItemsByColumnEntries
253
+ }
@@ -0,0 +1,132 @@
1
+ export { toggleMenuModal }
2
+ export { openMenuModal }
3
+ export { keepMenuModalOpen }
4
+ export { closeMenuModal }
5
+ export { closeMenuOnMouseLeave }
6
+ export { addListenerOpenMenuModal }
7
+
8
+ import { containerQueryMobileLayout } from '../Layout'
9
+ import { getViewportWidth } from '../utils/getViewportWidth'
10
+ import { isBrowser } from '../utils/isBrowser'
11
+
12
+ initScrollListener()
13
+
14
+ function keepMenuModalOpen() {
15
+ if (keepOpenIsDisabled) return
16
+ open()
17
+ }
18
+ function openMenuModal(menuNavigationId: number) {
19
+ open(menuNavigationId)
20
+ }
21
+ function open(menuNavigationId?: number) {
22
+ if (menuModalLock) {
23
+ if (menuNavigationId === undefined) {
24
+ clearTimeout(menuModalLock?.timeout)
25
+ menuModalLock = undefined
26
+ return
27
+ }
28
+ menuModalLock.idNext = menuNavigationId
29
+ return
30
+ }
31
+ const { classList } = document.documentElement
32
+ classList.add('menu-modal-show')
33
+ if (menuNavigationId !== undefined) {
34
+ const currentModalId = getCurrentMenuId()
35
+ if (currentModalId === menuNavigationId) return
36
+ if (currentModalId !== null) {
37
+ classList.remove(`menu-modal-show-${currentModalId}`)
38
+ }
39
+ classList.add(`menu-modal-show-${menuNavigationId}`)
40
+ }
41
+ listener?.()
42
+ }
43
+ let listener: () => void | undefined
44
+ function addListenerOpenMenuModal(cb: () => void) {
45
+ listener = cb
46
+ }
47
+ function closeMenuModal() {
48
+ document.documentElement.classList.remove('menu-modal-show')
49
+ }
50
+ let keepOpenIsDisabled: true | undefined
51
+ function closeAndForbidKeepOpen() {
52
+ if (!document.documentElement.classList.contains('menu-modal-show')) return
53
+ keepOpenIsDisabled = true
54
+ closeMenuModal()
55
+ setTimeout(() => {
56
+ keepOpenIsDisabled = undefined
57
+ }, 500)
58
+ }
59
+
60
+ let menuModalLock:
61
+ | {
62
+ idCurrent: number
63
+ idNext: number | undefined
64
+ timeout: NodeJS.Timeout
65
+ }
66
+ | undefined
67
+ function closeMenuOnMouseLeave() {
68
+ const currentModalId = getCurrentMenuId()
69
+ if (currentModalId === null) return
70
+ const timeout = setTimeout(() => {
71
+ const { idCurrent, idNext } = menuModalLock!
72
+ menuModalLock = undefined
73
+ if (idNext === idCurrent) return
74
+ if (idNext === undefined) {
75
+ closeAndForbidKeepOpen()
76
+ } else {
77
+ openMenuModal(idNext)
78
+ }
79
+ }, 100)
80
+ clearTimeout(menuModalLock?.timeout)
81
+ menuModalLock = {
82
+ idCurrent: currentModalId,
83
+ idNext: undefined,
84
+ timeout,
85
+ }
86
+ }
87
+ function getCurrentMenuId(): null | number {
88
+ const { classList } = document.documentElement
89
+ const prefix = 'menu-modal-show-'
90
+ const cls = Array.from(classList).find((cls) => cls.startsWith(prefix))
91
+ if (!cls) return null
92
+ return parseInt(cls.slice(prefix.length), 10)
93
+ }
94
+
95
+ function initScrollListener() {
96
+ if (!isBrowser()) return
97
+ window.addEventListener('scroll', closeAndForbidKeepOpen, { passive: true })
98
+ }
99
+
100
+ function toggleMenuModal(menuId: number) {
101
+ keepOpenIsDisabled = undefined
102
+ const { classList } = document.documentElement
103
+ if (classList.contains('menu-modal-show') && classList.contains(`menu-modal-show-${menuId}`)) {
104
+ closeMenuModal()
105
+ } else {
106
+ openMenuModal(menuId)
107
+ if (getViewportWidth() < containerQueryMobileLayout) autoScroll()
108
+ }
109
+ }
110
+
111
+ function autoScroll() {
112
+ const nav = document.querySelector('#menu-modal .navigation-content')!
113
+ const href = window.location.pathname
114
+ const navLinks = Array.from(nav.querySelectorAll(`a[href="${href}"]`))
115
+ const navLink = navLinks[0] as HTMLElement | undefined
116
+ if (!navLink) return
117
+ // None of the following seemes to be working: https://stackoverflow.com/questions/19669786/check-if-element-is-visible-in-dom
118
+ if (findCollapsibleEl(navLink)!.classList.contains('collapsible-collapsed')) return
119
+ navLink.scrollIntoView({
120
+ behavior: 'instant',
121
+ block: 'center',
122
+ inline: 'start',
123
+ })
124
+ }
125
+ function findCollapsibleEl(navLink: HTMLElement | undefined) {
126
+ let parentEl: HTMLElement | null | undefined = navLink
127
+ while (parentEl) {
128
+ if (parentEl.classList.contains('collapsible')) return parentEl
129
+ parentEl = parentEl.parentElement
130
+ }
131
+ return null
132
+ }