/** * @file * Provides backwards compatibility for Tours that no longer use Joyride. */ ((Drupal) => { /** * Converts the markup of a Shepherd tour tip to match Joyride. * * @param {Tour} shepherdTour * A ShepherdJS tour object. * * @internal */ Drupal.tour.convertToJoyrideMarkup = (shepherdTour) => { /** * Changes the tag of an element. * * @param {HTMLElement} element * The element that will have its tag changed. * @param {string} tag * The tag the element should be changed to. */ const changeTag = (element, tag) => { if (element) { const newTagElement = document.createElement(tag); [...element.attributes].forEach((attr) => { newTagElement.setAttribute(attr.name, attr.value); }); newTagElement.innerHTML = element.innerHTML; element.parentNode.replaceChild(newTagElement, element); } }; // Create variables for the elements that will be rearranged. const shepherdElement = shepherdTour.currentStep.el; const shepherdContent = shepherdElement.querySelector('.shepherd-content'); const shepherdCancel = shepherdElement.querySelector( '.shepherd-cancel-icon', ); const shepherdTitle = shepherdElement.querySelector('.shepherd-title'); const shepherdText = shepherdElement.querySelector('.shepherd-text'); const shepherdNext = shepherdElement.querySelector('footer .button'); const tourProgress = shepherdElement.querySelector('.tour-progress'); // Add attributes to the elements so they match what they were when Joyride // was providing Tour functionality. shepherdElement.classList.add('joyride-tip-guide'); shepherdContent.classList.add('joyride-content-wrapper'); shepherdNext.classList.add('joyride-next-tip'); shepherdNext.setAttribute('href', '#'); shepherdNext.setAttribute('role', 'button'); shepherdNext.removeAttribute('type'); shepherdCancel.classList.add('joyride-close-tip'); shepherdCancel.removeAttribute('type'); shepherdCancel.setAttribute('href', '#'); shepherdCancel.setAttribute('role', 'button'); shepherdElement.setAttribute( 'data-index', shepherdTour.currentStep.options.index, ); shepherdElement.querySelector('footer').remove(); // Rearrange elements so their structure matches Joyride's. shepherdContent.insertBefore(shepherdTitle, shepherdContent.firstChild); shepherdContent.insertBefore(tourProgress, shepherdText.nextSibling); shepherdContent.appendChild(shepherdCancel); shepherdContent.querySelector('.shepherd-header').remove(); shepherdContent.insertBefore(shepherdNext, tourProgress.nextSibling); shepherdCancel.innerHTML = ''; shepherdTitle.classList.add('tour-tip-label'); // Convert elements to use the tags they used in Joyride. changeTag(shepherdTitle, 'h2'); // Remove the wrapper Shepherd adds for tip content. shepherdText.outerHTML = shepherdText.innerHTML; // Convert the next and cancel buttons to links so they match Joyride's // markup. They must be re-queried as they were potentially moved elsewhere // in the DOM. changeTag(shepherdElement.querySelector('.joyride-close-tip'), 'a'); changeTag(shepherdElement.querySelector('.joyride-next-tip'), 'a'); // The arrow protruding from a tip pointing to the element it references. const shepherdArrow = shepherdElement.querySelector('.shepherd-arrow'); if (shepherdArrow) { shepherdArrow.classList.add('joyride-nub'); if (shepherdTour.currentStep.options.attachTo.on) { // Shepherd's positions are opposite of Joyride's as they specify the // tip location relative to the corresponding element as opposed to // their location on the tip itself. const stepToTipPosition = { bottom: 'top', top: 'bottom', left: 'right', right: 'left', }; shepherdArrow.classList.add( // Split at '-' as shepherd positioning accommodates dash-delimited // secondary axis positioning. // shepherdTour.currentStep.options.attachTo.on.split('-')[0] stepToTipPosition[ // Split at '-' as shepherd positioning accommodates dash-delimited // secondary axis positioning. shepherdTour.currentStep.options.attachTo.on.split('-')[0] ], ); } changeTag(shepherdArrow, 'span'); } else { // If there is no Shepherd arrow, there still needs to be markup for a // non-displayed nub to match Joyride's markup. const nub = document.createElement('span'); nub.classList.add('joyride-nub'); nub.setAttribute('style', 'display: none;'); shepherdElement.insertBefore(nub, shepherdElement.firstChild); } // When the next and cancel buttons were converted to links, they became // new DOM elements that no longer have their associated event listeners. // The events must be reintroduced here. shepherdElement .querySelector('.joyride-next-tip') .addEventListener('click', (e) => { e.preventDefault(); shepherdTour.next(); }); shepherdElement .querySelector('.joyride-close-tip') .addEventListener('click', (e) => { e.preventDefault(); shepherdTour.cancel(); }); shepherdElement.querySelector('.joyride-next-tip').focus(); }; })(Drupal);