Skip to content
Merged

Updates #1203

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
15 changes: 9 additions & 6 deletions src/components/Floater.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -217,21 +217,24 @@ export default function JoyrideFloater(props: FloaterProps) {
beaconMiddlewareRef.current = beaconFloating.middlewareData;

useEffect(() => {
const ref = tooltipFloating.refs.reference.current;
const floating = tooltipFloating.refs.floating.current;
const { floating, reference } = tooltipFloating.elements;

if (!ref || !floating || lifecycle !== LIFECYCLE.TOOLTIP) {
if (!reference || !floating || lifecycle !== LIFECYCLE.TOOLTIP) {
return undefined;
}

return autoUpdate(ref, floating, tooltipFloating.update, step.floatingOptions?.autoUpdate);
return autoUpdate(
reference,
floating,
tooltipFloating.update,
step.floatingOptions?.autoUpdate,
);
Comment thread
gilbarbara marked this conversation as resolved.
}, [
lifecycle,
tooltipFloating.refs.reference,
tooltipFloating.refs.floating,
tooltipFloating.update,
step.floatingOptions?.autoUpdate,
step.target,
tooltipFloating.elements,
]);

// Wire reference element to both floating instances
Expand Down
22 changes: 3 additions & 19 deletions src/components/Overlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,11 @@ export default function JoyrideOverlay(props: OverlayProps) {
spotlightPadding,
scrolling || waiting,
);
const previousLifecycleRef = useRef(lifecycle);
const overlayRef = useRef<HTMLDivElement>(null);

const [showSpotlight, setShowSpotlight] = useState(false);
const showSpotlight =
(lifecycle === LIFECYCLE.TOOLTIP || lifecycle === LIFECYCLE.TOOLTIP_BEFORE) &&
placement !== 'center';
Comment thread
gilbarbara marked this conversation as resolved.
const [spotlightReady, setSpotlightReady] = useState(false);

const container = portalElement ? (overlayRef.current?.offsetParent as HTMLElement | null) : null;
Expand All @@ -67,23 +68,6 @@ export default function JoyrideOverlay(props: OverlayProps) {
} as CSSProperties;
}, [overlayHeight, styles.overlay]);

useEffect(() => {
const previousLifecycle = previousLifecycleRef.current;

previousLifecycleRef.current = lifecycle;

if (
(lifecycle === LIFECYCLE.TOOLTIP || lifecycle === LIFECYCLE.TOOLTIP_BEFORE) &&
previousLifecycle !== LIFECYCLE.TOOLTIP &&
previousLifecycle !== LIFECYCLE.TOOLTIP_BEFORE &&
placement !== 'center'
) {
setShowSpotlight(true);
} else if (lifecycle === LIFECYCLE.COMPLETE && previousLifecycle !== LIFECYCLE.COMPLETE) {
setShowSpotlight(false);
}
}, [lifecycle, placement]);

const showCutout = showSpotlight && !scrolling && !waiting;

