@brillout/docpress 0.8.14 → 0.8.16

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 (51) hide show
  1. package/Layout.tsx +256 -179
  2. package/MenuModal.tsx +54 -46
  3. package/{Links.tsx → NavSecondaryContent.tsx} +5 -8
  4. package/components/HorizontalLine.tsx +4 -3
  5. package/components/Note.tsx +6 -2
  6. package/config/resolveHeadingsData.ts +7 -10
  7. package/css/code/block.css +5 -5
  8. package/css/code/inline.css +1 -1
  9. package/css/colorize-on-hover.css +2 -2
  10. package/dist/Layout.d.ts +10 -6
  11. package/dist/Layout.js +167 -122
  12. package/dist/MenuModal.d.ts +2 -0
  13. package/dist/MenuModal.js +46 -46
  14. package/dist/{Links.d.ts → NavSecondaryContent.d.ts} +3 -2
  15. package/dist/{Links.js → NavSecondaryContent.js} +5 -7
  16. package/dist/components/HorizontalLine.d.ts +1 -1
  17. package/dist/components/HorizontalLine.js +3 -2
  18. package/dist/components/Note.js +4 -3
  19. package/dist/config/resolveHeadingsData.d.ts +3 -4
  20. package/dist/config/resolveHeadingsData.js +5 -8
  21. package/dist/config/resolvePageContext.d.ts +2 -3
  22. package/dist/docsearch/SearchLink.js +1 -1
  23. package/dist/navigation/Collapsible.d.ts +10 -0
  24. package/dist/navigation/Collapsible.js +35 -0
  25. package/dist/navigation/Navigation.d.ts +0 -3
  26. package/dist/navigation/Navigation.js +106 -55
  27. package/dist/renderer/determineNavItemsColumnLayout.d.ts +3 -0
  28. package/dist/renderer/{determineColumnEntries.js → determineNavItemsColumnLayout.js} +34 -28
  29. package/dist/renderer/usePageContext.d.ts +2 -2
  30. package/dist/renderer/usePageContext.js +2 -4
  31. package/dist/utils/Style.d.ts +5 -0
  32. package/dist/utils/Style.js +6 -0
  33. package/dist/utils/cls.d.ts +3 -0
  34. package/dist/utils/cls.js +5 -0
  35. package/dist/utils/throttle.d.ts +1 -0
  36. package/dist/utils/throttle.js +14 -0
  37. package/docsearch/SearchLink.tsx +1 -1
  38. package/global.d.ts +1 -1
  39. package/navigation/Collapsible.css +11 -0
  40. package/navigation/Collapsible.tsx +64 -0
  41. package/navigation/Navigation.css +12 -6
  42. package/navigation/Navigation.tsx +191 -80
  43. package/package.json +1 -1
  44. package/renderer/{determineColumnEntries.ts → determineNavItemsColumnLayout.ts} +35 -29
  45. package/renderer/initOnNavigation.ts +37 -0
  46. package/renderer/onRenderClient.tsx +2 -0
  47. package/renderer/usePageContext.tsx +2 -5
  48. package/utils/Style.tsx +7 -0
  49. package/utils/cls.ts +8 -0
  50. package/utils/throttle.ts +10 -0
  51. package/dist/renderer/determineColumnEntries.d.ts +0 -3
