diff --git a/.config/typedoc.json b/.config/typedoc.json index d6b835843..5b9893b01 100644 --- a/.config/typedoc.json +++ b/.config/typedoc.json @@ -24,6 +24,7 @@ "treatWarningsAsErrors": false, "categorizeByGroup": false, "categoryOrder": ["Reflections", "Types", "Comments", "*"], + "groupOrder": ["Common", "*"], "validation": { "notExported": true, "invalidLink": true, diff --git a/CHANGELOG.md b/CHANGELOG.md index c05b3aa8b..d3679028a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,8 @@ title: Changelog - Fixed handling of `@enum` if the type was declared before the variable, #2719. - Introduced a new `@useDeclaredType` tag for type aliases which can sometimes improve their documentation, #2654. - Introduced `--favicon` option to specify a `.ico` or `.svg` favicon to reference. +- Sections within the page and in the "On This Page" navigation are now tied together and will expand/collapse together, #2335. +- The arrows to indicate whether or not a section is open now work when JavaScript is disabled. TODO: diff --git a/src/index.ts b/src/index.ts index 28f094263..f6791e8fe 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,13 +1,4 @@ /** - * The most commonly useful items in the API are: - * - * - {@link Application} - * - {@link Options} - * - {@link Converter} - * - {@link Serializer} - * - {@link Deserializer} - * - {@link Renderer} - * * @module TypeDoc API */ export { Application, type ApplicationEvents } from "./lib/application.js"; diff --git a/src/lib/application.ts b/src/lib/application.ts index 15e53d2d2..fe8a445dc 100644 --- a/src/lib/application.ts +++ b/src/lib/application.ts @@ -104,6 +104,9 @@ export interface ApplicationEvents { * * Access to an Application instance can be retrieved with {@link Application.bootstrap} or * {@link Application.bootstrapWithPlugins}. It can not be constructed manually. + * + * @group Common + * @summary Root level class which contains most useful behavior. */ export class Application extends AbstractComponent< Application, diff --git a/src/lib/converter/converter.ts b/src/lib/converter/converter.ts index 7314b8bda..4e208c5a0 100644 --- a/src/lib/converter/converter.ts +++ b/src/lib/converter/converter.ts @@ -91,6 +91,9 @@ export interface ConverterEvents { /** * Compiles source files using TypeScript and converts compiler symbols to reflections. + * + * @group Common + * @summary Responsible for converting TypeScript symbols into {@link Reflection}s and {@link Type}s. */ export class Converter extends AbstractComponent { /** @internal */ diff --git a/src/lib/output/renderer.ts b/src/lib/output/renderer.ts index 947f1a031..b27035fa9 100644 --- a/src/lib/output/renderer.ts +++ b/src/lib/output/renderer.ts @@ -164,6 +164,9 @@ export interface RendererEvents { * * {@link Renderer.EVENT_PREPARE_INDEX}
* Triggered when the JavascriptIndexPlugin is preparing the search index. Listeners receive * an instance of {@link IndexEvent}. + * + * @summary Writes HTML output from TypeDoc's models + * @group Common */ export class Renderer extends AbstractComponent { private themes = new Map Theme>([ diff --git a/src/lib/output/themes/default/assets/typedoc/components/Accordion.ts b/src/lib/output/themes/default/assets/typedoc/components/Accordion.ts index 2da0ccc9d..ad5dcd83f 100644 --- a/src/lib/output/themes/default/assets/typedoc/components/Accordion.ts +++ b/src/lib/output/themes/default/assets/typedoc/components/Accordion.ts @@ -1,54 +1,67 @@ import { Component, IComponentOptions } from "../Component.js"; import { storage } from "../utils/storage.js"; +const ACCORDION_INSTANCES = new Map(); + +class AccordionImpl { + open: boolean; + accordions: HTMLDetailsElement[] = []; + key: string; + + constructor(key: string, open: boolean) { + this.key = key; + this.open = open; + } + + add(accordion: HTMLDetailsElement) { + this.accordions.push(accordion); + accordion.open = this.open; + accordion.addEventListener("toggle", () => { + this.toggle(accordion.open); + }); + } + + toggle(open: boolean) { + for (const acc of this.accordions) { + acc.open = open; + } + storage.setItem(this.key, open.toString()); + } +} + /** * Handles accordion dropdown behavior. */ export class Accordion extends Component { - /** - * The heading container for this accordion. - */ - private summary: HTMLElement; - - /** - * The chevron icon next to this accordion's heading. - */ - private icon: SVGElement; - - /** - * The key by which to store this accordion's state in storage. - */ - private readonly key: string; - constructor(options: IComponentOptions) { super(options); - this.summary = this.el.querySelector(".tsd-accordion-summary")!; - this.icon = this.summary.querySelector("svg")!; - this.key = `tsd-accordion-${ - this.summary.dataset.key ?? - this.summary.textContent!.trim().replace(/\s+/g, "-").toLowerCase() - }`; - - const stored = storage.getItem(this.key); - this.el.open = stored ? stored === "true" : this.el.open; - - this.el.addEventListener("toggle", () => this.update()); + const summary = this.el.querySelector("summary")!; // Safari is broken and doesn't let you click on a link within // a tag, so we have to manually handle clicks there. - const link = this.summary.querySelector("a"); + const link = summary.querySelector("a"); if (link) { link.addEventListener("click", () => { location.assign(link.href); }); } - this.update(); - } + const key = `tsd-accordion-${ + summary.dataset.key ?? + summary.textContent!.trim().replace(/\s+/g, "-").toLowerCase() + }`; + + let inst: AccordionImpl; + if (ACCORDION_INSTANCES.has(key)) { + inst = ACCORDION_INSTANCES.get(key)!; + } else { + const stored = storage.getItem(key); + const open = stored ? stored === "true" : this.el.open; + inst = new AccordionImpl(key, open); + ACCORDION_INSTANCES.set(key, inst); + } - private update() { - this.icon.style.transform = `rotate(${this.el.open ? 0 : -90}deg)`; - storage.setItem(this.key, this.el.open.toString()); + inst.add(this.el); } } diff --git a/src/lib/output/themes/default/partials/navigation.tsx b/src/lib/output/themes/default/partials/navigation.tsx index 4e1c7c80f..ec10ec756 100644 --- a/src/lib/output/themes/default/partials/navigation.tsx +++ b/src/lib/output/themes/default/partials/navigation.tsx @@ -206,7 +206,7 @@ export function pageNavigation(context: DefaultThemeRenderContext, props: PageEv if (section.title) { sections.push(
- + {context.icons.chevronDown()} {section.title} diff --git a/src/lib/serialization/deserializer.ts b/src/lib/serialization/deserializer.ts index d8cca208a..4514798e2 100644 --- a/src/lib/serialization/deserializer.ts +++ b/src/lib/serialization/deserializer.ts @@ -49,6 +49,12 @@ export interface Deserializable { fromObject(d: Deserializer, o: T): void; } +/** + * Deserializes TypeDoc's JSON output back to {@link Reflection} instances. + * + * @group Common + * @summary Deserializes TypeDoc's JSON output + */ export class Deserializer { private deferred: Array<(project: ProjectReflection) => void> = []; private deserializers: DeserializerComponent[] = []; diff --git a/src/lib/serialization/serializer.ts b/src/lib/serialization/serializer.ts index f458826e1..faf8f8260 100644 --- a/src/lib/serialization/serializer.ts +++ b/src/lib/serialization/serializer.ts @@ -11,6 +11,12 @@ export interface SerializerEvents { end: [SerializeEvent]; } +/** + * Serializes TypeDoc's models to JSON + * + * @group Common + * @summary Serializes TypeDoc's models to JSON + */ export class Serializer extends EventDispatcher { /** * Triggered when the {@link Serializer} begins transforming a project. diff --git a/src/lib/utils/options/options.ts b/src/lib/utils/options/options.ts index cd996a286..8e8fca2c0 100644 --- a/src/lib/utils/options/options.ts +++ b/src/lib/utils/options/options.ts @@ -86,6 +86,9 @@ const optionSnapshots = new WeakMap< * 3. tsconfig-json (200) - Last config file reader, cannot specify the typedoc.json file to read. * 4. argv (300) - Read argv again since any options set there should override those set in config * files. + * + * @group Common + * @summary Contains all of TypeDoc's option declarations & values */ export class Options { private _readers: OptionsReader[] = []; diff --git a/static/style.css b/static/style.css index 983b90321..92073061e 100644 --- a/static/style.css +++ b/static/style.css @@ -852,6 +852,17 @@ a.tsd-index-link { margin-left: 0.25rem; vertical-align: text-top; } +/* + We need to be careful to target the arrow indicating whether the accordion + is open, but not any other SVGs included in the details element. +*/ +.tsd-accordion:not([open]) > .tsd-accordion-summary > svg:first-child, +.tsd-accordion:not([open]) > .tsd-accordion-summary > h1 > svg:first-child, +.tsd-accordion:not([open]) > .tsd-accordion-summary > h2 > svg:first-child, +.tsd-accordion:not([open]) > .tsd-accordion-summary > h3 > svg:first-child, +.tsd-accordion:not([open]) > .tsd-accordion-summary > h4 > svg:first-child { + transform: rotate(-90deg); +} .tsd-index-content > :not(:first-child) { margin-top: 0.75rem; }