@brillout/docpress 0.7.8 → 0.7.9-commit-ea6d0e2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Layout.tsx CHANGED
@@ -42,7 +42,7 @@ function Layout({ children }: { children: React.ReactNode }) {
42
42
  </a>
43
43
  )}
44
44
  <div style={{ display: 'flex', alignItems: 'center' }}>
45
- <TopNavigationLink id="doclink" aria-label={hotkeyLabel} data-balloon-pos="left">
45
+ <TopNavigationLink id="doclink" aria-label={hotkeyLabel} data-balloon-pos="left" data-balloon-blunt>
46
46
  Documentation
47
47
  </TopNavigationLink>
48
48
  {topNavigationList.map(({ title, url }) => (
package/autoScrollNav.ts CHANGED
@@ -1,37 +1,37 @@
1
1
  export { autoScrollNav }
2
+ export const autoScrollNav_SSR = `autoScrollNav();${autoScrollNav.toString()}`
2
3
 
3
- import { assert } from './utils/client'
4
+ // - We cannot use TypeScript syntax because of autoScrollNav_SSR
5
+ // - We have to save & restore `dodocument.documentElement.scrollTop` because scrollIntoView() scrolls the main view. (I don't know why).
6
+ // - Failed alternatives:
7
+ // - scrollIntoViewIfNeeded() (https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoViewIfNeeded) would work (it doesn't scroll the main view) but Firefox doesn't support it.
8
+ // - Doesn't work: the scrolling is off by hundreds of px (I guess because this function runs too early while the page is still rendering).
9
+ // ```js
10
+ // const navigationContainerEl = document.getElementById("navigation-container")
11
+ // const offset = navLink.offsetTop - (window.innerHeight / 2)
12
+ // navigationContainerEl.scrollTop = offset
13
+ // ```
14
+ // - Doesn't work: scrollIntoView() still scrolls the main view
15
+ // ```js
16
+ // const overflowOriginal = document.documentElement.style.overflow
17
+ // document.documentElement.style.overflow = 'hidden'
18
+ // // ...
19
+ // document.documentElement.style.overflow = overflowOriginal
20
+ // ```
4
21
 
5
22
  function autoScrollNav() {
6
23
  const navigationEl = document.getElementById('navigation-content-main')
7
- assert(navigationEl)
24
+ if (!navigationEl) return
8
25
  const href = window.location.pathname
9
- const navLinks: HTMLElement[] = Array.from(navigationEl.querySelectorAll(`a[href="${href}"]`))
10
- assert(navLinks.length <= 1, { navLinks, href })
26
+ const navLinks = Array.from(navigationEl.querySelectorAll(`a[href="${href}"]`))
11
27
  const navLink = navLinks[0]
12
28
  if (!navLink) return
13
29
 
14
- /* Doesn't work: the scrolling is off by hundreds of px (I guess because this function runs too early while the page is still rendering)
15
- const navigationContainerEl = document.getElementById("navigation-container")
16
- const offset = navLink.offsetTop - (window.innerHeight / 2)
17
- navigationContainerEl.scrollTop = offset
18
- */
19
-
20
- /* Doesn't work: scrollIntoView() still scrolls the main view
21
- const overflowOriginal = document.documentElement.style.overflow
22
- document.documentElement.style.overflow = 'hidden'
23
- ...
24
- document.documentElement.style.overflow = overflowOriginal
25
- */
26
-
27
30
  const scrollTopOriginal = document.documentElement.scrollTop
28
31
  navLink.scrollIntoView({
29
- // @ts-ignore https://github.com/microsoft/TypeScript/issues/46654
30
32
  behavior: 'instant',
31
33
  block: 'center',
32
34
  inline: 'start',
33
35
  })
34
- // Avoid scrollIntoView() from scrolling the main view.
35
- // - Alternatively scrollIntoViewIfNeeded() (https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoViewIfNeeded) would work (it doesn't scroll the main view) but Firefox doesn't support it.
36
36
  document.documentElement.scrollTop = scrollTopOriginal
37
37
  }
@@ -0,0 +1,3 @@
1
+ export { autoScrollNav };
2
+ export declare const autoScrollNav_SSR: string;
3
+ declare function autoScrollNav(): void;
@@ -0,0 +1,36 @@
1
+ export { autoScrollNav };
2
+ export var autoScrollNav_SSR = "autoScrollNav();".concat(autoScrollNav.toString());
3
+ // - We cannot use TypeScript syntax because of autoScrollNav_SSR
4
+ // - We have to save & restore `dodocument.documentElement.scrollTop` because scrollIntoView() scrolls the main view. (I don't know why).
5
+ // - Failed alternatives:
6
+ // - scrollIntoViewIfNeeded() (https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoViewIfNeeded) would work (it doesn't scroll the main view) but Firefox doesn't support it.
7
+ // - Doesn't work: the scrolling is off by hundreds of px (I guess because this function runs too early while the page is still rendering).
8
+ // ```js
9
+ // const navigationContainerEl = document.getElementById("navigation-container")
10
+ // const offset = navLink.offsetTop - (window.innerHeight / 2)
11
+ // navigationContainerEl.scrollTop = offset
12
+ // ```
13
+ // - Doesn't work: scrollIntoView() still scrolls the main view
14
+ // ```js
15
+ // const overflowOriginal = document.documentElement.style.overflow
16
+ // document.documentElement.style.overflow = 'hidden'
17
+ // // ...
18
+ // document.documentElement.style.overflow = overflowOriginal
19
+ // ```
20
+ function autoScrollNav() {
21
+ var navigationEl = document.getElementById('navigation-content-main');
22
+ if (!navigationEl)
23
+ return;
24
+ var href = window.location.pathname;
25
+ var navLinks = Array.from(navigationEl.querySelectorAll("a[href=\"".concat(href, "\"]")));
26
+ var navLink = navLinks[0];
27
+ if (!navLink)
28
+ return;
29
+ var scrollTopOriginal = document.documentElement.scrollTop;
30
+ navLink.scrollIntoView({
31
+ behavior: 'instant',
32
+ block: 'center',
33
+ inline: 'start',
34
+ });
35
+ document.documentElement.scrollTop = scrollTopOriginal;
36
+ }
@@ -2,6 +2,8 @@ export { Navigation };
2
2
  export { NavigationMask };
3
3
  export type { NavigationData };
4
4
  export type { NavItem };
5
+ export { groupByLevelMin };
6
+ export type { NavItemGrouped };
5
7
  import React from 'react';
6
8
  import './Navigation.css';
7
9
  type NavigationData = Parameters<typeof Navigation>[0];
@@ -18,3 +20,7 @@ type NavItem = {
18
20
  title: string;
19
21
  titleInNav: string;
20
22
  };
23
+ type NavItemGrouped = ReturnType<typeof groupByLevelMin>[number];
24
+ declare function groupByLevelMin<T extends NavItem>(navItems: T[]): (T & {
25
+ navItemChilds: T[];
26
+ })[];
@@ -11,12 +11,15 @@ var __assign = (this && this.__assign) || function () {
11
11
  };
12
12
  export { Navigation };
13
13
  export { NavigationMask };
14
+ // TODO/refactor: do this only on the server side?
15
+ export { groupByLevelMin };
14
16
  import React from 'react';
15
17
  import { NavigationHeader } from './NavigationHeader';
16
18
  import { assert, Emoji, assertWarning, jsxToTextContent } from '../utils/server';
17
19
  import './Navigation.css';
18
20
  import { NavigationFullscreenClose } from './navigation-fullscreen/NavigationFullscreenButton';
19
21
  import { parseTitle } from '../parseTitle';
22
+ import { autoScrollNav_SSR } from '../autoScrollNav';
20
23
  function Navigation(_a) {
21
24
  var navItems = _a.navItems, navItemsAll = _a.navItemsAll, currentUrl = _a.currentUrl, isDetachedPage = _a.isDetachedPage;
22
25
  return (React.createElement(React.Fragment, null,
@@ -27,7 +30,8 @@ function Navigation(_a) {
27
30
  navItems.length > 1 && (React.createElement(NavigationContent, { id: "navigation-content-detached", navItems: navItems, currentUrl: currentUrl })),
28
31
  React.createElement(DetachedPageNote, null))),
29
32
  React.createElement(NavigationContent, { id: "navigation-content-main", navItems: navItemsAll, currentUrl: currentUrl }),
30
- React.createElement(NavigationFullscreenClose, null)))));
33
+ React.createElement(NavigationFullscreenClose, null))),
34
+ React.createElement("script", { dangerouslySetInnerHTML: { __html: autoScrollNav_SSR } })));
31
35
  }