@@ -0,0 +1,64 @@
1
+ export { Collapsible }
2
+
3
+ import React, { useRef, useState } from 'react'
4
+ import { cls } from '../utils/cls'
5
+ import './Collapsible.css'
6
+
7
+ function Collapsible({
8
+ head,
9
+ children,
10
+ disabled = false,
11
+ collapsedInit,
12
+ marginBottomOnExpand,
13
+ }: {
14
+ head: (onClick: () => void) => React.ReactNode
15
+ children: React.ReactNode
16
+ disabled: boolean
17
+ collapsedInit: boolean
18
+ marginBottomOnExpand?: number
19
+ }) {
20
+ const [collapsed, setCollapsed] = useState(collapsedInit)
21
+ const [isAnimating, setIsAnimating] = useState(false)
22
+ const contentRef = useRef<HTMLDivElement>(null)
23
+
24
+ const onClick = () => {
25
+ if (!disabled) {
26
+ setIsAnimating(true)
27
+ if (!collapsed) {
28
+ // If expanding, set height to current scroll height before animation
29
+ contentRef.current!.style.height = `${contentRef.current!.scrollHeight}px`
30
+ // Force a reflow
31
+ contentRef.current!.offsetHeight
32
+ }
33
+ setCollapsed((prev) => !prev)
34
+ }
35
+ }
36
+
37
+ const onTransitionEnd = () => {
38
+ setIsAnimating(false)
39
+ }
40
+
41
+ const showContent = disabled ? true : !collapsed
42
+
43
+ return (
44
+ <div
45
+ className={cls(['collapsible', !disabled && (showContent ? 'collapsible-expanded' : 'collapsible-collapsed')])}
46
+ >
47
+ {head(onClick)}
48
+ <div
49
+ ref={contentRef}
50
+ onTransitionEnd={onTransitionEnd}
51
+ style={{
52
+ height: !showContent ? 0 : isAnimating ? contentRef.current!.scrollHeight : 'auto',
53
+ overflow: 'hidden',
54
+ transition: 'none 0.3s ease',
55
+ transitionProperty: 'height, margin-bottom',
56
+ marginBottom: (showContent && marginBottomOnExpand) || undefined,
57
+ }}
58
+ aria-expanded={showContent}
59
+ >
60
+ {children}
61
+ </div>
62
+ </div>
63
+ )
64
+ }
@@ -20,7 +20,7 @@
20
20
  padding-left: var(--padding-left-global);
21
21
  padding-right: 4px;
22
22
  }
23
- .nav-item-level-1 + .nav-item-level-4 {
23
+ .nav-item-level-4:first-child {
24
24
  padding-top: 0;
25
25
  margin-top: -2px;
26
26
  }
@@ -59,9 +59,6 @@
59
59
  padding: var(--padding) 0;
60
60
  padding-left: calc(var(--padding-left-global) + 5px);
61
61
  }
62
- #menu-modal .nav-item-level-3 {
63
- border-right: 4px solid #eee;
64
- }
65
62
  .nav-item-level-3 {
66
63
  --shadow-size: 9px;
67
64
  --shadow-color: rgba(0, 0, 0, 0.11);
@@ -106,8 +103,17 @@
106
103
  position: absolute;
107
104
  top: 0;
108
105
  left: 0;
109
- transform: translate(0, 53px);
110
- height: calc(100% - 53px);
106
+ height: 100%;
111
107
  width: 3px;
112
108
  z-index: 99;
113
109
  }
110
+
111
+ :has(> .collapsible-icon) {
112
+ position: relative;
113
+ }
114
+ .collapsible-icon {
115
+ position: absolute;
116
+ top: calc(100% / 2 - 6px);
117
+ margin-left: 9px;
118
+ vertical-align: middle;
119
+ }
@@ -2,7 +2,6 @@
2
2
  export { NavigationContent }
3
3
  // TODO/refactor: do this only on the server side?
4
4
  export type { NavItem }
5
- export type { NavItemAll }
6
5
 
7
6
  import React, { useEffect, useState } from 'react'
8
7
  import { assert, assertWarning, jsxToTextContent } from '../utils/server'
@@ -12,7 +11,9 @@ import { usePageContext } from '../renderer/usePageContext'
12
11
  import '@docsearch/css'
13
12
  import '../global.d.ts'
14
13
  import { getViewportWidth } from '../utils/getViewportWidth'
15
- import { navWidthMax, navWidthMin } from '../Layout'
14
+ import { navLeftWidthMax, navLeftWidthMin } from '../Layout'
15
+ import { throttle } from '../utils/throttle'
16
+ import { Collapsible } from './Collapsible'
16
17
 