useEffect(() => {
Expand Down
4 changes: 2 additions & 2 deletions src/components/Tooltip/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export default function Tooltip(props: TooltipProps) {
const handleClickBack = (event: MouseEvent<HTMLElement>) => {
event.preventDefault();

controls.prev();
controls.prev(ORIGIN.BUTTON_BACK);
};

const handleClickClose = (event: MouseEvent<HTMLElement>) => {
Expand All @@ -44,7 +44,7 @@ export default function Tooltip(props: TooltipProps) {
return;
}

controls.next();
controls.next(ORIGIN.BUTTON_PRIMARY);
};

const handleClickSkip = (event: MouseEvent<HTMLElement>) => {
Expand Down
4 changes: 2 additions & 2 deletions src/components/TourRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export default function TourRenderer({

if (event.key === 'Escape' && step.dismissKeyAction) {
if (step.dismissKeyAction === 'next') {
controls.next();
controls.next(ORIGIN.KEYBOARD);
} else {
controls.close(ORIGIN.KEYBOARD);
}
Expand All @@ -90,7 +90,7 @@ export default function TourRenderer({
if (step?.overlayClickAction === 'close') {
controls.close(ORIGIN.OVERLAY);
} else if (step?.overlayClickAction === 'next') {
controls.next();
controls.next(ORIGIN.OVERLAY);
}
}, [controls, step?.overlayClickAction]);

Expand Down
6 changes: 4 additions & 2 deletions src/hooks/useControls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export default function useControls(

const info = () => omit(store.current.getSnapshot(), 'positioned');

const next = () => {
const next = (origin?: Origin | null) => {
const { index, size, status } = getState();

if (status !== STATUS.RUNNING) {
Expand All @@ -81,6 +81,7 @@ export default function useControls(
action: ACTIONS.NEXT,
index: getUpdatedIndex(index + 1, size),
lifecycle: LIFECYCLE.COMPLETE,
origin,
positioned: false,
scrolling: false,
waiting: false,
Expand All @@ -103,7 +104,7 @@ export default function useControls(
});
};

const previous = () => {
const previous = (origin?: Origin | null) => {
const { index, size, status } = getState();

if (status !== STATUS.RUNNING) {
Expand All @@ -114,6 +115,7 @@ export default function useControls(
action: ACTIONS.PREV,
index: getUpdatedIndex(index - 1, size),
lifecycle: LIFECYCLE.COMPLETE,
origin,
positioned: false,
scrolling: false,
waiting: false,
Expand Down
6 changes: 3 additions & 3 deletions src/hooks/useLifecycleEffect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import type { EmitEvent } from '~/hooks/useEventEmitter';
import type { AddFailure, MergedProps } from '~/hooks/useTourEngine';
import { ACTIONS, EVENTS, LIFECYCLE, STATUS } from '~/literals';
import { treeChanges } from '~/modules/changes';
import { getElement, isElementVisible } from '~/modules/dom';
import { log, needsScrolling, shouldHideBeacon } from '~/modules/helpers';
import { getMergedStep } from '~/modules/step';
import { getElement, isElementVisible, needsScrolling } from '~/modules/dom';
import { log } from '~/modules/helpers';
import { getMergedStep, shouldHideBeacon } from '~/modules/step';
import createStore from '~/modules/store';
import type { StoreState } from '~/modules/store';

Expand Down
3 changes: 2 additions & 1 deletion src/literals/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,10 @@ export const LIFECYCLE = {
} as const;

export const ORIGIN = {
BUTTON_BACK: 'button_back',
BUTTON_CLOSE: 'button_close',
BUTTON_SKIP: 'button_skip',
BUTTON_PRIMARY: 'button_primary',
BUTTON_SKIP: 'button_skip',
KEYBOARD: 'keyboard',
OVERLAY: 'overlay',
} as const;
Expand Down
33 changes: 32 additions & 1 deletion src/modules/dom.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
import scroll from 'scroll';
import scrollParent from 'scrollparent';

import type { StepTarget } from '~/types';
import { LIFECYCLE } from '~/literals';

import type { Lifecycle, StepMerged, StepTarget } from '~/types';

interface NeedsScrollingOptions {
isFirstStep: boolean;
scrollToFirstStep: boolean;
step: StepMerged;
target: HTMLElement | null;
targetLifecycle?: Lifecycle;
}

export function canUseDOM() {
return !!(typeof window !== 'undefined' && window.document?.createElement);
Expand Down Expand Up @@ -294,6 +304,27 @@ export function isElementVisible(element: HTMLElement): boolean {
return true;
}

export function needsScrolling(options: NeedsScrollingOptions): boolean {
const { isFirstStep, scrollToFirstStep, step, target, targetLifecycle } = options;

if (
step.skipScroll ||
(isFirstStep && !scrollToFirstStep && targetLifecycle !== LIFECYCLE.TOOLTIP) ||
step.placement === 'center'
) {
return false;
}

const parent = (target?.isConnected ? getScrollParent(target) : scrollDocument()) as Element;
const isCustomScrollParent = parent ? !parent.isSameNode(scrollDocument()) : false;

if ((step.isFixed || hasPosition(target)) && !isCustomScrollParent) {
return false;
}

return parent.scrollHeight > parent.clientHeight;
}

export function scrollDocument(): Element | HTMLElement {
return document.scrollingElement ?? document.documentElement;
}
Expand Down
68 changes: 1 addition & 67 deletions src/modules/helpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,7 @@ import innerText from 'react-innertext';
import deepmergeFactory from '@fastify/deepmerge';
import is from 'is-lite';

import { ACTIONS, LIFECYCLE } from '~/literals';

import type {
Actions,
AnyObject,
Lifecycle,
NarrowPlainObject,
PlainObject,
Simplify,
State,
Step,
StepMerged,
} from '~/types';

import { getScrollParent, hasPosition, scrollDocument } from './dom';
import type { AnyObject, NarrowPlainObject, PlainObject, Simplify } from '~/types';

type RemoveType<TObject, TExclude = undefined> = {
[Key in keyof TObject as TObject[Key] extends TExclude ? never : Key]: TObject[Key];
Expand All @@ -29,14 +15,6 @@ interface GetReactNodeTextOptions {
steps?: number;
}

interface NeedsScrollingOptions {
isFirstStep: boolean;
scrollToFirstStep: boolean;
step: StepMerged;
target: HTMLElement | null;
targetLifecycle?: Lifecycle;
}

/**
* Remove properties with undefined value from an object
*/
Expand Down Expand Up @@ -94,18 +72,6 @@ export function getReactNodeText(input: ReactNode, options: GetReactNodeTextOpti
return text;
}

/**
* Convert hex to RGB
*/
export function hexToRGB(hex: string): Array<number> {
const shorthandRegex = /^#?([\da-f])([\da-f])([\da-f])$/i;
const properHex = hex.replace(shorthandRegex, (_m, r, g, b) => r + r + g + g + b + b);

const result = /^#?([\da-f]{2})([\da-f]{2})([\da-f]{2})/i.exec(properHex);

return result ? [parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)] : [];
}

/**
* Log method calls if debug is enabled
*/
Expand Down Expand Up @@ -141,27 +107,6 @@ export function mergeProps<TDefaultProps extends PlainObject<any>, TProps extend
>;
}

export function needsScrolling(options: NeedsScrollingOptions): boolean {
const { isFirstStep, scrollToFirstStep, step, target, targetLifecycle } = options;

if (
step.skipScroll ||
(isFirstStep && !scrollToFirstStep && targetLifecycle !== LIFECYCLE.TOOLTIP) ||
step.placement === 'center'
) {
return false;
}

const parent = (target?.isConnected ? getScrollParent(target) : scrollDocument()) as Element;
const isCustomScrollParent = parent ? !parent.isSameNode(scrollDocument()) : false;

if ((step.isFixed || hasPosition(target)) && !isCustomScrollParent) {
return false;
}

return parent.scrollHeight > parent.clientHeight;
}

/**
* A function that does nothing.
*/
Expand Down Expand Up @@ -275,17 +220,6 @@ export function replaceLocaleContent(input: ReactNode, step: number, steps: numb
return input;
}

/**
* Decide if the step shouldn't skip the beacon
*/
export function shouldHideBeacon(step: Step, state: State, continuous: boolean): boolean {
const { action } = state;

const withContinuous = continuous && ([ACTIONS.PREV, ACTIONS.NEXT] as Actions[]).includes(action);

return step.skipBeacon || step.placement === 'center' || withContinuous;
}

/**
* Sort object keys
*/
Expand Down
14 changes: 14 additions & 0 deletions src/modules/step.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import is from 'is-lite';

import { defaultFloatingOptions, defaultLocale, defaultOptions, defaultStep } from '~/defaults';
import { ACTIONS } from '~/literals';
import getStyles from '~/styles';

import type {
Actions,
FloatingOptions,
Locale,
Options,
Props,
SpotlightPadding,
State,
Step,
StepMerged,
} from '~/types';
Expand Down Expand Up @@ -109,6 +112,17 @@ export function normalizeSpotlightPadding(
};
}

/**
* Decide if the step shouldn't skip the beacon
Comment thread
gilbarbara marked this conversation as resolved.
*/
export function shouldHideBeacon(step: Step, state: State, continuous: boolean): boolean {
const { action } = state;

const withContinuous = continuous && ([ACTIONS.PREV, ACTIONS.NEXT] as Actions[]).includes(action);

return step.skipBeacon || step.placement === 'center' || withContinuous;
}

/**
* Validate if a step is valid
*/
Expand Down
14 changes: 13 additions & 1 deletion src/styles.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
import { canUseDOM } from '~/modules/dom';
import { deepMerge, hexToRGB } from '~/modules/helpers';
import { deepMerge } from '~/modules/helpers';

import type { Props, StepMerged, Styles } from '~/types';

/**
* Convert hex to RGB
*/
export function hexToRGB(hex: string): Array<number> {
const shorthandRegex = /^#?([\da-f])([\da-f])([\da-f])$/i;
const properHex = hex.replace(shorthandRegex, (_m, r, g, b) => r + r + g + g + b + b);

const result = /^#?([\da-f]{2})([\da-f]{2})([\da-f]{2})/i.exec(properHex);

return result ? [parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)] : [];
}

const buttonReset = {
backgroundColor: 'transparent',
border: 0,
Expand Down
4 changes: 2 additions & 2 deletions src/types/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ export type Controls = {
/** Get the current tour state. */
info: () => State;
/** Advance to the next step. */
next: () => void;
next: (origin?: Origin | null) => void;
/** Open the tooltip for the current step. */
open: () => void;
/** Go back to the previous step. */
prev: () => void;
prev: (origin?: Origin | null) => void;
/** Reset the tour. Optionally restart from the beginning. */
reset: (restart?: boolean) => void;
/** Skip the tour entirely. */
Expand Down
2 changes: 1 addition & 1 deletion test/__fixtures__/CustomOptions.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Joyride, type Props, STATUS, type Step } from '~/index';
import { hexToRGB } from '~/modules/helpers';
import { hexToRGB } from '~/styles';

interface CustomOptionsProps extends Omit<Props, 'steps'> {
finishedCallback: () => void;
Expand Down
Loading
Loading