32
36
  function NavigationMask() {
33
37
  return React.createElement("div", { id: "mobile-navigation-mask" });
@@ -35,10 +39,9 @@ function NavigationMask() {
35
39
  function NavigationContent(props) {
36
40
  var navItemsWithComputed = addComputedProps(props.navItems, props.currentUrl);
37
41
  var navItemsGrouped = groupByLevelMin(navItemsWithComputed);
38
- return (React.createElement("div", { id: props.id, className: "navigation-content" },
39
- React.createElement("div", { className: "nav-column", style: { position: 'relative' } }, navItemsGrouped.map(function (navItemGroup, i) { return (React.createElement("div", { className: "nav-items-group", key: i },
40
- React.createElement(NavItemComponent, { navItem: navItemGroup }),
41
- navItemGroup.navItemChilds.map(function (navItem, j) { return (React.createElement(NavItemComponent, { navItem: navItem, key: j })); }))); }))));
42
+ return (React.createElement("div", { id: props.id, className: "navigation-content" }, navItemsGrouped.map(function (navItemGroup, i) { return (React.createElement("div", { className: "nav-items-group", key: i },
43
+ React.createElement(NavItemComponent, { navItem: navItemGroup }),
44
+ navItemGroup.navItemChilds.map(function (navItem, j) { return (React.createElement(NavItemComponent, { navItem: navItem, key: j })); }))); })));
42
45
  }
43
46
  function NavItemComponent(_a) {
44
47
  var _b;
@@ -15,9 +15,9 @@ function NavigationFullscreenButton() {
15
15
  overflow: 'hidden',
16
16
  } },
17
17
  React.createElement("div", null)),
18
- React.createElement("div", { style: { position: 'fixed', height: '100vh', width: 20 }, "aria-label": hotkeyLabel, "data-balloon-pos": "right" }))));
18
+ React.createElement("div", { style: { position: 'fixed', height: '100vh', width: 20 }, "aria-label": hotkeyLabel, "data-balloon-pos": "right", "data-balloon-blunt": true }))));
19
19
  }