17
18
  type NavItem = {
18
19
  level: number
@@ -21,8 +22,6 @@ type NavItem = {
21
22
  title: string
22
23
  titleInNav: string
23
24
  menuModalFullWidth?: true
24
- }
25
- type NavItemAll = NavItem & {
26
25
  isColumnEntry?: ColumnMap
27
26
  }
28
27
  function NavigationContent(props: {
@@ -35,12 +34,12 @@ function NavigationContent(props: {
35
34
 
36
35
  let navContent: React.ReactNode
37
36
  if (!props.columnLayout) {
38
- navContent = navItemsWithComputed
39
- .filter((navItemGroup) => !props.showOnlyRelevant || navItemGroup.isRelevant)
40
- .map((navItem, i) => <NavItemComponent navItem={navItem} key={i} />)
37
+ let navItemsRelevant = navItemsWithComputed
38
+ if (props.showOnlyRelevant) navItemsRelevant = navItemsRelevant.filter((navItemGroup) => navItemGroup.isRelevant)
39
+ navContent = navItemsRelevant.map((navItem, i) => <NavItemComponent navItem={navItem} key={i} />)
41
40
  } else {
42
41
  assert(!props.showOnlyRelevant)
43
- navContent = <NavigationColumnLayout navItemsWithComputed={navItemsWithComputed} />
42
+ navContent = <NavigationWithColumnLayout navItemsWithComputed={navItemsWithComputed} />
44
43
  }
45
44
 
46
45
  return (
@@ -50,75 +49,124 @@ function NavigationContent(props: {
50
49
  )
51
50
  }
52
51
 
53
- function NavigationColumnLayout(props: { navItemsWithComputed: NavItemComputed[] }) {
52
+ function NavigationWithColumnLayout(props: { navItemsWithComputed: NavItemComputed[] }) {
54
53
  let [viewportWidth, setViewportWidth] = useState<number | undefined>()
55
54
  const updateviewportwidth = () => setViewportWidth(getViewportWidth())
56
55
  useEffect(() => {
57
56
  updateviewportwidth()
58
- window.addEventListener('resize', updateviewportwidth, { passive: true })
57
+ window.addEventListener('resize', throttle(updateviewportwidth, 300), { passive: true })
59
58
  })
60
-
61
59
  const navItemsByColumnLayouts = getNavItemsByColumnLayouts(props.navItemsWithComputed, viewportWidth)
62
- const margin = 40
63
-
64
60
  return (
65
61
  <>
66
- {navItemsByColumnLayouts.map(({ columns, isFullWidth }, i) => (
67
- <div
68
- key={i}
69
- style={{
70
- display: 'flex',
71
- width: columns.length * (navWidthMax + 20),
72
- justifyContent: 'space-between',
73
- maxWidth: '100%',
74
- margin: 'auto',
75
- marginTop: i === 0 ? -1 * margin : undefined,
76
- marginBottom: margin,
77
- }}
78
- >
79
- {columns.map((columnEntry, j) => (
80
- <div
81
- key={j}
82
- style={{
83
- flexGrow: 1,
84
- maxWidth: navWidthMax,
85
- display: 'flex',
86
- flexDirection: 'column',
87
- paddingTop: isFullWidth && j !== 0 ? 36 : undefined,
88
- }}
89
- >
90
- {columnEntry.map((navItems, k) => (
91
- <div key={k} style={{ marginTop: isFullWidth ? undefined : margin }}>
92
- {navItems.map((navItem, l) => (
93
- <NavItemComponent navItem={navItem} key={l} />
94
- ))}
95
- <CategoryBorder navItemLevel1={isFullWidth ? undefined : navItems[0]!} />
96
- </div>
97
- ))}
62
+ {navItemsByColumnLayouts.map((columnLayout, i) => (
63
+ <div key={i}>
64
+ {columnLayout.isFullWidthCategory ? (
65
+ <div style={{ marginTop: 0 }}>
66
+ <ColumnsWrapper numberOfColumns={columnLayout.columns.length}>
67
+ <Collapsible
68
+ head={(onClick) => <NavItemComponent navItem={columnLayout.navItemLevel1} onClick={onClick} />}
69
+ disabled={columnLayout.columns.length > 1}
70
+ collapsedInit={!columnLayout.navItemLevel1.isRelevant}
71
+ marginBottomOnExpand={10}
72
+ >
73
+ <ColumnsLayout className="collapsible">
74
+ {columnLayout.columns.map((column, j) => (
75
+ <Column key={j}>
76
+ {column.navItems.map((navItem, k) => (
77
+ <NavItemComponent key={k} navItem={navItem} />
78
+ ))}
79
+ </Column>
80
+ ))}
81
+ <CategoryBorder navItemLevel1={columnLayout.navItemLevel1} />
82
+ </ColumnsLayout>
83
+ </Collapsible>
84
+ </ColumnsWrapper>
98
85
  </div>
99
- ))}
100
- <CategoryBorder navItemLevel1={!isFullWidth ? undefined : columns[0][0][0]!} />
86
+ ) : (
87
+ <ColumnsWrapper numberOfColumns={columnLayout.columns.length}>
88
+ <ColumnsLayout>
89
+ {columnLayout.columns.map((column, j) => (
90
+ <Column key={j}>
91
+ {column.categories.map((category, k) => (
92
+ <div key={k} style={{ marginBottom: 0 }}>
93
+ <Collapsible
94
+ head={(onClick) => <NavItemComponent navItem={category.navItemLevel1} onClick={onClick} />}
95
+ disabled={columnLayout.columns.length > 1}
96
+ collapsedInit={!category.navItemLevel1.isRelevant}
97
+ marginBottomOnExpand={40}
98
+ >
99
+ {category.navItems.map((navItem, l) => (
100
+ <NavItemComponent key={l} navItem={navItem} />
101
+ ))}
102
+ <CategoryBorder navItemLevel1={category.navItemLevel1} />
103
+ </Collapsible>
104
+ </div>
105
+ ))}
106
+ </Column>
107
+ ))}
108
+ </ColumnsLayout>
109
+ </ColumnsWrapper>
110
+ )}
101
111
  </div>
102
112
  ))}
103
113
  </>
104
114
  )
105
115
  }
106
-
107
- function CategoryBorder({ navItemLevel1 }: { navItemLevel1?: NavItemComputed }) {
108
- return !navItemLevel1 ? null : (
116
+ function Column({ children }: { children: React.ReactNode }) {
117
+ return (
109
118
  <div
110
- className="category-border"
111
119
  style={{
112
- background: navItemLevel1.color!,
120
+ flexGrow: 1,
121
+ maxWidth: navLeftWidthMax,
122
+ display: 'flex',
123
+ flexDirection: 'column',
113
124
  }}
114
- />
125
+ >
126
+ {children}
127
+ </div>
115
128
  )
116
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
+ }
117
160
 
161
+ type PropsNavItem = PropsAnchor & PropsSpan
162
+ type PropsAnchor = React.HTMLProps<HTMLAnchorElement>
163
+ type PropsSpan = React.HTMLProps<HTMLSpanElement>
118
164
  function NavItemComponent({
119
165
  navItem,
166
+ onClick,
120
167
  }: {
121
168
  navItem: NavItemComputed
169
+ onClick?: PropsNavItem['onClick']
122
170
  }) {
123
171
  assert([1, 2, 3, 4].includes(navItem.level), navItem)
124
172
 
@@ -141,9 +189,20 @@ function NavItemComponent({
141
189
  )
142
190
  }
143
191
 
144
- const props: PropsAnchor & PropsSpan = {
192
+ let children: JSX.Element = titleInNavJsx
193
+ if (navItem.level === 1) {
194
+ children = (
195
+ <>
196
+ {children}
197
+ <Chevron className="collapsible-icon" height={9} />
198
+ </>
199
+ )
200
+ }
201
+
202
+ const props: PropsNavItem = {
145
203
  href: navItem.url ?? undefined,
146
- children: titleInNavJsx,
204
+ children,
205
+ onClick,
147
206
  className: [
148
207
  'nav-item',
149
208
  'nav-item-level-' + navItem.level,
@@ -159,8 +218,6 @@ function NavItemComponent({
159
218
  ['--category-color']: navItem.color!,
160
219
  }
161
220
  }
162
- type PropsAnchor = React.HTMLProps<HTMLAnchorElement>
163
- type PropsSpan = React.HTMLProps<HTMLSpanElement>
164
221
 
165
222
  if (navItem.level === 2 || navItem.level === 3) {
166
223
  return <a {...props} />
@@ -169,45 +226,88 @@ function NavItemComponent({
169
226
  }
170
227
  }
171
228
 
172
- type NavItemsByColumnLayout = { columns: NavItemComputed[][][]; isFullWidth: boolean }
229
+ type NavItemsByColumnLayout =
230
+ | {
231
+ columns: {
232
+ categories: {
233
+ navItemLevel1: NavItemComputed
234
+ navItems: NavItemComputed[]
235
+ }[]
236
+ }[]
237
+ isFullWidthCategory: false
238
+ }
239
+ | {
240
+ navItemLevel1: NavItemComputed
241
+ columns: { navItems: NavItemComputed[] }[]
242
+ isFullWidthCategory: true
243
+ }
244
+ type NavItemsByColumnLayout2 = { columns: NavItemComputed[][][]; isFullWidthCategory: boolean }
173
245
  function getNavItemsByColumnLayouts(navItems: NavItemComputed[], viewportWidth: number = 0): NavItemsByColumnLayout[] {
174
246
  const navItemsByColumnEntries = getNavItemsByColumnEntries(navItems)
175
- const numberOfColumnsMax = Math.floor(viewportWidth / navWidthMin) || 1
247
+ const numberOfColumnsMax = Math.floor(viewportWidth / navLeftWidthMin) || 1
176
248
  const navItemsByColumnLayouts: NavItemsByColumnLayout[] = navItemsByColumnEntries.map(
177
- ({ columnEntries, isFullWidth }) => {
249
+ ({ columnEntries, isFullWidthCategory }) => {
178
250
  const numberOfColumns = Math.min(numberOfColumnsMax, columnEntries.length)
179
- const columns: NavItemComputed[][][] = []
180
- columnEntries.forEach((columnEntry) => {
181
- const idx = numberOfColumns === 1 ? 0 : columnEntry.columnMap[numberOfColumns]!
182
- assert(idx >= 0)
183
- columns[idx] ??= []
184
- columns[idx].push(columnEntry.navItems)
185
- })
186
- const navItemsByColumnLayout: NavItemsByColumnLayout = { columns, isFullWidth }
187
- return navItemsByColumnLayout
251
+ if (!isFullWidthCategory) {
252
+ const columns: {
253
+ categories: {
254
+ navItemLevel1: NavItemComputed
255
+ navItems: NavItemComputed[]
256
+ }[]
257
+ }[] = []
258
+ columnEntries.forEach((columnEntry) => {
259
+ const idx = numberOfColumns === 1 ? 0 : columnEntry.columnMap[numberOfColumns]!
260
+ assert(idx >= 0)
261
+ columns[idx] ??= { categories: [] }
262
+ const navItemLevel1 = columnEntry.navItems[0]
263
+ const navItems = columnEntry.navItems.slice(1)
264
+ columns[idx].categories.push({ navItemLevel1, navItems })
265
+ })
266
+ const navItemsByColumnLayout: NavItemsByColumnLayout = { columns, isFullWidthCategory }
267
+ return navItemsByColumnLayout
268
+ } else {
269
+ let navItemLevel1: NavItemComputed
270
+ const columns: { navItems: NavItemComputed[] }[] = []
271
+ columnEntries.forEach((columnEntry, i) => {
272
+ const idx = numberOfColumns === 1 ? 0 : columnEntry.columnMap[numberOfColumns]!
273
+ assert(idx >= 0)
274
+ columns[idx] ??= { navItems: [] }
275
+ let { navItems } = columnEntry
276
+ if (i === 0) {
277
+ navItemLevel1 = navItems[0]
278
+ navItems = navItems.slice(1)
279
+ }
280
+ columns[idx].navItems.push(...navItems)
281
+ })
282
+ const navItemsByColumnLayout: NavItemsByColumnLayout = {
283
+ columns,
284
+ navItemLevel1: navItemLevel1!,
285
+ isFullWidthCategory,
286
+ }
287
+ return navItemsByColumnLayout
288
+ }
188
289
  },
189
290
  )
190
291
  return navItemsByColumnLayouts
191
292
  }
192
-
193
- type NavItemsByColumnEntries = { columnEntries: ColumnEntry[]; isFullWidth: boolean }[]
293
+ type NavItemsByColumnEntries = { columnEntries: ColumnEntry[]; isFullWidthCategory: boolean }[]
194
294
  type ColumnEntry = { navItems: NavItemComputed[]; columnMap: ColumnMap }
195
295
  type ColumnMap = Record<number, number>
196
296
  function getNavItemsByColumnEntries(navItems: NavItemComputed[]): NavItemsByColumnEntries {
197
297
  const navItemsByColumnEntries: NavItemsByColumnEntries = []
198
298
  let columnEntries: ColumnEntry[] = []
199
299
  let columnEntry: ColumnEntry
200
- let isFullWidth: boolean | undefined
300
+ let isFullWidthCategory: boolean | undefined
201
301
  navItems.forEach((navItem) => {
202
302
  if (navItem.level === 1) {
203
- const isFullWidthPrevious = isFullWidth
204
- isFullWidth = !!navItem.menuModalFullWidth
205
- if (isFullWidthPrevious !== undefined && isFullWidthPrevious !== isFullWidth) {
206
- navItemsByColumnEntries.push({ columnEntries, isFullWidth: isFullWidthPrevious })
303
+ const isFullWidthCategoryPrevious = isFullWidthCategory
304
+ isFullWidthCategory = !!navItem.menuModalFullWidth
305
+ if (isFullWidthCategoryPrevious !== undefined && isFullWidthCategoryPrevious !== isFullWidthCategory) {
306
+ navItemsByColumnEntries.push({ columnEntries, isFullWidthCategory: isFullWidthCategoryPrevious })
207
307
  columnEntries = []
208
308
  }
209
309
  }
210
- assert(isFullWidth !== undefined)
310
+ assert(isFullWidthCategory !== undefined)
211
311
  if (navItem.isColumnEntry) {
212
312
  assert(navItem.level === 1 || navItem.level === 4)
213
313
  columnEntry = { navItems: [navItem], columnMap: navItem.isColumnEntry }
@@ -217,13 +317,13 @@ function getNavItemsByColumnEntries(navItems: NavItemComputed[]): NavItemsByColu
217
317
  columnEntry.navItems.push(navItem)
218
318
  }
219
319
  })
220
- assert(isFullWidth !== undefined)
221
- navItemsByColumnEntries.push({ columnEntries, isFullWidth })
320
+ assert(isFullWidthCategory !== undefined)
321
+ navItemsByColumnEntries.push({ columnEntries, isFullWidthCategory })
222
322
  return navItemsByColumnEntries
223
323
  }
224
324
 
225
325
  type NavItemComputed = ReturnType<typeof getNavItemsWithComputed>[number]
226
- function getNavItemsWithComputed(navItems: NavItemAll[], currentUrl: string) {
326
+ function getNavItemsWithComputed(navItems: NavItem[], currentUrl: string) {
227
327
  let navItemIdx: number | undefined
228
328
  const navItemsWithComputed = navItems.map((navItem, i) => {
229
329
  assert([1, 2, 3, 4].includes(navItem.level), navItem)
@@ -269,3 +369,14 @@ function getNavItemsWithComputed(navItems: NavItemAll[], currentUrl: string) {
269
369
 
270
370
  return navItemsWithComputed
271
371
  }
372
+
373
+ function Chevron(props: React.HTMLProps<SVGSVGElement>) {
374
+ return (
375
+ <svg viewBox="0 0 512 292.52" xmlns="http://www.w3.org/2000/svg" {...props}>
376
+ <path
377
+ fill="#aaa"
378
+ d="M10.725 82.42L230.125 261.82c6.8 6.8 16.2 10.7 25.9 10.7s19.1-3.9 25.9-10.7l219.4-179.4c14.3-14.3 14.3-37.4 0-51.7s-37.4-14.3-51.7 0l-193.6 153.6-193.6-153.6c-14.3-14.3-37.4-14.3-51.7 0s-14.3 37.5 0 51.7z"
379
+ />
380
+ </svg>
381
+ )
382
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brillout/docpress",
3
- "version": "0.8.14",
3
+ "version": "0.8.16",
4
4
  "type": "module",
5
5
  "dependencies": {
6
6
  "@brillout/picocolors": "^1.0.10",
@@ -1,14 +1,30 @@
1
- export { determineColumnEntries }
1
+ export { determineNavItemsColumnLayout }
2
2
 
3
3
  // A CSS-only solution doesn't seem to exist.
4
4
  // - https://github.com/brillout/docpress/blob/2e41d8b9df098ff8312b02f7e9d41a202548e2b9/src/renderer/getStyleColumnLayout.ts#L4-L26
5
5
 
6
- import { type NavItemAll } from '../navigation/Navigation'
6
+ import { type NavItem } from '../navigation/Navigation'
7
7
  import { assert, assertUsage, isBrowser } from '../utils/server'
8
8
  assert(!isBrowser())
9
9
 
10
- type NavItemWithLength = NavItemAll & { numberOfHeadings: number | null }
11
- function determineColumnEntries(navItems: NavItemAll[]): undefined {
10
+ function determineNavItemsColumnLayout(navItems: NavItem[]): undefined {
11
+ const columnLayouts = getColumnEntries(navItems)
12
+ columnLayouts.forEach((columnEntries) => {
13
+ for (let numberOfColumns = columnEntries.length; numberOfColumns >= 1; numberOfColumns--) {
14
+ const columnMapping = determineColumnLayout(
15
+ columnEntries.map((columnEntry) => columnEntry.numberOfEntries),
16
+ numberOfColumns,
17
+ )
18
+ columnEntries.forEach((columnEntry, i) => {
19
+ columnEntry.navItemLeader.isColumnEntry ??= {}
20
+ columnEntry.navItemLeader.isColumnEntry[numberOfColumns] = columnMapping[i]
21
+ })
22
+ }
23
+ })
24
+ }
25
+
26
+ function getColumnEntries(navItems: NavItem[]) {
27
+ type NavItemWithLength = NavItem & { numberOfHeadings: number | null }
12
28
  const navItemsWithLength: NavItemWithLength[] = navItems.map((navItem) => ({
13
29
  ...navItem,
14
30
  numberOfHeadings: navItem.level === 1 || navItem.level === 4 ? 0 : null,
@@ -36,17 +52,17 @@ function determineColumnEntries(navItems: NavItemAll[]): undefined {
36
52
  }
37
53
  })
38
54
 
39
- type ColumnEntry = { navItemLeader: NavItemAll; numberOfEntries: number }
55
+ type ColumnEntry = { navItemLeader: NavItem; numberOfEntries: number }
40
56
  const columnLayouts: ColumnEntry[][] = []
41
57
  let columnEntries: ColumnEntry[] = []
42
- let isFullWidth: boolean | undefined
58
+ let isFullWidthCategory: boolean | undefined
43
59
  navItemsWithLength.forEach((navItem, i) => {
44
- let isFullWidthBegin = false
60
+ let isFullWidthCategoryBegin = false
45
61
  if (navItem.level === 1) {
46
- const isFullWidthPrevious = isFullWidth
47
- isFullWidth = !!navItem.menuModalFullWidth
48
- if (isFullWidth) isFullWidthBegin = true
49
- if (isFullWidthPrevious !== undefined && isFullWidthPrevious !== isFullWidth) {
62
+ const isFullWidthCategoryPrevious = isFullWidthCategory
63
+ isFullWidthCategory = !!navItem.menuModalFullWidth
64
+ if (isFullWidthCategory) isFullWidthCategoryBegin = true
65
+ if (isFullWidthCategoryPrevious !== undefined && isFullWidthCategoryPrevious !== isFullWidthCategory) {
50
66
  columnLayouts.push(columnEntries)
51
67
  columnEntries = []
52
68
  }
@@ -54,16 +70,18 @@ function determineColumnEntries(navItems: NavItemAll[]): undefined {
54
70
  const navItemPrevious = navItemsWithLength[i - 1]
55
71
  const navItemNext = navItemsWithLength[i + 1]
56
72
  if (
57
- !isFullWidth ? navItem.level === 1 : (navItem.level === 4 && navItemPrevious!.level !== 1) || isFullWidthBegin
73
+ !isFullWidthCategory
74
+ ? navItem.level === 1
75
+ : (navItem.level === 4 && navItemPrevious!.level !== 1) || isFullWidthCategoryBegin
58
76
  ) {
59
- if (isFullWidth) {
60
- assert(navItem.level === 4 || (navItem.level === 1 && isFullWidthBegin))
77
+ if (isFullWidthCategory) {
78
+ assert(navItem.level === 4 || (navItem.level === 1 && isFullWidthCategoryBegin))
61
79
  } else {
62
80
  assert(navItem.level === 1)
63
81
  }
64
82
  let { numberOfHeadings } = navItem
65
83
  assert(numberOfHeadings !== null)
66
- if (isFullWidthBegin) {
84
+ if (isFullWidthCategoryBegin) {
67
85
  assert(navItem.level === 1)
68
86
  assertUsage(
69
87
  navItemNext && navItemNext.level === 4,
@@ -78,22 +96,10 @@ function determineColumnEntries(navItems: NavItemAll[]): undefined {
78
96
  })
79
97
  assert(columnEntries!)
80
98
  columnLayouts.push(columnEntries)
81
-
82
- columnLayouts.forEach((columnEntries) => {
83
- for (let numberOfColumns = columnEntries.length; numberOfColumns >= 1; numberOfColumns--) {
84
- const columnsIdMap = determineColumns(
85
- columnEntries.map((columnEntry) => columnEntry.numberOfEntries),
86
- numberOfColumns,
87
- )
88
- columnEntries.forEach((columnEntry, i) => {
89
- columnEntry.navItemLeader.isColumnEntry ??= {}
90
- columnEntry.navItemLeader.isColumnEntry[numberOfColumns] = columnsIdMap[i]
91
- })
92
- }
93
- })
99
+ return columnLayouts
94
100
  }
95
101
 
96
- function determineColumns(columnsUnmerged: number[], numberOfColumns: number): number[] {
102
+ function determineColumnLayout(columnsUnmerged: number[], numberOfColumns: number): number[] {
97
103
  assert(numberOfColumns <= columnsUnmerged.length)
98
104
  const columnsMergingInit: ColumnMerging[] = columnsUnmerged.map((columnHeight, i) => ({
99
105
  columnIdsMerged: [i],
@@ -0,0 +1,37 @@
1
+ export { initOnNavigation }
2
+
3
+ import { isBrowser } from '../utils/isBrowser'
4
+ import { closeMenuModal } from '../MenuModal'
5
+ import { unexpandNav } from '../Layout'
6
+
7
+ function onNavigation() {
8
+ closeMenuModal()
9
+ unexpandNav()
10
+ }
11
+
12
+ function initOnNavigation() {
13
+ if (!isBrowser()) return
14
+ document.addEventListener('click', (ev) => onLinkClick(ev, onNavigation))
15
+ // It's redundant as onLinkClick() should be enough, but just to be sure.
16
+ addEventListener('hashchange', onNavigation)
17
+ }
18
+
19
+ function onLinkClick(ev: MouseEvent, callback: () => void) {
20
+ if (ev.altKey || ev.ctrlKey || ev.metaKey || ev.shiftKey) return
21
+ const linkTag = findLinkTag(ev.target as HTMLElement)
22
+ if (!linkTag) return
23
+ const href = linkTag.getAttribute('href')
24
+ if (!href) return
25
+ if (!href.startsWith('/') && !href.startsWith('#')) return
26
+ callback()
27
+ }
28
+ function findLinkTag(target: HTMLElement): null | HTMLElement {
29
+ while (target.tagName !== 'A') {
30
+ const { parentNode } = target
31
+ if (!parentNode) {
32
+ return null
33
+ }
34
+ target = parentNode as HTMLElement
35
+ }
36
+ return target
37
+ }
@@ -11,6 +11,7 @@ import { autoScrollNav } from '../autoScrollNav'
11
11
  import { installSectionUrlHashs } from '../installSectionUrlHashs'
12
12
  import { getGlobalObject } from '../utils/client'
13
13
  import { initKeyBindings } from '../initKeyBindings'
14
+ import { initOnNavigation } from './initOnNavigation'
14
15
 
15
16
  const globalObject = getGlobalObject<{
16
17
  root?: ReactDOM.Root
@@ -19,6 +20,7 @@ const globalObject = getGlobalObject<{
19
20
 
20
21
  addEcosystemStamp()
21
22
  initKeyBindings()
23
+ initOnNavigation()
22
24
 
23
25
  async function onRenderClient(pageContext: PageContextClient) {
24
26
  onRenderStart()
@@ -1,13 +1,10 @@
1
- // `usePageContext` allows us to access `pageContext` in any React component.
2
- // More infos: https://vike.dev/pageContext-anywhere
1
+ export { usePageContext }
2
+ export { PageContextProvider }
3
3
 
4
4
  import React, { useContext } from 'react'
5
5
  import type { PageContextResolved } from '../config/resolvePageContext'
6
6
  import type { PageContext } from 'vike/types'
7
7
 
8
- export { PageContextProvider }
9
- export { usePageContext }
10
-
11
8
  const Context = React.createContext<PageContextResolved>(undefined as any)
12
9
 
13
10
  function PageContextProvider({