Skip to content

Commit

Permalink
Expand/collapse sections on the page together
Browse files Browse the repository at this point in the history
Also make the arrows work properly without JS

As discussed in #2335
  • Loading branch information
Gerrit0 committed Sep 29, 2024
1 parent d4ee5ad commit 4a3fa9e
Show file tree
Hide file tree
Showing 12 changed files with 84 additions and 42 deletions.
1 change: 1 addition & 0 deletions .config/typedoc.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"treatWarningsAsErrors": false,
"categorizeByGroup": false,
"categoryOrder": ["Reflections", "Types", "Comments", "*"],
"groupOrder": ["Common", "*"],
"validation": {
"notExported": true,
"invalidLink": true,
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
9 changes: 0 additions & 9 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down
3 changes: 3 additions & 0 deletions src/lib/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
3 changes: 3 additions & 0 deletions src/lib/converter/converter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Application, ConverterEvents> {
/** @internal */
Expand Down
3 changes: 3 additions & 0 deletions src/lib/output/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,9 @@ export interface RendererEvents {
* * {@link Renderer.EVENT_PREPARE_INDEX}<br>
* 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<Application, RendererEvents> {
private themes = new Map<string, new (renderer: Renderer) => Theme>([
Expand Down
Original file line number Diff line number Diff line change
@@ -1,54 +1,67 @@
import { Component, IComponentOptions } from "../Component.js";
import { storage } from "../utils/storage.js";

const ACCORDION_INSTANCES = new Map<string, AccordionImpl>();

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<HTMLDetailsElement> {
/**
* 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 <summary> 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);
}
}
2 changes: 1 addition & 1 deletion src/lib/output/themes/default/partials/navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ export function pageNavigation(context: DefaultThemeRenderContext, props: PageEv
if (section.title) {
sections.push(
<details open class="tsd-accordion tsd-page-navigation-section">
<summary class="tsd-accordion-summary" data-key={`tsd-otp-${section.title}`}>
<summary class="tsd-accordion-summary" data-key={`section-${section.title}`}>
{context.icons.chevronDown()}
{section.title}
</summary>
Expand Down
6 changes: 6 additions & 0 deletions src/lib/serialization/deserializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ export interface Deserializable<T> {
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[] = [];
Expand Down
6 changes: 6 additions & 0 deletions src/lib/serialization/serializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<SerializerEvents> {
/**
* Triggered when the {@link Serializer} begins transforming a project.
Expand Down
3 changes: 3 additions & 0 deletions src/lib/utils/options/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[] = [];
Expand Down
11 changes: 11 additions & 0 deletions static/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down

0 comments on commit 4a3fa9e

Please sign in to comment.