20
20
  function NavigationFullscreenClose() {
21
- return (React.createElement("a", { id: "navigation-fullscreen-close", style: { position: 'absolute', top: 11, right: 11, zIndex: 10 }, "aria-label": hotkeyLabel, "data-balloon-pos": "left" },
21
+ return (React.createElement("a", { id: "navigation-fullscreen-close", style: { position: 'absolute', top: 11, right: 11, zIndex: 10 }, "aria-label": hotkeyLabel, "data-balloon-pos": "left", "data-balloon-blunt": true },
22
22
  React.createElement("img", { src: closeIcon, height: 50, width: 50, style: { display: 'block' } })));
23
23
  }
@@ -22,9 +22,6 @@
22
22
  padding-right: 4px;
23
23
  text-decoration: none;
24
24
  }
25
- .nav-column:first-of-type > .nav-items-group:first-of-type > .nav-item-level-1:first-of-type {
26
- margin-top: 20px;
27
- }
28
25
  .nav-item-level-1 {
29
26
  margin-top: 30px;
30
27
  font-size: 15.4px;
@@ -114,14 +114,18 @@ html.navigation-fullscreen .navigation-content {
114
114
  display: flex;
115
115
  margin: auto; /* A `max-width` is set by src/navigation/navigation-fullscreen/initNavigationFullscreen.ts */
116
116
  }
117
- html.navigation-fullscreen .navigation-content > .nav-column {
118
- flex-grow: 1;
119
- max-width: 350px;
120
- }
121
- html.navigation-fullscreen .nav-column > .nav-items-group:first-child > .nav-item-level-1:first-child {
122
- margin-top: 0px;
123
- }
124
117
  html.navigation-fullscreen {
125
118
  /* disable scroll of main view */
126
119
  overflow: hidden !important;
127
120
  }
121
+ html.navigation-fullscreen #navigation-body {
122
+ display: flex;
123
+ }
124
+ html.navigation-fullscreen #navigation-content-main {
125
+ flex-grow: 1;
126
+ display: grid;
127
+ margin-top: -25px;
128
+ }
129
+ html.navigation-fullscreen .nav-items-group {
130
+ padding: 0 10px;
131
+ }
@@ -3,12 +3,17 @@ export { NavigationMask }
3
3
  export type { NavigationData }
