Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/packages/tour/option.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { TooltipPosition } from "../../packages/tooltip";
import { TourStep, ScrollTo } from "./steps";
import { ThemeType } from "./theme";

export interface TourOptions {
steps: Partial<TourStep>[];
Expand Down Expand Up @@ -74,6 +75,8 @@ export interface TourOptions {
progressBarAdditionalClass: string;
/* Optional property to determine if content should be rendered as HTML */
tooltipRenderAsHtml?: boolean;
/* Theme for the tour - light, dark, auto */
theme?: ThemeType;
}

export function getDefaultTourOptions(): TourOptions {
Expand Down Expand Up @@ -116,5 +119,6 @@ export function getDefaultTourOptions(): TourOptions {
buttonClass: "introjs-button",
progressBarAdditionalClass: "",
tooltipRenderAsHtml: true,
theme: "auto",
};
}
113 changes: 113 additions & 0 deletions src/packages/tour/theme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// theme.ts
export type ThemeType = "light" | "dark" | "auto";

export interface ThemeOptions {
theme?: ThemeType;
root?: HTMLElement;
}

export class Theme {
private _theme: "light" | "dark";
private _root: HTMLElement;
private mqlDark: MediaQueryList | null = null;
private boundHandleSystemThemeChange: () => void;
private themeType: ThemeType;

constructor(options: ThemeOptions = {}) {
this._root = options.root ?? document.documentElement;
this.themeType = options.theme ?? "auto";

this.boundHandleSystemThemeChange = this.handleSystemThemeChange.bind(this);

this._theme = this.resolveTheme(this.themeType);
this.applyToRoot();

if (this.themeType === "auto") {
this.mqlDark = window.matchMedia("(prefers-color-scheme: dark)");
if ("addEventListener" in this.mqlDark) {
this.mqlDark.addEventListener(
"change",
this.boundHandleSystemThemeChange
);
} else {
(this.mqlDark as any).addListener(this.boundHandleSystemThemeChange);
}
}
}

private resolveTheme(theme: ThemeType): "light" | "dark" {
if (theme === "auto") {
return window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light";
}
return theme;
}

private handleSystemThemeChange() {
if (this.themeType === "auto") {
this._theme = this.resolveTheme("auto");
this.applyToRoot();
}
}

private applyToRoot() {
this._root.classList.remove("introjs-light", "introjs-dark");
this._root.classList.add(`introjs-${this._theme}`);
}

/** Change theme manually */
public setTheme(themeType: ThemeType) {
this.themeType = themeType;
this._theme = this.resolveTheme(themeType);
this.applyToRoot();
}

/** Update the root element (useful for tour reopen) */
public setRoot(root: HTMLElement) {
if (this._root !== root) {
this._root = root;
this.applyToRoot();
}
}

/** Optional cleanup */
public destroy() {
if (this.mqlDark) {
if ("removeEventListener" in this.mqlDark) {
this.mqlDark.removeEventListener(
"change",
this.boundHandleSystemThemeChange
);
} else {
(this.mqlDark as any).removeListener(this.boundHandleSystemThemeChange);
}
this.mqlDark = null;
}
}

public get value(): "light" | "dark" {
return this._theme;
}
}

let currentTheme: Theme | null = null;

export function getTheme(): Theme {
if (!currentTheme) {
currentTheme = new Theme({ theme: "auto" });
}
return currentTheme;
}

export function applyTheme(options: ThemeOptions = {}) {
if (!currentTheme) {
currentTheme = new Theme(options);
} else {
currentTheme.setTheme(options.theme ?? "auto");
if (options.root) {
currentTheme.setRoot(options.root);
}
}
return currentTheme;
}
8 changes: 8 additions & 0 deletions src/packages/tour/tour.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import onKeyDown from "./onKeyDown";
import dom from "../dom";
import { TourRoot } from "./components/TourRoot";
import { FloatingElement } from "./components/FloatingElement";
import { applyTheme } from "./theme";

/**
* Intro.js Tour class
Expand Down Expand Up @@ -446,13 +447,20 @@ export class Tour implements Package<TourOptions> {
async start() {
if (await start(this)) {
this.createRoot();
this.initTheme();
this.enableKeyboardNavigation();
this.enableRefreshOnResize();
}

return this;
}

private initTheme() {
if (!this._root || !(this._root instanceof HTMLElement)) return;

applyTheme({ root: this._root, theme: this._options.theme });
}

/**
* Exit the tour
* @param {boolean} force whether to force exit the tour
Expand Down
Loading