Skip to content

Commit

Permalink
feat(many-targets): allow to spotlight many targets
Browse files Browse the repository at this point in the history
  • Loading branch information
Aliath committed Sep 13, 2021
1 parent 3063acd commit ecae293
Show file tree
Hide file tree
Showing 9 changed files with 79 additions and 53 deletions.
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"javascript.validate.enable": false,
"typescript.validate.enable": false
}
2 changes: 1 addition & 1 deletion defs/globals.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export interface StepProps {
spotlightClicks?: boolean;
spotlightPadding?: number;
styles?: Object;
target: string | HTMLElement;
target: string | HTMLElement[];
title?: ReactNode;
tooltipComponent?: ReactNode;
}
Expand Down
11 changes: 6 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-joyride",
"version": "2.3.1",
"name": "react-extended-joyride",
"version": "2.3.2",
"description": "Create guided tours for your apps",
"author": "Gil Barbara <gilbarbara@gmail.com>",
"repository": {
Expand Down Expand Up @@ -125,16 +125,17 @@
"format": "prettier \"**/*.{js,jsx,ts,tsx}\" --write",
"validate": "npm run lint && npm run test:coverage && flow && npm run build && npm run size",
"size": "size-limit",
"prepublishOnly": "npm run validate"
"prepublishOnly": "npm run validate",
"prepare": "npm run build"
},
"size-limit": [
{
"path": "./es/index.js",
"limit": "32 kB"
"limit": "33 kB"
},
{
"path": "./lib/index.js",
"limit": "32 kB"
"limit": "33 kB"
}
],
"husky": {
Expand Down
69 changes: 40 additions & 29 deletions src/components/Overlay.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import treeChanges from 'tree-changes';
import {
getClientRect,
getDocumentHeight,
getElement,
getElements,
getElementPosition,
getScrollParent,
hasCustomScrollParent,
Expand Down Expand Up @@ -42,15 +42,16 @@ export default class JoyrideOverlay extends React.Component {

componentDidMount() {
const { debug, disableScrolling, disableScrollParentFix, target } = this.props;
const element = getElement(target);
const elements = getElements(target);
const firstElement = elements[0];

this.scrollParent = getScrollParent(element, disableScrollParentFix, true);
this.scrollParent = getScrollParent(firstElement, disableScrollParentFix, true);
this._isMounted = true;

/* istanbul ignore else */
if (!disableScrolling) {
/* istanbul ignore else */
if (process.env.NODE_ENV === 'development' && hasCustomScrollParent(element, true)) {
if (process.env.NODE_ENV === 'development' && hasCustomScrollParent(firstElement, true)) {
log({
title: 'step has a custom scroll parent and can cause trouble with scrolling',
data: [{ key: 'parent', value: this.scrollParent }],
Expand Down Expand Up @@ -103,33 +104,39 @@ export default class JoyrideOverlay extends React.Component {
const { showSpotlight } = this.state;
const { disableScrollParentFix, spotlightClicks, spotlightPadding, styles, target } =
this.props;
const element = getElement(target);
const elementRect = getClientRect(element);
const isFixedTarget = hasPosition(element);
const top = getElementPosition(element, spotlightPadding, disableScrollParentFix);

return {
...(isLegacy() ? styles.spotlightLegacy : styles.spotlight),
height: Math.round(elementRect.height + spotlightPadding * 2),
left: Math.round(elementRect.left - spotlightPadding),
opacity: showSpotlight ? 1 : 0,
pointerEvents: spotlightClicks ? 'none' : 'auto',
position: isFixedTarget ? 'fixed' : 'absolute',
top,
transition: 'opacity 0.2s',
width: Math.round(elementRect.width + spotlightPadding * 2),
};
const elements = getElements(target);

return elements.map(element => {
const elementRect = getClientRect(element);
const isFixedTarget = hasPosition(element);
const top = getElementPosition(element, spotlightPadding, disableScrollParentFix);

return {
...(isLegacy() ? styles.spotlightLegacy : styles.spotlight),
height: Math.round(elementRect.height + spotlightPadding * 2),
left: Math.round(elementRect.left - spotlightPadding),
opacity: showSpotlight ? 1 : 0,
pointerEvents: spotlightClicks ? 'none' : 'auto',
position: isFixedTarget ? 'fixed' : 'absolute',
top,
transition: 'opacity 0.2s',
width: Math.round(elementRect.width + spotlightPadding * 2),
};
});
}

handleMouseMove = e => {
const { mouseOverSpotlight } = this.state;
const { height, left, position, top, width } = this.spotlightStyles;

const offsetY = position === 'fixed' ? e.clientY : e.pageY;
const offsetX = position === 'fixed' ? e.clientX : e.pageX;
const inSpotlightHeight = offsetY >= top && offsetY <= top + height;
const inSpotlightWidth = offsetX >= left && offsetX <= left + width;
const inSpotlight = inSpotlightWidth && inSpotlightHeight;
const inSpotlight = this.spotlightStyles.some(({ height, left, position, top, width }) => {
const offsetY = position === 'fixed' ? e.clientY : e.pageY;
const offsetX = position === 'fixed' ? e.clientX : e.pageX;
const inSpotlightHeight = offsetY >= top && offsetY <= top + height;
const inSpotlightWidth = offsetX >= left && offsetX <= left + width;
const itemInSpotlight = inSpotlightWidth && inSpotlightHeight;

return itemInSpotlight;
});

if (inSpotlight !== mouseOverSpotlight) {
this.updateState({ mouseOverSpotlight: inSpotlight });
Expand All @@ -138,7 +145,7 @@ export default class JoyrideOverlay extends React.Component {

handleScroll = () => {
const { target } = this.props;
const element = getElement(target);
const elements = getElements(target);

if (this.scrollParent !== document) {
const { isScrolling } = this.state;
Expand All @@ -152,7 +159,7 @@ export default class JoyrideOverlay extends React.Component {
this.scrollTimeout = setTimeout(() => {
this.updateState({ isScrolling: false, showSpotlight: true });
}, 50);
} else if (hasPosition(element, 'sticky')) {
} else if (elements.some(element => element.hasPosition(element, 'sticky'))) {
this.updateState({});
}
};
Expand Down Expand Up @@ -201,7 +208,11 @@ export default class JoyrideOverlay extends React.Component {
};

let spotlight = placement !== 'center' && showSpotlight && (
<Spotlight styles={this.spotlightStyles} />
<>
{this.spotlightStyles.map((itemStyles, index) => (
<Spotlight styles={itemStyles} key={index} />
))}
</>
);

// Hack for Safari bug with mix-blend-mode with z-index
Expand Down
8 changes: 5 additions & 3 deletions src/components/Step.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import is from 'is-lite';

import { ACTIONS, EVENTS, LIFECYCLE, STATUS } from '../constants';

import { getElement, isElementVisible, hasPosition } from '../modules/dom';
import { getElements, isElementVisible, hasPosition } from '../modules/dom';
import { log, hideBeacon } from '../modules/helpers';
import { componentTypeWithRefs } from '../modules/propTypes';
import Scope from '../modules/scope';
Expand Down Expand Up @@ -134,7 +134,8 @@ export default class JoyrideStep extends React.Component {

// There's a step to use, but there's no target in the DOM
if (hasStoreChanged && step) {
const element = getElement(step.target);
const elements = getElements(step.target);
const element = elements[0];
const elementExists = !!element;
const hasRenderedTarget = elementExists && isElementVisible(element);

Expand Down Expand Up @@ -260,7 +261,8 @@ export default class JoyrideStep extends React.Component {

render() {
const { continuous, debug, helpers, index, lifecycle, shouldScroll, size, step } = this.props;
const target = getElement(step.target);
const elements = getElements(step.target) || [];
const target = elements[0];

if (!validateStep(step) || !is.domElement(target)) {
return null;
Expand Down
9 changes: 6 additions & 3 deletions src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import is from 'is-lite';

import Store from '../modules/store';
import {
getElement,
getElements,
getScrollParent,
getScrollTo,
hasCustomScrollParent,
Expand Down Expand Up @@ -109,7 +109,8 @@ class Joyride extends React.Component {

const stepsChanged = !isEqual(prevSteps, steps);
const stepIndexChanged = is.number(stepIndex) && changedProps('stepIndex');
const target = getElement(step?.target);
const elements = getElements(step?.target) || [];
const target = elements[0];

if (stepsChanged) {
if (validateSteps(steps, debug)) {
Expand Down Expand Up @@ -281,7 +282,9 @@ class Joyride extends React.Component {

/* istanbul ignore else */
if (step) {
const target = getElement(step.target);
const elements = getElements(step.target) || [];
const target = elements[0];

const shouldScroll = this.shouldScroll(
disableScrolling,
index,
Expand Down
17 changes: 11 additions & 6 deletions src/modules/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,22 @@ export function getDocumentHeight(): number {
* Find and return the target DOM element based on a step's 'target'.
*
* @private
* @param {string|HTMLElement} element
* @param {string|HTMLElement[]|HTMLElement} elements
*
* @returns {HTMLElement|null}
* @returns {HTMLElement[]}
*/
export function getElement(element: string | HTMLElement): ?HTMLElement {
export function getElements(elements: string | HTMLElement[] | HTMLElement): HTMLElement[] {
/* istanbul ignore else */
if (typeof element === 'string') {
return document.querySelector(element);
if (typeof elements === 'string') {
// that's to keep correct order if joined with commas
const selectors = elements.split(',');
return selectors.flatMap(selector => [...document.querySelectorAll(selector)]);
}
if (!Array.isArray(elements)) {
return [elements];
}

return element;
return elements;
}

/**
Expand Down
10 changes: 5 additions & 5 deletions src/modules/step.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import deepmerge from 'deepmerge';
import is from 'is-lite';

import { getElement, hasCustomScrollParent } from './dom';
import { getElements, hasCustomScrollParent } from './dom';
import { log } from './helpers';
import getStyles from '../styles';

Expand Down Expand Up @@ -43,10 +43,10 @@ export function getMergedStep(step: StepProps, props: JoyrideProps): ?StepProps
isMergeableObject: is.plainObject,
});
const mergedStyles = getStyles(deepmerge(props.styles || {}, step.styles || {}));
const scrollParent = hasCustomScrollParent(
getElement(step.target),
mergedStep.disableScrollParentFix,
);
const elements = getElements(step.target);
const firstElement = elements[0];

const scrollParent = hasCustomScrollParent(firstElement, mergedStep.disableScrollParentFix);
const floaterProps = deepmerge.all([
props.floaterProps || {},
DEFAULTS.floaterProps,
Expand Down
2 changes: 1 addition & 1 deletion types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ export interface Step extends CommonProps {
offset?: number;
placement?: Placement | 'auto' | 'center';
placementBeacon?: Placement;
target: string | HTMLElement;
target: string | HTMLElement | HTMLElement[];
title?: React.ReactNode;
}

Expand Down

0 comments on commit ecae293

Please sign in to comment.