4
4
  export type { NavItem }
5
5
 
6
+ // TODO/refactor: do this only on the server side?
7
+ export { groupByLevelMin }
8
+ export type { NavItemGrouped }
9
+
6
10
  import React from 'react'
7
11
  import { NavigationHeader } from './NavigationHeader'
8
12
  import { assert, Emoji, assertWarning, jsxToTextContent } from '../utils/server'
9
13
  import './Navigation.css'
10
14
  import { NavigationFullscreenClose } from './navigation-fullscreen/NavigationFullscreenButton'
11
15
  import { parseTitle } from '../parseTitle'
16
+ import { autoScrollNav_SSR } from '../autoScrollNav'
12
17
 
13
18
  type NavigationData = Parameters<typeof Navigation>[0]
14
19
 
@@ -40,6 +45,8 @@ function Navigation({
40
45
  <NavigationFullscreenClose />
41
46
  </div>
42
47
  </div>
48
+ {/* Early scrolling, to avoid flashing */}
49
+ <script dangerouslySetInnerHTML={{ __html: autoScrollNav_SSR }}></script>
43
50
  </>
44
51
  )
45
52
  }
@@ -72,16 +79,14 @@ function NavigationContent(props: {
72
79
 
73
80
  return (
74
81
  <div id={props.id} className="navigation-content">
75
- <div className="nav-column" style={{ position: 'relative' }}>
76
- {navItemsGrouped.map((navItemGroup, i) => (
77
- <div className="nav-items-group" key={i}>
78
- <NavItemComponent navItem={navItemGroup} />
79
- {navItemGroup.navItemChilds.map((navItem, j) => (
80
- <NavItemComponent navItem={navItem} key={j} />
81
- ))}
82
- </div>
83
- ))}
84
- </div>
82
+ {navItemsGrouped.map((navItemGroup, i) => (
83
+ <div className="nav-items-group" key={i}>
84
+ <NavItemComponent navItem={navItemGroup} />
85
+ {navItemGroup.navItemChilds.map((navItem, j) => (
86
+ <NavItemComponent navItem={navItem} key={j} />
87
+ ))}
88
+ </div>
89
+ ))}
85
90
  </div>
86
91
  )
87
92
  }
@@ -130,8 +135,9 @@ function NavItemComponent({
130
135
  )
131
136
  }
132
137
 
133
- function groupByLevelMin(navItems: NavItemComputed[]) {
134
- const navItemsGrouped: (NavItemComputed & { navItemChilds: NavItemComputed[] })[] = []
138
+ type NavItemGrouped = ReturnType<typeof groupByLevelMin>[number]
139
+ function groupByLevelMin<T extends NavItem>(navItems: T[]) {
140
+ const navItemsGrouped: (T & { navItemChilds: T[] })[] = []
135
141
  const levelMin: number = Math.min(...navItems.map((h) => h.level))
136
142
  navItems.forEach((navItem) => {
137
143
  if (navItem.level === levelMin) {
@@ -14,6 +14,6 @@ function navigationHeaderRightClickInterceptor() {
14
14
  const navHeaderImg = document.querySelector('#navigation-header-logo img') as HTMLElement
15
15
  navHeaderImg.oncontextmenu = (ev) => {
16
16
  ev.preventDefault()
17
- navigate('/press')
17
+ navigate('/press#logo')
18
18
  }
19
19
  }
@@ -25,6 +25,7 @@ function NavigationFullscreenButton() {
25
25
  style={{ position: 'fixed', height: '100vh', width: 20 }}
26
26
  aria-label={hotkeyLabel}
27
27
  data-balloon-pos="right"
28
+ data-balloon-blunt
28
29
  ></div>
29
30
  </a>
30
31
  </>
@@ -38,6 +39,7 @@ function NavigationFullscreenClose() {
38
39
  style={{ position: 'absolute', top: 11, right: 11, zIndex: 10 }}
39
40
  aria-label={hotkeyLabel}
40
41
  data-balloon-pos="left"
42
+ data-balloon-blunt
41
43
  >
42
44
  <img src={closeIcon} height={50} width={50} style={{ display: 'block' }} />
43
45
  </a>
@@ -7,7 +7,7 @@ import { assert } from '../../utils/client'
7
7
  let scrollPositionBeforeToggle: number
8
8
 
9
9
  function initNavigationFullscreenOnce() {
10
- scrollPositionBeforeToggle = 0
10
+ scrollPositionBeforeToggle = 0 // Initial scroll of fullscreen navigation is 0
11
11
  initKeyBindings()
12
12
  }
13
13
  function initKeyBindings() {
@@ -20,13 +20,11 @@ function initKeyBindings() {
20
20
  },
21
21
  false,
22
22
  )
23
- window.addEventListener('resize', updateColumnWidth, { passive: true })
24
23
  initTopNavigation()
25
24
  }
26
25
  function initNavigationFullscreen() {
27
26
  document.getElementById('navigation-fullscreen-button')!.onclick = toggleNavExpend
28
27
  document.getElementById('navigation-fullscreen-close')!.onclick = toggleNavExpend
29
- updateColumnWidth()
30
28
  }
31
29
 
32
30
  function toggleNavExpend() {
@@ -42,91 +40,6 @@ function hideNavigationFullScreen() {
42
40
  toggleNavExpend()
43
41
  }
44
42
 
45
- function updateColumnWidth() {
46
- const navMinWidth = 299
47
- const navH1Groups = Array.from(document.querySelectorAll('#navigation-content-main .nav-items-group'))
48
- const numberOfColumnsMax = navH1Groups.length
49
-
50
- const widthAvailable = getViewportWidth()
51
- const numberOfColumns = Math.max(1, Math.min(numberOfColumnsMax, Math.floor(widthAvailable / navMinWidth)))
52
-
53
- let columns = navH1Groups.map((navH1Group) => {
54
- const column = [
55
- {
56
- element: navH1Group,
57
- elementHeight: navH1Group.children.length,
58
- },
59
- ]
60
- return column
61
- })
62
-
63
- mergeColumns(columns, numberOfColumns)
64
-
65
- const navContent = document.getElementById('navigation-content-main')!
66
-
67
- Array.from(navContent.children).forEach((child) => {
68
- assert(child.className === 'nav-column')
69
- })
70
- navContent.innerHTML = ''
71
-
72
- columns.forEach((column) => {
73
- const columnEl = document.createElement('div')
74
- columnEl.className = 'nav-column'
75
- column.forEach(({ element }) => {
76
- columnEl.appendChild(element)
77
- })
78
- navContent.appendChild(columnEl)
79
- })
80
-
81
- const navItemMaxWidth = 350
82
- navContent.style.maxWidth = `${numberOfColumns * navItemMaxWidth}px`
83
- }
84
- function getViewportWidth(): number {
85
- // `window.innerWidth` inlcudes scrollbar width: https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes
86
- return document.documentElement.clientWidth
87
- }
88
-
89
- function mergeColumns<T>(columns: { element: T; elementHeight: number }[][], maxNumberOfColumns: number) {
90
- assert(columns.length > 0)
91
- assert(maxNumberOfColumns > 0)
92
- if (columns.length <= maxNumberOfColumns) {
93
- return columns
94
- }
95
-
96
- let mergeCandidate = {
97
- i: -1,
98
- mergeHeight: Infinity,
99
- }
100
- for (let i = 0; i <= columns.length - 2; i++) {
101
- const column1 = columns[i + 0]
102
- const column2 = columns[i + 1]
103
- const column1Height = sum(column1.map((c) => c.elementHeight))
104
- const column2Height = sum(column2.map((c) => c.elementHeight))
105
- const mergeHeight = column1Height + column2Height
106
- if (mergeCandidate.mergeHeight > mergeHeight) {
107
- mergeCandidate = {
108
- i,
109
- mergeHeight,
110
- }
111
- }
112
- }
113
-
114
- {
115
- const { i } = mergeCandidate
116
- assert(-1 < i && i < columns.length - 1, { i, columnsLength: columns.length, maxNumberOfColumns })
117
- columns[i] = [...columns[i], ...columns[i + 1]]
118
- columns.splice(i + 1, 1)
119
- }
120
-
121
- mergeColumns(columns, maxNumberOfColumns)
122
- }
123
-
124
- function sum(arr: number[]): number {
125
- let total = 0
126
- arr.forEach((n) => (total += n))
127
- return total
128
- }
129
-
130
43
  function initTopNavigation() {
131
44
  document.addEventListener('click', (ev) => {
132
45
  const linkTag = findLinkTag(ev.target as HTMLElement)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brillout/docpress",
3
- "version": "0.7.8",
3
+ "version": "0.7.9-commit-ea6d0e2",
4
4
  "type": "module",
5
5
  "dependencies": {
6
6
  "@brillout/picocolors": "^1.0.10",
@@ -0,0 +1,99 @@
1
+ export { getCSSForResponsiveFullcreenNavItems }
2
+
3
+ import assert from 'assert'
4
+ import { type NavItemGrouped } from '../navigation/Navigation'
5
+
6
+ function getCSSForResponsiveFullcreenNavItems(navItemsGrouped: NavItemGrouped[]) {
7
+ const columnWidthMin = 300
8
+ const columnWidthMax = 350
9
+ const columnsUnmerged = navItemsGrouped.map((navItem) => navItem.navItemChilds.length)
10
+ let CSS = '\n'
11
+ for (let numberOfColumns = navItemsGrouped.length; numberOfColumns >= 1; numberOfColumns--) {
12
+ let CSS_block: string[] = []
13
+ CSS_block.push(
14
+ ...[
15
+ //
16
+ ` html.navigation-fullscreen #navigation-content-main {`,
17
+ ` max-width: ${columnWidthMax * numberOfColumns}px;`,
18
+ ` grid-template-columns: repeat(${numberOfColumns}, auto);`,
19
+ ` }`,
20
+ ],
21
+ )
22
+ const columnsIdMap = determineColumns(columnsUnmerged, numberOfColumns)
23
+ columnsIdMap.forEach((columnGroupedId, columnUngroupedId) => {
24
+ CSS_block.push(
25
+ ...[
26
+ //
27
+ ` .nav-items-group:nth-child(${columnUngroupedId + 1}) {`,
28
+ ` grid-column: ${columnGroupedId + 1};`,
29
+ ` }`,
30
+ ],
31
+ )
32
+ })
33
+ const noMediaQuery = numberOfColumns === navItemsGrouped.length
34
+ if (!noMediaQuery) {
35
+ const maxWidth = (numberOfColumns + 1) * columnWidthMin - 1
36
+ CSS_block = [
37
+ //
38
+ `@media screen and (max-width: ${maxWidth}px) {`,
39
+ ...CSS_block,
40
+ `}`,
41
+ ]
42
+ }
43
+ CSS += CSS_block.join('\n') + '\n'
44
+ }
45
+ return CSS
46
+ }
47
+
48
+ function determineColumns(columnsUnmerged: number[], numberOfColumns: number): number[] {
49
+ assert(numberOfColumns <= columnsUnmerged.length)
50
+ const columnsMergingInit: ColumnMerging[] = columnsUnmerged.map((columnHeight, i) => ({
51
+ columnIdsMerged: [i],
52
+ heightTotal: columnHeight,
53
+ }))
54
+ const columnsMerged = mergeColumns(columnsMergingInit, numberOfColumns)
55
+ const columnsIdMap: number[] = new Array(columnsUnmerged.length)
56
+ assert(columnsMerged.length === numberOfColumns)
57
+ columnsMerged.forEach((columnMerged, columnMergedId) => {
58
+ columnMerged.columnIdsMerged.forEach((columnId) => {
59
+ columnsIdMap[columnId] = columnMergedId
60
+ })
61
+ })
62
+ assert(columnsIdMap.length === columnsUnmerged.length)
63
+ return columnsIdMap
64
+ }
65
+ type ColumnMerging = { columnIdsMerged: number[]; heightTotal: number }
66
+ function mergeColumns(columnsMerging: ColumnMerging[], maxNumberOfColumns: number): ColumnMerging[] {
67
+ if (columnsMerging.length <= maxNumberOfColumns) return columnsMerging
68
+
69
+ let mergeCandidate: null | (ColumnMerging & { i: number }) = null
70
+ for (let i = 0; i <= columnsMerging.length - 2; i++) {
71
+ const column1 = columnsMerging[i + 0]
72
+ const column2 = columnsMerging[i + 1]
73
+ const heightTotal = column1.heightTotal + column2.heightTotal
74
+ if (!mergeCandidate || mergeCandidate.heightTotal > heightTotal) {
75
+ mergeCandidate = {
76
+ i,
77
+ columnIdsMerged: [
78
+ //
79
+ ...column1.columnIdsMerged,
80
+ ...column2.columnIdsMerged,
81
+ ],
82
+ heightTotal,
83
+ }
84
+ }
85
+ }
86
+ assert(mergeCandidate)
87
+
88
+ const { i } = mergeCandidate
89
+ assert(-1 < i && i < columnsMerging.length - 1)
90
+ const columnsMergingMod = [
91
+ //
92
+ ...columnsMerging.slice(0, i),
93
+ mergeCandidate,
94
+ ...columnsMerging.slice(i + 2),
95
+ ]
96
+
97
+ assert(columnsMergingMod.length === columnsMerging.length - 1)
98
+ return mergeColumns(columnsMergingMod, maxNumberOfColumns)
99
+ }
@@ -18,12 +18,13 @@ import { installSectionUrlHashs } from '../installSectionUrlHashs'
18
18
  import { addFeatureClickHandlers, addTwitterWidgets } from '../components/FeatureList/FeatureList.client'
19
19
 
20
20
  addEcosystemStamp()
21
- initOnLinkClick()
22
21
  initNavigationFullscreenOnce()
23
22
 
24
23
  let root: ReactDOM.Root
25
24
  let renderPromiseResolve: () => void
26
25
  async function onRenderClient(pageContext: PageContextClient) {
26
+ onRenderStart()
27
+
27
28
  // TODO: stop using any
28
29
  const pageContextResolved: PageContextResolved = (pageContext as any).pageContextResolved
29
30
  const renderPromise = new Promise<void>((r) => {
@@ -52,6 +53,11 @@ function applyHead(pageContext: PageContextClient) {
52
53
  document.title = pageContextResolved.documentTitle
53
54
  }
54
55
 
56
+ function onRenderStart() {
57
+ hideMobileNavigation()
58
+ hideNavigationFullScreen()
59
+ }
60
+
55
61
  function onRenderDone() {
56
62
  autoScrollNav()
57
63
  installSectionUrlHashs()
@@ -69,27 +75,6 @@ function OnRenderDoneHook({ children }: { children: React.ReactNode }) {
69
75
  return children
70
76
  }
71
77
 
72
- function initOnLinkClick() {
73
- document.addEventListener('click', (ev) => {
74
- const linkTag = findLinkTag(ev.target as HTMLElement)
75
- if (!linkTag) return
76
- const url = linkTag.getAttribute('href')
77
- if (!url) return
78
- hideMobileNavigation()
79
- hideNavigationFullScreen()
80
- })
81
- }
82
- function findLinkTag(target: HTMLElement): null | HTMLElement {
83
- while (target.tagName !== 'A') {
84
- const { parentNode } = target
85
- if (!parentNode) {
86
- return null
87
- }
88
- target = parentNode as HTMLElement
89
- }
90
- return target
91
- }
92
-
93
78
  function setHydrationIsFinished() {
94
79
  // Used by:
95
80
  // - https://github.com/vikejs/vike/blob/9d67f3dd4bdfb38c835186b8147251e0e3b06657/docs/.testRun.ts#L22
@@ -6,6 +6,8 @@ import { assert } from '../utils/server'
6
6
  import type { PageContextResolved } from '../config/resolvePageContext'
7
7
  import { getPageElement } from './getPageElement'
8
8
  import type { OnRenderHtmlAsync } from 'vike/types'
9
+ import { groupByLevelMin } from '../navigation/Navigation'
10
+ import { getCSSForResponsiveFullcreenNavItems } from './getCSSForResponsiveFullcreenNavItems'
9
11
 
10
12
  const onRenderHtml: OnRenderHtmlAsync = async (
11
13
  pageContext,
@@ -15,6 +17,12 @@ Promise<Awaited<ReturnType<OnRenderHtmlAsync>>> => {
15
17
 
16
18
  const page = getPageElement(pageContext, pageContextResolved)
17
19
 
20
+ const { navItemsAll } = pageContextResolved.navigationData
21
+ const navItemsGrouped = groupByLevelMin(navItemsAll)
22
+ const CSSResponsiveNavItems = getCSSForResponsiveFullcreenNavItems(navItemsGrouped)
23
+
24
+ pageContextResolved.navigationData.navItems
25
+
18
26
  const descriptionTag = pageContextResolved.isLandingPage
19
27
  ? dangerouslySkipEscape(`<meta name="description" content="${pageContextResolved.meta.tagline}" />`)
20
28
  : ''
@@ -30,6 +38,7 @@ Promise<Awaited<ReturnType<OnRenderHtmlAsync>>> => {
30
38
  ${descriptionTag}
31
39
  <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no" />
32
40
  ${getOpenGraphTags(pageContext.urlPathname, pageContextResolved.documentTitle, pageContextResolved.meta)}
41
+ <style>${dangerouslySkipEscape(CSSResponsiveNavItems)}</style>
33
42
  </head>
34
43
  <body>
35
44
  <div id="page-view">${dangerouslySkipEscape(pageHtml)}</div>