diff --git a/CHANGELOG.md b/CHANGELOG.md
index a87666a8f..1ff5d2f9b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,7 @@
## Features
+- Added a `--navigation.includeFolders` (default: `true`) option to create nested navigation for projects which include many entry points, #2388.
- Type parameters on functions/classes can will now link to the "Type Parameters" section, #2322.
Type parameters have also been changed to have a distinct color from type aliases when rendering, which can be changed with custom CSS.
- TypeDoc now provides warnings if a signature comment is directly specified on a signature and contains `@param` tags which do not apply, #2368.
@@ -29,6 +30,8 @@
- `@group` and `@category` organization is now applied later to allow inherited comments to create groups/categories, #2459.
- Conversion order should no longer affect link resolution for classes with properties whose type does not rely on `this`, #2466.
- Keyword syntax highlighting introduced in 0.25.4 was not always applied to keywords.
+- Module reflections now have a custom `M` icon rather than sharing with the namespace icon.
+ Note: The default CSS still colors both modules and namespaces the same, as it is generally uncommon to have both in a generated site.
- If all members in a group are hidden from the page, the group will be hidden in the page index on page load.
## v0.25.4 (2023-11-26)
diff --git a/src/lib/output/themes/default/DefaultTheme.tsx b/src/lib/output/themes/default/DefaultTheme.tsx
index da0b6754a..b0900d44f 100644
--- a/src/lib/output/themes/default/DefaultTheme.tsx
+++ b/src/lib/output/themes/default/DefaultTheme.tsx
@@ -305,27 +305,70 @@ export class DefaultTheme extends Theme {
return parent.groups.map(toNavigation);
}
+ if (
+ opts.includeFolders &&
+ parent.children?.every((child) => child.kindOf(ReflectionKind.Module)) &&
+ parent.children.some((child) => child.name.includes("/"))
+ ) {
+ return deriveModuleFolders(parent.children);
+ }
+
return parent.children?.map(toNavigation);
}
- function shouldShowCategories(
- reflection: Reflection,
- opts: { includeCategories: boolean; includeGroups: boolean },
- ) {
- if (opts.includeCategories) {
- return !reflection.comment?.hasModifier("@hideCategories");
+ function deriveModuleFolders(children: DeclarationReflection[]) {
+ const result: NavigationElement[] = [];
+
+ const resolveOrCreateParents = (
+ path: string[],
+ root: NavigationElement[] = result,
+ ): NavigationElement[] => {
+ if (path.length > 1) {
+ const inner = root.find((el) => el.text === path[0]);
+ if (inner) {
+ inner.children ||= [];
+ return resolveOrCreateParents(path.slice(1), inner.children);
+ } else {
+ root.push({
+ text: path[0],
+ children: [],
+ });
+ return resolveOrCreateParents(path.slice(1), root[root.length - 1].children);
+ }
+ }
+
+ return root;
+ };
+
+ // Note: This might end up putting a module within another module if we document
+ // both foo/index.ts and foo/bar.ts.
+ for (const child of children) {
+ const parts = child.name.split("/");
+ const collection = resolveOrCreateParents(parts);
+ const nav = toNavigation(child);
+ nav.text = parts[parts.length - 1];
+ collection.push(nav);
}
- return reflection.comment?.hasModifier("@showCategories") === true;
- }
- function shouldShowGroups(
- reflection: Reflection,
- opts: { includeCategories: boolean; includeGroups: boolean },
- ) {
- if (opts.includeGroups) {
- return !reflection.comment?.hasModifier("@hideGroups");
+ // Now merge single-possible-paths together so we don't have folders in our navigation
+ // which contain only another single folder.
+ const queue = [...result];
+ while (queue.length) {
+ const review = queue.shift()!;
+ queue.push(...(review.children || []));
+ if (review.kind || review.path) continue;
+
+ if (review.children?.length === 1) {
+ const copyFrom = review.children[0];
+ const fullName = `${review.text}/${copyFrom.text}`;
+ delete review.children;
+ Object.assign(review, copyFrom);
+ review.text = fullName;
+ queue.push(review);
+ }
}
- return reflection.comment?.hasModifier("@showGroups") === true;
+
+ return result;
}
}
@@ -401,3 +444,17 @@ function getReflectionClasses(reflection: DeclarationReflection, filters: Record
return classes.join(" ");
}
+
+function shouldShowCategories(reflection: Reflection, opts: { includeCategories: boolean; includeGroups: boolean }) {
+ if (opts.includeCategories) {
+ return !reflection.comment?.hasModifier("@hideCategories");
+ }
+ return reflection.comment?.hasModifier("@showCategories") === true;
+}
+
+function shouldShowGroups(reflection: Reflection, opts: { includeCategories: boolean; includeGroups: boolean }) {
+ if (opts.includeGroups) {
+ return !reflection.comment?.hasModifier("@hideGroups");
+ }
+ return reflection.comment?.hasModifier("@showGroups") === true;
+}
diff --git a/src/lib/output/themes/default/partials/icon.tsx b/src/lib/output/themes/default/partials/icon.tsx
index ebc417352..dc26cec98 100644
--- a/src/lib/output/themes/default/partials/icon.tsx
+++ b/src/lib/output/themes/default/partials/icon.tsx
@@ -123,9 +123,14 @@ export const icons: Record<
true,
),
[ReflectionKind.Module]() {
- return this[ReflectionKind.Namespace]();
+ return kindIcon(
+ ,
+ "var(--color-ts-module)",
+ );
},
-
[ReflectionKind.Namespace]: () =>
kindIcon(
kindIcon(
diff --git a/src/lib/utils/options/declaration.ts b/src/lib/utils/options/declaration.ts
index 4b77c63ff..6bfb0f880 100644
--- a/src/lib/utils/options/declaration.ts
+++ b/src/lib/utils/options/declaration.ts
@@ -144,6 +144,7 @@ export interface TypeDocOptionMap {
navigation: {
includeCategories: boolean;
includeGroups: boolean;
+ includeFolders: boolean;
fullTree: boolean;
};
visibilityFilters: ManuallyValidatedOption<{
diff --git a/src/lib/utils/options/sources/typedoc.ts b/src/lib/utils/options/sources/typedoc.ts
index 493674cd9..0116d7070 100644
--- a/src/lib/utils/options/sources/typedoc.ts
+++ b/src/lib/utils/options/sources/typedoc.ts
@@ -464,6 +464,7 @@ export function addTypeDocOptions(options: Pick) {
defaults: {
includeCategories: false,
includeGroups: false,
+ includeFolders: true,
fullTree: false,
},
});