Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Separate modal overlay opening element option #1837

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
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
1 change: 1 addition & 0 deletions docs-src/tutorials/02-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ For example: `{selector: '.some-element', event: 'click'}`. It doesn't have to
You can also always manually advance the Tour by calling `myTour.next()`.
- `highlightClass`: An extra class to apply to the `attachTo` element when it is highlighted (that is, when its step is active). You can then target that selector in your CSS.
- `id`: The string to use as the `id` for the step. If an id is not passed one will be generated.
- `modalOverlayOpeningElement`: An element selector string or a DOM element that the modal overlay opening should target. Defaults to using the same element as `attachTo` if unspecified.
- `modalOverlayOpeningPadding`: An amount of padding to add around the modal overlay opening
- `modalOverlayOpeningRadius`: An amount of border radius to add around the modal overlay opening
- `popperOptions`: Extra options to pass to [Popper](https://popper.js.org/docs/v2/constructors/#options)
Expand Down
9 changes: 6 additions & 3 deletions src/js/components/shepherd-modal.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script>
import { uuid } from '../utils/general.js';
import { uuid, parseElementOrSelector } from '../utils/general.js';
import { makeOverlayPath } from '../utils/overlay-path.js';

export let element, openingProperties;
Expand All @@ -13,6 +13,7 @@
closeModalOpening();

export const getElement = () => element;
export const getOpeningProperties = () => openingProperties;

export function closeModalOpening() {
openingProperties = {
Expand Down Expand Up @@ -128,11 +129,13 @@
*/
function _styleForStep(step) {
const {
modalOverlayOpeningElement,
modalOverlayOpeningPadding,
modalOverlayOpeningRadius
} = step.options;

const scrollParent = _getScrollParent(step.target);
const target = parseElementOrSelector(modalOverlayOpeningElement) || step.target;
const scrollParent = _getScrollParent(target);

// Setup recursive function to call requestAnimationFrame to update the modal opening position
const rafLoop = () => {
Expand All @@ -141,7 +144,7 @@
modalOverlayOpeningPadding,
modalOverlayOpeningRadius,
scrollParent,
step.target
target
);
rafId = requestAnimationFrame(rafLoop);
};
Expand Down
2 changes: 2 additions & 0 deletions src/js/step.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ export class Step extends Evented {
* @param {string} options.highlightClass An extra class to apply to the `attachTo` element when it is
* highlighted (that is, when its step is active). You can then target that selector in your CSS.
* @param {string} options.id The string to use as the `id` for the step.
* @param {HTMLElement|string} options.modalOverlayOpeningElement An element selector string or a DOM element that the modal overlay
* opening should target. Defaults to using the same element as `attachTo` if unspecified.
* @param {number} options.modalOverlayOpeningPadding An amount of padding to add around the modal overlay opening
* @param {number} options.modalOverlayOpeningRadius An amount of border radius to add around the modal overlay opening
* @param {object} options.popperOptions Extra options to pass to Popper
Expand Down
32 changes: 27 additions & 5 deletions src/js/utils/general.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,41 @@ export function parseAttachTo(step) {
// guarantee that the element will exist in the future.
try {
returnOpts.element = document.querySelector(returnOpts.element);
returnOpts.element = parseElementOrSelector(options.element);
} catch (e) {
// TODO
return null;
}
if (!returnOpts.element) {
console.error(
`The element for this Shepherd step was not found ${options.element}`
);
}
}
if (!returnOpts.element) {
console.error(
`The element for this Shepherd step was not found ${options.element}`
);
}

return returnOpts;
}

/**
* Takes in an HTMLElement or string or nullish value, returns HTMLElement or null if not found
* @param {HTMLElement|string|void} element DOM element or string selector (or any falsy value)
* @returns {HTMLElement|null}
*/
export function parseElementOrSelector(element) {
if (!element) {
return null;
}
if (isString(element)) {
try {
return document.querySelector(element);
} catch (e) {
// TODO
return null;
}
}
return element;
}

/**
* Checks if the step should be centered or not. Does not trigger attachTo.element evaluation, making it a pure
* alternative for the deprecated step.isCentered() method.
Expand Down
7 changes: 7 additions & 0 deletions src/types/step.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,13 @@ declare namespace Step {
*/
id?: string;

/**
* An element selector string or a DOM element that the modal overlay
* opening should target. Defaults to using the same element as `attachTo`
* if unspecified.
*/
modalOverlayOpeningElement?: HTMLElement | string;

/**
* An amount of padding to add around the modal overlay opening
*/
Expand Down
27 changes: 27 additions & 0 deletions test/cypress/integration/modal.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,31 @@ describe('Modal mode', () => {
cy.get('.shepherd-modal-is-visible').should('have.length', 1);
});
});

describe('Modal with separate selector', function() {
const steps = () => {
return [
{
attachTo: {
element: '.hero-welcome',
on: 'bottom'
},
modalOverlayOpeningElement: '.hero-welcome h1',
}
];
};

beforeEach(() => {
tour = setupTour(Shepherd, {}, steps, {
useModalOverlay: true
});
});

it('has the modal target the separate selector element', function() {
tour.start();
cy.get('.shepherd-modal-is-visible').should('have.length', 1);
const openingPropertyHeight = tour.modal.getOpeningProperties().height;
cy.get('.hero-welcome h1').invoke('outerHeight').should('eq', openingPropertyHeight);
});
});
});
26 changes: 25 additions & 1 deletion test/unit/utils/general.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { should } from 'chai';
import { spy } from 'sinon';
import { Step } from '../../../src/js/step.js';
import { Tour } from '../../../src/js/tour.js';
import { getPopperOptions, parseAttachTo, shouldCenterStep } from '../../../src/js/utils/general.js';
import { getPopperOptions, parseAttachTo, shouldCenterStep, parseElementOrSelector } from '../../../src/js/utils/general.js';

describe('General Utils', function() {
let optionsElement;
Expand Down Expand Up @@ -61,6 +61,30 @@ describe('General Utils', function() {
});
});

describe('parseElementOrSelector()', function() {
it('Returns the same HTMLElement passed to it', function () {
const element = document.createElement('div');
const parsedElement = parseElementOrSelector(element);
expect(element === parsedElement).toBeTruthy();
});

it('Returns an HTMLElement when passed a matching selector', function() {
const parsedElement = parseElementOrSelector('.options-test');
expect(parsedElement).toBeInstanceOf(window.HTMLElement);
});

it('Returns null when passed a non-matching selector', function() {
const parsedElement = parseElementOrSelector('.element-does-not-exist');
expect(parsedElement).toBeNull();
});

it('Returns null when passed falsy values', function() {
expect(parseElementOrSelector(undefined)).toBeNull();
expect(parseElementOrSelector(false)).toBeNull();
expect(parseElementOrSelector(null)).toBeNull();
});
});

describe('getPopperOptions', function() {
it('modifiers can be overridden', function() {
const step = new Step({}, {
Expand Down