text: /*******************************************************************************
   * Copyright 2018 Adobe
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   ******************************************************************************/
  (function() {
      "use strict";
  
      var dataLayerEnabled;
      var dataLayer;
  
      var NS = "cmp";
      var IS = "carousel";
  
      var keyCodes = {
          SPACE: 32,
          END: 35,
          HOME: 36,
          ARROW_LEFT: 37,
          ARROW_UP: 38,
          ARROW_RIGHT: 39,
          ARROW_DOWN: 40
      };
  
      var selectors = {
          self: "[data-" + NS + '-is="' + IS + '"]'
      };
  
      var properties = {
          /**
           * Determines whether the Carousel will automatically transition between slides
           *
           * @memberof Carousel
           * @type {Boolean}
           * @default false
           */
          "autoplay": {
              "default": false,
              "transform": function(value) {
                  return !(value === null || typeof value === "undefined");
              }
          },
          /**
           * Duration (in milliseconds) before automatically transitioning to the next slide
           *
           * @memberof Carousel
           * @type {Number}
           * @default 5000
           */
          "delay": {
              "default": 5000,
              "transform": function(value) {
                  value = parseFloat(value);
                  return !isNaN(value) ? value : null;
              }
          },
          /**
           * Determines whether automatic pause on hovering the carousel is disabled
           *
           * @memberof Carousel
           * @type {Boolean}
           * @default false
           */
          "autopauseDisabled": {
              "default": false,
              "transform": function(value) {
                  return !(value === null || typeof value === "undefined");
              }
          }
      };
  
      /**
       * Carousel Configuration
       *
       * @typedef {Object} CarouselConfig Represents a Carousel configuration
       * @property {HTMLElement} element The HTMLElement representing the Carousel
       * @property {Object} options The Carousel options
       */
  
      /**
       * Carousel
       *
       * @class Carousel
       * @classdesc An interactive Carousel component for navigating a list of generic items
       * @param {CarouselConfig} config The Carousel configuration
       */
      function Carousel(config) {
          var that = this;
  
          if (config && config.element) {
              init(config);
          }
  
          /**
           * Initializes the Carousel
           *
           * @private
           * @param {CarouselConfig} config The Carousel configuration
           */
          function init(config) {
              // prevents multiple initialization
              config.element.removeAttribute("data-" + NS + "-is");
  
              setupProperties(config.options);
              cacheElements(config.element);
  
              that._active = 0;
              that._paused = false;
  
              if (that._elements.item) {
                  refreshActive();
                  bindEvents();
                  resetAutoplayInterval();
                  refreshPlayPauseActions();
              }
  
              // TODO: This section is only relevant in edit mode and should move to the editor clientLib
              if (window.Granite && window.Granite.author && window.Granite.author.MessageChannel) {
                  /*
                   * Editor message handling:
                   * - subscribe to "cmp.panelcontainer" message requests sent by the editor frame
                   * - check that the message data panel container type is correct and that the id (path) matches this specific Carousel component
                   * - if so, route the "navigate" operation to enact a navigation of the Carousel based on index data
                   */
                  window.CQ = window.CQ || {};
                  window.CQ.CoreComponents = window.CQ.CoreComponents || {};
                  window.CQ.CoreComponents.MESSAGE_CHANNEL = window.CQ.CoreComponents.MESSAGE_CHANNEL || new window.Granite.author.MessageChannel("cqauthor", window);
                  window.CQ.CoreComponents.MESSAGE_CHANNEL.subscribeRequestMessage("cmp.panelcontainer", function(message) {
                      if (message.data && message.data.type === "cmp-carousel" && message.data.id === that._elements.self.dataset["cmpPanelcontainerId"]) {
                          if (message.data.operation === "navigate") {
                              navigate(message.data.index);
                          }
                      }
                  });
              }
          }
  
          /**
           * Caches the Carousel elements as defined via the {@code data-carousel-hook="ELEMENT_NAME"} markup API
           *
           * @private
           * @param {HTMLElement} wrapper The Carousel wrapper element
           */
          function cacheElements(wrapper) {
              that._elements = {};
              that._elements.self = wrapper;
              var hooks = that._elements.self.querySelectorAll("[data-" + NS + "-hook-" + IS + "]");
  
              for (var i = 0; i < hooks.length; i++) {
                  var hook = hooks[i];
                  var capitalized = IS;
                  capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1);
                  var key = hook.dataset[NS + "Hook" + capitalized];
                  if (that._elements[key]) {
                      if (!Array.isArray(that._elements[key])) {
                          var tmp = that._elements[key];
                          that._elements[key] = [tmp];
                      }
                      that._elements[key].push(hook);
                  } else {
                      that._elements[key] = hook;
                  }
              }
          }
  
          /**
           * Sets up properties for the Carousel based on the passed options.
           *
           * @private
           * @param {Object} options The Carousel options
           */
          function setupProperties(options) {
              that._properties = {};
  
              for (var key in properties) {
                  if (properties.hasOwnProperty(key)) {
                      var property = properties[key];
                      var value = null;
  
                      if (options && options[key] != null) {
                          value = options[key];
  
                          // transform the provided option
                          if (property && typeof property.transform === "function") {
                              value = property.transform(value);
                          }
                      }
  
                      if (value === null) {
                          // value still null, take the property default
                          value = properties[key]["default"];
                      }
  
                      that._properties[key] = value;
                  }
              }
          }
  
          /**
           * Binds Carousel event handling
           *
           * @private
           */
          function bindEvents() {
              if (that._elements["previous"]) {
                  that._elements["previous"].addEventListener("click", function() {
                      var index = getPreviousIndex();
                      navigate(index);
                      if (dataLayerEnabled) {
                          dataLayer.push({
                              event: "cmp:show",
                              eventInfo: {
                                  path: "component." + getDataLayerId(that._elements.item[index])
                              }
                          });
                      }
                  });
              }
  
              if (that._elements["next"]) {
                  that._elements["next"].addEventListener("click", function() {
                      var index = getNextIndex();
                      navigate(index);
                      if (dataLayerEnabled) {
                          dataLayer.push({
                              event: "cmp:show",
                              eventInfo: {
                                  path: "component." + getDataLayerId(that._elements.item[index])
                              }
                          });
                      }
                  });
              }
  
              var indicators = that._elements["indicator"];
              if (indicators) {
                  for (var i = 0; i < indicators.length; i++) {
                      (function(index) {
                          indicators[i].addEventListener("click", function(event) {
                              navigateAndFocusIndicator(index);
                          });
                      })(i);
                  }
              }
  
              if (that._elements["pause"]) {
                  if (that._properties.autoplay) {
                      that._elements["pause"].addEventListener("click", onPauseClick);
                  }
              }
  
              if (that._elements["play"]) {
                  if (that._properties.autoplay) {
                      that._elements["play"].addEventListener("click", onPlayClick);
                  }
              }
  
              that._elements.self.addEventListener("keydown", onKeyDown);
  
              if (!that._properties.autopauseDisabled) {
                  that._elements.self.addEventListener("mouseenter", onMouseEnter);
                  that._elements.self.addEventListener("mouseleave", onMouseLeave);
              }
          }
  
          /**
           * Handles carousel keydown events
           *
           * @private
           * @param {Object} event The keydown event
           */
          function onKeyDown(event) {
              var index = that._active;
              var lastIndex = that._elements["indicator"].length - 1;
  
              switch (event.keyCode) {
                  case keyCodes.ARROW_LEFT:
                  case keyCodes.ARROW_UP:
                      event.preventDefault();
                      if (index > 0) {
                          navigateAndFocusIndicator(index - 1);
                      }
                      break;
                  case keyCodes.ARROW_RIGHT:
                  case keyCodes.ARROW_DOWN:
                      event.preventDefault();
                      if (index < lastIndex) {
                          navigateAndFocusIndicator(index + 1);
                      }
                      break;
                  case keyCodes.HOME:
                      event.preventDefault();
                      navigateAndFocusIndicator(0);
                      break;
                  case keyCodes.END:
                      event.preventDefault();
                      navigateAndFocusIndicator(lastIndex);
                      break;
                  case keyCodes.SPACE:
                      if (that._properties.autoplay && (event.target !== that._elements["previous"] && event.target !== that._elements["next"])) {
                          event.preventDefault();
                          if (!that._paused) {
                              pause();
                          } else {
                              play();
                          }
                      }
                      if (event.target === that._elements["pause"]) {
                          that._elements["play"].focus();
                      }
                      if (event.target === that._elements["play"]) {
                          that._elements["pause"].focus();
                      }
                      break;
                  default:
                      return;
              }
          }
  
          /**
           * Handles carousel mouseenter events
           *
           * @private
           * @param {Object} event The mouseenter event
           */
          function onMouseEnter(event) {
              clearAutoplayInterval();
          }
  
          /**
           * Handles carousel mouseleave events
           *
           * @private
           * @param {Object} event The mouseleave event
           */
          function onMouseLeave(event) {
              resetAutoplayInterval();
          }
  
          /**
           * Handles pause element click events
           *
           * @private
           * @param {Object} event The click event
           */
          function onPauseClick(event) {
              pause();
              that._elements["play"].focus();
          }
  
          /**
           * Handles play element click events
           *
           * @private
           * @param {Object} event The click event
           */
          function onPlayClick() {
              play();
              that._elements["pause"].focus();
          }
  
          /**
           * Pauses the playing of the Carousel. Sets {@code Carousel#_paused} marker.
           * Only relevant when autoplay is enabled
           *
           * @private
           */
          function pause() {
              that._paused = true;
              clearAutoplayInterval();
              refreshPlayPauseActions();
          }
  
          /**
           * Enables the playing of the Carousel. Sets {@code Carousel#_paused} marker.
           * Only relevant when autoplay is enabled
           *
           * @private
           */
          function play() {
              that._paused = false;
  
              // If the Carousel is hovered, don't begin auto transitioning until the next mouse leave event
              var hovered = false;
              if (that._elements.self.parentElement) {
                  hovered = that._elements.self.parentElement.querySelector(":hover") === that._elements.self;
              }
              if (that._properties.autopauseDisabled || !hovered) {
                  resetAutoplayInterval();
              }
  
              refreshPlayPauseActions();
          }
  
          /**
           * Refreshes the play/pause action markup based on the {@code Carousel#_paused} state
           *
           * @private
           */
          function refreshPlayPauseActions() {
              setActionDisabled(that._elements["pause"], that._paused);
              setActionDisabled(that._elements["play"], !that._paused);
          }
  
          /**
           * Refreshes the item markup based on the current {@code Carousel#_active} index
           *
           * @private
           */
          function refreshActive() {
              var items = that._elements["item"];
              var indicators = that._elements["indicator"];
  
              if (items) {
                  if (Array.isArray(items)) {
                      for (var i = 0; i < items.length; i++) {
                          if (i === parseInt(that._active)) {
                              items[i].classList.add("cmp-carousel__item--active");
                              items[i].removeAttribute("aria-hidden");
                              indicators[i].classList.add("cmp-carousel__indicator--active");
                              indicators[i].setAttribute("aria-selected", true);
                              indicators[i].setAttribute("tabindex", "0");
                          } else {
                              items[i].classList.remove("cmp-carousel__item--active");
                              items[i].setAttribute("aria-hidden", true);
                              indicators[i].classList.remove("cmp-carousel__indicator--active");
                              indicators[i].setAttribute("aria-selected", false);
                              indicators[i].setAttribute("tabindex", "-1");
                          }
                      }
                  } else {
                      // only one item
                      items.classList.add("cmp-carousel__item--active");
                      indicators.classList.add("cmp-carousel__indicator--active");
                  }
              }
          }
  
          /**
           * Focuses the element and prevents scrolling the element into view
           *
           * @param {HTMLElement} element Element to focus
           */
          function focusWithoutScroll(element) {
              var x = window.scrollX || window.pageXOffset;
              var y = window.scrollY || window.pageYOffset;
              element.focus();
              window.scrollTo(x, y);
          }
  
          /**
           * Retrieves the next active index, with looping
           *
           * @private
           * @returns {Number} Index of the next carousel item
           */
          function getNextIndex() {
              return that._active === (that._elements["item"].length - 1) ? 0 : that._active + 1;
          }
  
          /**
           * Retrieves the previous active index, with looping
           *
           * @private
           * @returns {Number} Index of the previous carousel item
           */
          function getPreviousIndex() {
              return that._active === 0 ? (that._elements["item"].length - 1) : that._active - 1;
          }
  
          /**
           * Navigates to the item at the provided index
           *
           * @private
           * @param {Number} index The index of the item to navigate to
           */
          function navigate(index) {
              if (index < 0 || index > (that._elements["item"].length - 1)) {
                  return;
              }
  
              that._active = index;
              refreshActive();
  
              if (dataLayerEnabled) {
                  var carouselId = that._elements.self.id;
                  var activeItem = getDataLayerId(that._elements.item[index]);
                  var updatePayload = { component: {} };
                  updatePayload.component[carouselId] = { shownItems: [activeItem] };
  
                  var removePayload = { component: {} };
                  removePayload.component[carouselId] = { shownItems: undefined };
  
                  dataLayer.push(removePayload);
                  dataLayer.push(updatePayload);
              }
  
              // reset the autoplay transition interval following navigation, if not already hovering the carousel
              if (that._elements.self.parentElement) {
                  if (that._elements.self.parentElement.querySelector(":hover") !== that._elements.self) {
                      resetAutoplayInterval();
                  }
              }
          }
  
          /**
           * Navigates to the item at the provided index and ensures the active indicator gains focus
           *
           * @private
           * @param {Number} index The index of the item to navigate to
           */
          function navigateAndFocusIndicator(index) {
              navigate(index);
              focusWithoutScroll(that._elements["indicator"][index]);
  
              if (dataLayerEnabled) {
                  dataLayer.push({
                      event: "cmp:show",
                      eventInfo: {
                          path: "component." + getDataLayerId(that._elements.item[index])
                      }
                  });
              }
          }
  
          /**
           * Starts/resets automatic slide transition interval
           *
           * @private
           */
          function resetAutoplayInterval() {
              if (that._paused || !that._properties.autoplay) {
                  return;
              }
              clearAutoplayInterval();
              that._autoplayIntervalId = window.setInterval(function() {
                  if (document.visibilityState && document.hidden) {
                      return;
                  }
                  var indicators = that._elements["indicators"];
                  if (indicators !== document.activeElement && indicators.contains(document.activeElement)) {
                      // if an indicator has focus, ensure we switch focus following navigation
                      navigateAndFocusIndicator(getNextIndex());
                  } else {
                      navigate(getNextIndex());
                  }
              }, that._properties.delay);
          }
  
          /**
           * Clears/pauses automatic slide transition interval
           *
           * @private
           */
          function clearAutoplayInterval() {
              window.clearInterval(that._autoplayIntervalId);
              that._autoplayIntervalId = null;
          }
  
          /**
           * Sets the disabled state for an action and toggles the appropriate CSS classes
           *
           * @private
           * @param {HTMLElement} action Action to disable
           * @param {Boolean} [disable] {@code true} to disable, {@code false} to enable
           */
          function setActionDisabled(action, disable) {
              if (!action) {
                  return;
              }
              if (disable !== false) {
                  action.disabled = true;
                  action.classList.add("cmp-carousel__action--disabled");
              } else {
                  action.disabled = false;
                  action.classList.remove("cmp-carousel__action--disabled");
              }
          }
      }
  
      /**
       * Reads options data from the Carousel wrapper element, defined via {@code data-cmp-*} data attributes
       *
       * @private
       * @param {HTMLElement} element The Carousel element to read options data from
       * @returns {Object} The options read from the component data attributes
       */
      function readData(element) {
          var data = element.dataset;
          var options = [];
          var capitalized = IS;
          capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1);
          var reserved = ["is", "hook" + capitalized];
  
          for (var key in data) {
              if (data.hasOwnProperty(key)) {
                  var value = data[key];
  
                  if (key.indexOf(NS) === 0) {
                      key = key.slice(NS.length);
                      key = key.charAt(0).toLowerCase() + key.substring(1);
  
                      if (reserved.indexOf(key) === -1) {
                          options[key] = value;
                      }
                  }
              }
          }
  
          return options;
      }
  
      /**
       * Parses the dataLayer string and returns the ID
       *
       * @private
       * @param {HTMLElement} item the accordion item
       * @returns {String} dataLayerId or undefined
       */
      function getDataLayerId(item) {
          if (item && item.dataset.cmpDataLayer) {
              return Object.keys(JSON.parse(item.dataset.cmpDataLayer))[0];
          } else {
              return item.id;
          }
      }
  
      /**
       * Document ready handler and DOM mutation observers. Initializes Carousel components as necessary.
       *
       * @private
       */
      function onDocumentReady() {
          dataLayerEnabled = document.body.hasAttribute("data-cmp-data-layer-enabled");
          dataLayer = (dataLayerEnabled) ? window.adobeDataLayer = window.adobeDataLayer || [] : undefined;
  
          var elements = document.querySelectorAll(selectors.self);
          for (var i = 0; i < elements.length; i++) {
              new Carousel({ element: elements[i], options: readData(elements[i]) });
          }
  
          var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
          var body = document.querySelector("body");
          var observer = new MutationObserver(function(mutations) {
              mutations.forEach(function(mutation) {
                  // needed for IE
                  var nodesArray = [].slice.call(mutation.addedNodes);
                  if (nodesArray.length > 0) {
                      nodesArray.forEach(function(addedNode) {
                          if (addedNode.querySelectorAll) {
                              var elementsArray = [].slice.call(addedNode.querySelectorAll(selectors.self));
                              elementsArray.forEach(function(element) {
                                  new Carousel({ element: element, options: readData(element) });
                              });
                          }
                      });
                  }
              });
          });
  
          observer.observe(body, {
              subtree: true,
              childList: true,
              characterData: true
          });
      }
  
      if (document.readyState !== "loading") {
          onDocumentReady();
      } else {
          document.addEventListener("DOMContentLoaded", onDocumentReady);
      }
  
  }());
  