From 3238d9bea5d1cd40532cfc042c515a6686845679 Mon Sep 17 00:00:00 2001 From: binrysearch Date: Sat, 29 Jun 2024 19:02:51 +0100 Subject: [PATCH] Add Hints APIs and the remaining methods --- src/index.ts | 4 + src/packages/hint/hint.ts | 226 ++++++++++++++++++++++++++++++++--- src/packages/hint/remove.ts | 6 - src/packages/hint/render.ts | 22 +--- src/packages/hint/tooltip.ts | 11 ++ src/util/DOMEvent.ts | 1 + 6 files changed, 226 insertions(+), 44 deletions(-) diff --git a/src/index.ts b/src/index.ts index 1b0152d3e..8cb07e684 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ import { version } from "../package.json"; +import { Hint } from "./packages/hint/hint"; import { Tour } from "./packages/tour"; /** @@ -12,6 +13,9 @@ const introJs = { tour: (elementOrSelector?: string | HTMLElement) => new Tour(elementOrSelector), + hint: (elementOrSelector?: string | HTMLElement) => + new Hint(elementOrSelector), + /** * Current Intro.js version */ diff --git a/src/packages/hint/hint.ts b/src/packages/hint/hint.ts index 2c400ff65..631df6633 100644 --- a/src/packages/hint/hint.ts +++ b/src/packages/hint/hint.ts @@ -1,11 +1,17 @@ import { Package } from "../package"; import { getDefaultHintOptions, HintOptions } from "./option"; -import { HintItem } from "./hintItem"; +import { fetchHintItems, HintItem } from "./hintItem"; import { setOption, setOptions } from "src/option"; import isFunction from "../../util/isFunction"; -import debounce from "src/util/debounce"; +import debounce from "../../util/debounce"; import { reAlignHints } from "./position"; import DOMEvent from "src/util/DOMEvent"; +import { getContainerElement } from "src/util/containerElement"; +import { renderHints } from "./render"; +import { hideHint, hideHints } from "./hide"; +import { showHint, showHints } from "./show"; +import { removeHint, removeHints } from "./remove"; +import { showHintDialog } from "./tooltip"; type hintsAddedCallback = (this: Hint) => void | Promise; type hintClickCallback = ( @@ -20,18 +26,35 @@ export class Hint implements Package { private _hints: HintItem[] = []; private readonly _targetElement: HTMLElement; private _options: HintOptions; + private readonly callbacks: { hintsAdded?: hintsAddedCallback; hintClick?: hintClickCallback; hintClose?: hintCloseCallback; } = {}; + + // Event handlers private _hintsAutoRefreshFunction?: () => void; - public constructor(targetElement: HTMLElement) { - this._targetElement = targetElement; - this._options = getDefaultHintOptions(); + /** + * Create a new Hint instance + * @param elementOrSelector Optional target element or CSS query to start the Hint on + * @param options Optional Hint options + */ + public constructor( + elementOrSelector?: string | HTMLElement, + options?: Partial + ) { + this._targetElement = getContainerElement(elementOrSelector); + this._options = options + ? setOptions(this._options, options) + : getDefaultHintOptions(); } + /** + * Get the callback function for the provided callback name + * @param callbackName The name of the callback + */ callback( callbackName: K ): (typeof this.callbacks)[K] | undefined { @@ -42,28 +65,141 @@ export class Hint implements Package { return undefined; } + /** + * Get the target element for the Hint + */ getTargetElement(): HTMLElement { return this._targetElement; } + /** + * Get the Hint items + */ getHints(): HintItem[] { return this._hints; } + /** + * Get the Hint item for the provided step ID + * @param stepId The step ID + */ getHint(stepId: number): HintItem | undefined { return this._hints[stepId]; } + /** + * Set the Hint items + * @param hints The Hint items + */ setHints(hints: HintItem[]): this { this._hints = hints; return this; } + /** + * Add a Hint item + * @param hint The Hint item + */ addHint(hint: HintItem): this { this._hints.push(hint); return this; } + /** + * Render hints on the page + */ + async render(): Promise { + if (!this.isActive()) { + return this; + } + + fetchHintItems(this); + await renderHints(this); + return this; + } + + /** + * @deprecated renderHints() is deprecated, please use render() instead + */ + async addHints() { + return this.render(); + } + + /** + * Hide a specific hint on the page + * @param stepId The hint step ID + */ + async hideHint(stepId: number) { + await hideHint(this, stepId); + return this; + } + + /** + * Hide all hints on the page + */ + async hideHints() { + await hideHints(this); + return this; + } + + /** + * Show a specific hint on the page + * @param stepId The hint step ID + */ + showHint(stepId: number) { + showHint(stepId); + return this; + } + + /** + * Show all hints on the page + */ + async showHints() { + await showHints(this); + return this; + } + + /** + * Destroys and removes all hint elements on the page + * Useful when you want to destroy the elements and add them again (e.g. a modal or popup) + */ + destroy() { + removeHints(this); + return this; + } + + /** + * @deprecated removeHints() is deprecated, please use destroy() instead + */ + removeHints() { + this.destroy(); + return this; + } + + /** + * Remove one single hint element from the page + * Useful when you want to destroy the element and add them again (e.g. a modal or popup) + * Use removeHints if you want to remove all elements. + * + * @param stepId The hint step ID + */ + removeHint(stepId: number) { + removeHint(stepId); + return this; + } + + /** + * Show hint dialog for a specific hint + * @param stepId The hint step ID + */ + async showHintDialog(stepId: number) { + await showHintDialog(this, stepId); + return this; + } + + /** + * Enable hint auto refresh on page scroll and resize for hints + */ enableHintAutoRefresh(): this { const hintAutoRefreshInterval = this.getOption("hintAutoRefreshInterval"); if (hintAutoRefreshInterval >= 0) { @@ -73,14 +209,19 @@ export class Hint implements Package { ); DOMEvent.on(window, "scroll", this._hintsAutoRefreshFunction, true); + DOMEvent.on(window, "resize", this._hintsAutoRefreshFunction, true); } return this; } + /** + * Disable hint auto refresh on page scroll and resize for hints + */ disableHintAutoRefresh(): this { if (this._hintsAutoRefreshFunction) { DOMEvent.off(window, "scroll", this._hintsAutoRefreshFunction, true); + DOMEvent.on(window, "resize", this._hintsAutoRefreshFunction, true); this._hintsAutoRefreshFunction = undefined; } @@ -88,51 +229,90 @@ export class Hint implements Package { return this; } + /** + * Get specific Hint option + * @param key The option key + */ getOption(key: K): HintOptions[K] { return this._options[key]; } + /** + * Set Hint options + * @param partialOptions Hint options + */ setOptions(partialOptions: Partial): this { this._options = setOptions(this._options, partialOptions); return this; } + /** + * Set specific Hint option + * @param key Option key + * @param value Option value + */ setOption(key: K, value: HintOptions[K]): this { this._options = setOption(this._options, key, value); return this; } + /** + * Clone the Hint instance + */ clone(): ThisType { - return new Hint(this._targetElement); + return new Hint(this._targetElement, this._options); } + /** + * Returns true if the Hint is active + */ isActive(): boolean { return this.getOption("isActive"); } - render(): Promise { - throw new Error("Method not implemented."); - } - - onhintsadded(providedCallback: hintsAddedCallback) { - if (isFunction(providedCallback)) { - this.callbacks.hintsAdded = providedCallback; - } else { + onHintsAdded(providedCallback: hintsAddedCallback) { + if (!isFunction(providedCallback)) { throw new Error("Provided callback for onhintsadded was not a function."); } + + this.callbacks.hintsAdded = providedCallback; return this; } - onhintclick(providedCallback: hintClickCallback) { - if (isFunction(providedCallback)) { - this.callbacks.hintClick = providedCallback; - } else { + /** + * @deprecated onhintsadded is deprecated, please use onHintsAdded instead + * @param providedCallback callback function + */ + onhintsadded(providedCallback: hintsAddedCallback) { + this.onHintsAdded(providedCallback); + } + + /** + * Callback for when hint items are clicked + * @param providedCallback callback function + */ + onHintClick(providedCallback: hintClickCallback) { + if (!isFunction(providedCallback)) { throw new Error("Provided callback for onhintclick was not a function."); } + + this.callbacks.hintClick = providedCallback; return this; } - onhintclose(providedCallback: hintCloseCallback) { + /** + * @deprecated onhintclick is deprecated, please use onHintClick instead + * @param providedCallback + */ + onhintclick(providedCallback: hintClickCallback) { + this.onHintClick(providedCallback); + } + + /** + * Callback for when hint items are closed + * @param providedCallback callback function + */ + onHintClose(providedCallback: hintCloseCallback) { if (isFunction(providedCallback)) { this.callbacks.hintClose = providedCallback; } else { @@ -140,4 +320,12 @@ export class Hint implements Package { } return this; } + + /** + * @deprecated onhintclose is deprecated, please use onHintClose instead + * @param providedCallback + */ + onhintclose(providedCallback: hintCloseCallback) { + this.onHintClose(providedCallback); + } } diff --git a/src/packages/hint/remove.ts b/src/packages/hint/remove.ts index 5ede4cb05..945f37403 100644 --- a/src/packages/hint/remove.ts +++ b/src/packages/hint/remove.ts @@ -1,8 +1,5 @@ import { Hint } from "./hint"; import { dataStepAttribute } from "./dataAttributes"; -import DOMEvent from "src/util/DOMEvent"; -import { removeHintTooltip } from "./tooltip"; -import { reAlignHints } from "./position"; import { hintElement, hintElements } from "./selector"; /** @@ -22,9 +19,6 @@ export function removeHints(hint: Hint) { removeHint(parseInt(step, 10)); } - DOMEvent.off(document, "click", removeHintTooltip, hint, false); - DOMEvent.off(window, "resize", reAlignHints, hint, true); - hint.disableHintAutoRefresh(); } diff --git a/src/packages/hint/render.ts b/src/packages/hint/render.ts index 140da09ec..28632eb19 100644 --- a/src/packages/hint/render.ts +++ b/src/packages/hint/render.ts @@ -1,7 +1,6 @@ import { queryElement, queryElementByClassName } from "../../util/queryElement"; import { Hint } from "./hint"; -import { fetchHintItems, HintPosition } from "./hintItem"; -import DOMEvent from "../../util/DOMEvent"; +import { HintPosition } from "./hintItem"; import { fixedHintClassName, hintClassName, @@ -15,8 +14,8 @@ import { dataStepAttribute } from "./dataAttributes"; import setAnchorAsButton from "../../util/setAnchorAsButton"; import { addClass } from "../../util/className"; import isFixed from "../../util/isFixed"; -import { alignHintPosition, reAlignHints } from "./position"; -import { removeHintTooltip, showHintDialog } from "./tooltip"; +import { alignHintPosition } from "./position"; +import { showHintDialog } from "./tooltip"; /** * Returns an event handler unique to the hint iteration @@ -109,18 +108,3 @@ export async function renderHints(hint: Hint) { hint.enableHintAutoRefresh(); } - -/** - * Render hints on the page - * @api private - */ -export async function render(hint: Hint): Promise { - fetchHintItems(hint); - - await renderHints(hint); - - DOMEvent.on(document, "click", removeHintTooltip, hint, false); - DOMEvent.on(window, "resize", reAlignHints, hint, true); - - return true; -} diff --git a/src/packages/hint/tooltip.ts b/src/packages/hint/tooltip.ts index 68a74160c..ff2e2e459 100644 --- a/src/packages/hint/tooltip.ts +++ b/src/packages/hint/tooltip.ts @@ -14,6 +14,10 @@ import { setClass } from "../../util/className"; import { hideHint } from "./hide"; import { setPositionRelativeTo } from "../../util/setPositionRelativeTo"; import { placeTooltip } from "../../packages/tooltip"; +import DOMEvent from "src/util/DOMEvent"; + +// The hint close function used when the user clicks outside the hint +let _hintCloseFunction: () => void | undefined; /** * Removes open hint (tooltip hint) @@ -136,4 +140,11 @@ export async function showHintDialog(hint: Hint, stepId: number) { hint.getOption("autoPosition"), hintItem.tooltipClass ?? hint.getOption("tooltipClass") ); + + _hintCloseFunction = () => { + removeHintTooltip(); + DOMEvent.off(document, "click", _hintCloseFunction, false); + }; + + DOMEvent.on(document, "click", _hintCloseFunction, false); } diff --git a/src/util/DOMEvent.ts b/src/util/DOMEvent.ts index deeb3f7ac..3a58c6c39 100644 --- a/src/util/DOMEvent.ts +++ b/src/util/DOMEvent.ts @@ -11,6 +11,7 @@ interface Events { keydown: KeyboardEvent; resize: Event; scroll: Event; + click: MouseEvent; } type Listener = (e: T) => void | undefined | string | Promise;