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
24 changes: 20 additions & 4 deletions src/lib/boarding-types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import HighlightElement, {
HighlightElementHybridOptions,
} from "./core/highlight-element";
import { OverlayTopLevelOptions } from "./core/overlay";
import {BoardingExitReason, OverlayTopLevelOptions} from "./core/overlay";
import {
PopoverHybridOptions,
PopoverStepLevelOptions,
Expand Down Expand Up @@ -68,21 +68,37 @@ export interface BoardingOptions
* Simple event that triggers for boarding.start()
*/
onStart?: (element: HighlightElement) => void;
/**
* Event that will trigger when boarding is finished, after everything is cleaned up
*/
onFinish?: (reason: BoardingExitReason) => void;
/**
* Event that will trigger when a step is entered
*/
onStep?: (step: number) => void;
}

/** Identifier for different navigation actions relevant to an event */
export type StepNavigationInitiator = "next" | "prev" | "init" | "reset";

export interface BoardingStepDefinition extends HighlightElementHybridOptions {
/**
* Query selector representing the DOM Element
*/
element: string | HTMLElement;

/**
* A method that will run very early for the element to-be highlighted. The method will run right before `onNext` (or `onPrevious` when going backwards)
* A method that will run very early for the element to-be highlighted.
* The method will run right before `onNext` (or `onPrevious` when going backwards).
* If returning a promise, it will wait for it to resolve before moving into the step.
*
* Note: This method won't run for the first step when starting
* @param initiator either "next", "prev" or "init"
*/
prepareElement?: (initiator: "next" | "prev" | "init") => void;
prepareElement?: (initiator: StepNavigationInitiator) => void;
/**
* Event that will trigger when leaving a step
*/
cleanupElement?: (initiator: StepNavigationInitiator) => void;
/**
* Options representing popover for this step
*/
Expand Down
80 changes: 77 additions & 3 deletions src/lib/boarding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,25 +24,35 @@ type HighlightSelector = BoardingStepDefinition | string | HTMLElement;
enum MovementType {
Start,
Highlight,
CleanupNext,
PrepareNext,
Next,
CleanupPrevious,
PreparePrevious,
Previous,
CleanupReset,
}

type Movement =
| {
movement:
| MovementType.Start
| MovementType.Next
| MovementType.CleanupNext
| MovementType.PrepareNext
| MovementType.CleanupPrevious
| MovementType.PreparePrevious
| MovementType.Previous;
index: number;
}
| {
movement: MovementType.Highlight;
selector: HighlightSelector;
}
| {
movement: MovementType.CleanupReset;
immediate: boolean;
exitReason: BoardingExitReason;
};

/**
Expand All @@ -64,6 +74,7 @@ class Boarding {
private currentMovePrevented: Movement | false;

private overlay: Overlay;
private stepCleanupRequired: boolean = false;

constructor(options?: BoardingOptions) {
const {
Expand Down Expand Up @@ -137,6 +148,8 @@ class Boarding {
if (!this.steps || this.steps.length === 0) {
throw new Error("There are no steps defined to iterate");
}

this.stepCleanupRequired = true;
this.steps[index].prepareElement?.("init");
if (this.currentMovePrevented) {
return;
Expand All @@ -161,6 +174,7 @@ class Boarding {
? selector
: { element: selector };

this.stepCleanupRequired = true;
stepDefinition.prepareElement?.("init");
if (this.currentMovePrevented) {
return;
Expand Down Expand Up @@ -205,8 +219,8 @@ class Boarding {
* If preventMove was called, you can use this method to continue where the movement was stopped.
* It's a smart method that chooses the correct method from: `next`, `previous`, `start` and `highlight`
*/
public async continue() {
// setTimout foces the continue to always be executed async from the original (this is necessary, so a user can't make the mistake of calling preventMove and continue synchronously which would cause issues)
public continue() {
// setTimout foces the continue to always be executed from the original (this is necessary, so a user can't make the mistake of calling preventMove and continue synchronously which would cause issues)
setTimeout(() => {
if (this.currentMovePrevented === this.lastMovementRequested) {
// reset, we are continuing
Expand All @@ -220,18 +234,27 @@ class Boarding {
case MovementType.Highlight:
this.handleHighlight(this.lastMovementRequested.selector);
break;
case MovementType.CleanupNext:
this.next();
break;
case MovementType.PrepareNext:
this.handleNext();
break;
case MovementType.Next:
this.moveNext();
break;
case MovementType.CleanupPrevious:
this.previous();
break;
case MovementType.PreparePrevious:
this.handlePrevious();
break;
case MovementType.Previous:
this.movePrevious();
break;
case MovementType.CleanupReset:
this.reset(this.lastMovementRequested.immediate, this.lastMovementRequested.exitReason);
break;
}
} else {
console.warn(
Expand All @@ -257,12 +280,28 @@ class Boarding {
return;
}

if (this.stepCleanupRequired && (
this.lastMovementRequested?.movement !== MovementType.CleanupNext &&
this.lastMovementRequested?.movement !== MovementType.PrepareNext)) {
this.lastMovementRequested = {
movement: MovementType.CleanupNext,
index: this.currentStep,
};

this.stepCleanupRequired = false;
this.steps[this.currentStep]?.cleanupElement?.("next");
if (this.currentMovePrevented) {
return;
}
}

this.lastMovementRequested = {
movement: MovementType.PrepareNext,
index: this.currentStep,
};

// call prepareElement for coming element if available
this.stepCleanupRequired = true;
this.steps[this.currentStep + 1]?.prepareElement?.("next");
// check if prepareElement wants to stop
if (this.currentMovePrevented) {
Expand All @@ -281,12 +320,28 @@ class Boarding {
return;
}

if (this.stepCleanupRequired && (
this.lastMovementRequested?.movement !== MovementType.CleanupPrevious &&
this.lastMovementRequested?.movement !== MovementType.PreparePrevious)) {
this.lastMovementRequested = {
movement: MovementType.CleanupPrevious,
index: this.currentStep,
};

this.stepCleanupRequired = false;
this.steps[this.currentStep]?.cleanupElement?.("prev");
if (this.currentMovePrevented) {
return;
}
}

this.lastMovementRequested = {
movement: MovementType.PreparePrevious,
index: this.currentStep,
};

// call prepareElement for coming element if available
this.stepCleanupRequired = true;
this.steps[this.currentStep - 1]?.prepareElement?.("prev");
// check if prepareElement wants to stop
if (this.currentMovePrevented) {
Expand Down Expand Up @@ -316,6 +371,21 @@ class Boarding {
* @param exitReason report the reason reset is called to `onReset`. This string has no other functionallity and is purely of informational purpose inside `onReset`
*/
public reset(immediate = false, exitReason: BoardingExitReason = "cancel") {
if (this.stepCleanupRequired &&
this.lastMovementRequested?.movement !== MovementType.CleanupReset) {
this.lastMovementRequested = {
movement: MovementType.CleanupReset,
immediate: immediate,
exitReason: exitReason,
};

this.stepCleanupRequired = false;
this.steps[this.currentStep]?.cleanupElement?.("reset");
if (this.currentMovePrevented) {
return;
}
}

this.currentStep = 0;
this.isActivated = false;
this.overlay.clear(immediate, exitReason);
Expand All @@ -328,6 +398,7 @@ class Boarding {
// reset step tracking
this.lastMovementRequested = undefined;
this.currentMovePrevented = false;
this.options.onFinish?.(exitReason);
}

/**
Expand Down Expand Up @@ -456,6 +527,7 @@ class Boarding {
this.setStrictClickHandlingRules(nextElem);
this.overlay.highlight(nextElem);
this.currentStep += 1;
this.options.onStep?.(this.currentStep);
}

/**
Expand All @@ -471,6 +543,7 @@ class Boarding {
this.setStrictClickHandlingRules(previousElem);
this.overlay.highlight(previousElem);
this.currentStep -= 1;
this.options.onStep?.(this.currentStep);
}

/**
Expand All @@ -483,6 +556,7 @@ class Boarding {
this.isActivated = true;
this.setStrictClickHandlingRules(element);
this.overlay.highlight(element);
this.options.onStep?.(this.currentStep);
}

/**
Expand Down Expand Up @@ -697,7 +771,7 @@ class Boarding {
this.previous();
},
onCloseClick: () => {
this.reset(false, "cancel");
this.reset(false, "close-button");
},
});
}
Expand Down
5 changes: 3 additions & 2 deletions src/lib/core/overlay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ type OverlaySupportedSharedOptions = Pick<
"animate" | "padding" | "radius"
>;

/** Identifier for different exist reasons that causing onReset, such as cancel or finish */
export type BoardingExitReason = "cancel" | "finish";
/** Identifier for different exit reasons that causing onReset, such as cancel or finish */
export type BoardingExitReason = "cancel" | "close-button" | "finish";

/** The options of overlay that will come from the top-level */
export interface OverlayTopLevelOptions {
Expand Down Expand Up @@ -133,6 +133,7 @@ class Overlay {
/**
* Removes the overlay and cancel any listeners
* @param immediate immediately unmount overlay or animate out
* @param exitReason the reason for clearing the overlay, such as cancel or finish
*/
public clear(immediate = false, exitReason: BoardingExitReason) {
// Callback for when overlay is about to be reset
Expand Down