From 1864d8dd016606c5958ff943ced1d6568a600866 Mon Sep 17 00:00:00 2001 From: Brian Sipple Date: Wed, 12 Sep 2018 15:28:29 -0700 Subject: [PATCH] Add ability to controll clickability of step target elements. --- README.md | 7 +++ addon/services/tour.js | 69 ++++++++++++++--------- tests/acceptance/ember-shepherd-test.js | 56 +++++++++++++----- tests/dummy/app/styles/app.scss | 1 + tests/dummy/app/templates/application.hbs | 2 +- 5 files changed, 92 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 052da785..eed48bab 100644 --- a/README.md +++ b/README.md @@ -258,6 +258,13 @@ Extra classes to apply to the step, for styling purposes and such. > **default value:** `''` +#### canClickTarget + +Whether or not the target element being attached to should be "clickable". If set to `false`, Ember Shepherd sets the element's [`pointerEvents` style](https://developer.mozilla.org/en-US/docs/Web/CSS/pointer-events) to `none` while the step is active. + +> **default value:** `true` + + ##### copyStyles This is a boolean, and when set to `true` it will fully clone the element and styles, rather than just increasing the element's z-index. This should only be used if the element does not pop out and highlight like it should, when using modal functionality. diff --git a/addon/services/tour.js b/addon/services/tour.js index 1ba448f3..117227aa 100644 --- a/addon/services/tour.js +++ b/addon/services/tour.js @@ -104,31 +104,9 @@ export default Service.extend(Evented, { if (get(this, 'disableScroll')) { disableScroll.off(window); } - if (get(this, 'modal')) { - const tour = get(this, 'tourObject'); - - if (tour) { - const { steps } = tour; - - steps.map((step) => { - const stepElement = getElementForStep(step); - - if (step && step.options.attachTo && stepElement) { - stepElement.style.pointerEvents = 'auto'; - } - }); - } - run('afterRender', () => { - removeElement('#shepherdOverlay'); - removeElement('#highlightOverlay'); - const shepherdModal = document.querySelector('.shepherd-modal'); - - if (shepherdModal) { - shepherdModal.classList.remove('shepherd-modal'); - } - }); - } + this._cleanupSteps(); + this._cleanupModal(); }, /** @@ -256,7 +234,7 @@ export default Service.extend(Evented, { * @param step The step object that attaches to the element * @private */ - popoutElement(step) { + styleTargetElement(step) { const currentElement = getElementForStep(step); if (!currentElement) { @@ -267,9 +245,11 @@ export default Service.extend(Evented, { currentElement.classList.add(step.options.highlightClass); } - if (get(this, 'modal')) { + if (step.options.canClickTarget === false) { currentElement.style.pointerEvents = 'none'; + } + if (this.modal) { if (step.options.copyStyles) { this.createHighlightOverlay(step); } else { @@ -344,7 +324,7 @@ export default Service.extend(Evented, { const currentStep = tour.steps[index]; currentStep.on('before-show', () => { - this.popoutElement(currentStep); + this.styleTargetElement(currentStep); }); currentStep.on('hide', () => { // Remove element copy, if it was cloned @@ -377,5 +357,38 @@ export default Service.extend(Evented, { } }); - }) + }), + + _cleanupSteps() { + const tour = this.tourObject; + + if (tour) { + const { steps } = tour; + + steps.forEach((step) => { + if (step.options && step.options.canClickTarget === false && step.options.attachTo) { + const stepElement = getElementForStep(step); + + if (stepElement instanceof HTMLElement) { + stepElement.style.pointerEvents = 'auto'; + } + } + }); + } + }, + + _cleanupModal() { + if (this.modal) { + run('afterRender', () => { + removeElement('#shepherdOverlay'); + removeElement('#highlightOverlay'); + + const shepherdModal = document.querySelector('.shepherd-modal'); + + if (shepherdModal) { + shepherdModal.classList.remove('shepherd-modal'); + } + }); + } + } }); diff --git a/tests/acceptance/ember-shepherd-test.js b/tests/acceptance/ember-shepherd-test.js index ddef213b..f1fccf48 100644 --- a/tests/acceptance/ember-shepherd-test.js +++ b/tests/acceptance/ember-shepherd-test.js @@ -306,27 +306,55 @@ module('Acceptance | Tour functionality tests', function(hooks) { assert.ok(buttonActionCalled, 'button action triggered'); }); - test('`pointer-events` is set to `auto` for any step element on clean up', async function(assert) { - assert.expect(2); + test('`pointer-events` is set to `auto` for any previously disabled `attachTo` targets', async function(assert) { + const steps = [ + { + id: 'step-1', + options: { + attachTo: '.shepherd-logo-link top', + builtInButtons: [ + builtInButtons.cancel, + builtInButtons.next, + ], + title: 'Controlling Clickability', + text: 'By default, target elements should have their `pointerEvents` style unchanged', + }, + }, + { + id: 'step-2', + options: { + attachTo: '.shepherd-logo-link top', + canClickTarget: false, + builtInButtons: [ + builtInButtons.cancel, + ], + title: 'Controlling Clickability', + text: 'Clickability of target elements can be disabled by setting `canClickTarget` to false', + } + }, + ]; - await visit('/'); + await visit('/'); - await click('.toggleHelpModal'); + tour.set('steps', steps); + tour.set('modal', true); + + await click('.toggleHelpModal'); + + // Get the target element + const targetElement = document.querySelector('.shepherd-target'); - // Go through a step of the tour... - await click(document.querySelector('[data-id="intro"] .next-button')); + assert.equal(getComputedStyle(targetElement)['pointer-events'], 'auto'); - // Get the target element - const targetElement = document.querySelector('.shepherd-target'); + // Exit the tour + await click(document.querySelector('[data-id="step-1"] .next-button')); - // Check the target element has pointer-events = 'none' - assert.equal(targetElement.style.pointerEvents, 'none'); + assert.equal(getComputedStyle(targetElement)['pointer-events'], 'none'); - // Exit the tour - await click(document.querySelector('[data-id="installation"] .cancel-button')); + // Exit the tour + await click(document.querySelector('[data-id="step-2"] .cancel-button')); - // Check the target element now has pointer-events = 'auto' - assert.equal(targetElement.style.pointerEvents, 'auto'); + assert.equal(getComputedStyle(targetElement)['pointer-events'], 'auto'); }); test('scrollTo works with disableScroll on', async function(assert) { diff --git a/tests/dummy/app/styles/app.scss b/tests/dummy/app/styles/app.scss index 7ce0424e..2a23c625 100644 --- a/tests/dummy/app/styles/app.scss +++ b/tests/dummy/app/styles/app.scss @@ -70,6 +70,7 @@ a { font-size: 0.75em; margin-bottom: 1.25rem; padding: 1.25rem; + text-align: center; } .button.dark { diff --git a/tests/dummy/app/templates/application.hbs b/tests/dummy/app/templates/application.hbs index b1fe0651..f80fd04f 100644 --- a/tests/dummy/app/templates/application.hbs +++ b/tests/dummy/app/templates/application.hbs @@ -68,7 +68,7 @@ tour.cancel(); - +