text: (function($, anim) {
    'use strict';
  
    let _defaults = {
      edge: 'left',
      draggable: true,
      dragTargetWidth: '10px',
      inDuration: 250,
      outDuration: 200,
      onOpenStart: null,
      onOpenEnd: null,
      onCloseStart: null,
      onCloseEnd: null,
      preventScrolling: true
    };
  
    /**
     * @class
     */
    class Sidenav extends Component {
      /**
       * Construct Sidenav instance and set up overlay
       * @constructor
       * @param {Element} el
       * @param {Object} options
       */
      constructor(el, options) {
        super(Sidenav, el, options);
  
        this.el.M_Sidenav = this;
        this.id = this.$el.attr('id');
  
        /**
         * Options for the Sidenav
         * @member Sidenav#options
         * @prop {String} [edge='left'] - Side of screen on which Sidenav appears
         * @prop {Boolean} [draggable=true] - Allow swipe gestures to open/close Sidenav
         * @prop {String} [dragTargetWidth='10px'] - Width of the area where you can start dragging
         * @prop {Number} [inDuration=250] - Length in ms of enter transition
         * @prop {Number} [outDuration=200] - Length in ms of exit transition
         * @prop {Function} onOpenStart - Function called when sidenav starts entering
         * @prop {Function} onOpenEnd - Function called when sidenav finishes entering
         * @prop {Function} onCloseStart - Function called when sidenav starts exiting
         * @prop {Function} onCloseEnd - Function called when sidenav finishes exiting
         */
        this.options = $.extend({}, Sidenav.defaults, options);
  
        /**
         * Describes open/close state of Sidenav
         * @type {Boolean}
         */
        this.isOpen = false;
  
        /**
         * Describes if Sidenav is fixed
         * @type {Boolean}
         */
        this.isFixed = this.el.classList.contains('sidenav-fixed');
  
        /**
         * Describes if Sidenav is being draggeed
         * @type {Boolean}
         */
        this.isDragged = false;
  
        // Window size variables for window resize checks
        this.lastWindowWidth = window.innerWidth;
        this.lastWindowHeight = window.innerHeight;
  
        this._createOverlay();
        this._createDragTarget();
        this._setupEventHandlers();
        this._setupClasses();
        this._setupFixed();
  
        Sidenav._sidenavs.push(this);
      }
  
      static get defaults() {
        return _defaults;
      }
  
      static init(els, options) {
        return super.init(this, els, options);
      }
  
      /**
       * Get Instance
       */
      static getInstance(el) {
        let domElem = !!el.jquery ? el[0] : el;
        return domElem.M_Sidenav;
      }
  
      /**
       * Teardown component
       */
      destroy() {
        this._removeEventHandlers();
        this._enableBodyScrolling();
        this._overlay.parentNode.removeChild(this._overlay);
        this.dragTarget.parentNode.removeChild(this.dragTarget);
        this.el.M_Sidenav = undefined;
        this.el.style.transform = '';
  
        let index = Sidenav._sidenavs.indexOf(this);
        if (index >= 0) {
          Sidenav._sidenavs.splice(index, 1);
        }
      }
  
      _createOverlay() {
        let overlay = document.createElement('div');
        this._closeBound = this.close.bind(this);
        overlay.classList.add('sidenav-overlay');
  
        overlay.addEventListener('click', this._closeBound);
  
        document.body.appendChild(overlay);
        this._overlay = overlay;
      }
  
      _setupEventHandlers() {
        if (Sidenav._sidenavs.length === 0) {
          document.body.addEventListener('click', this._handleTriggerClick);
        }
  
        this._handleDragTargetDragBound = this._handleDragTargetDrag.bind(this);
        this._handleDragTargetReleaseBound = this._handleDragTargetRelease.bind(this);
        this._handleCloseDragBound = this._handleCloseDrag.bind(this);
        this._handleCloseReleaseBound = this._handleCloseRelease.bind(this);
        this._handleCloseTriggerClickBound = this._handleCloseTriggerClick.bind(this);
  
        this.dragTarget.addEventListener('touchmove', this._handleDragTargetDragBound, passiveIfSupported);
        this.dragTarget.addEventListener('touchend', this._handleDragTargetReleaseBound);
        this._overlay.addEventListener('touchmove', this._handleCloseDragBound, passiveIfSupported);
        this._overlay.addEventListener('touchend', this._handleCloseReleaseBound);
        this.el.addEventListener('touchmove', this._handleCloseDragBound, passiveIfSupported);
        this.el.addEventListener('touchend', this._handleCloseReleaseBound);
        this.el.addEventListener('click', this._handleCloseTriggerClickBound);
  
        // Add resize for side nav fixed
        if (this.isFixed) {
          this._handleWindowResizeBound = this._handleWindowResize.bind(this);
          window.addEventListener('resize', this._handleWindowResizeBound);
        }
      }
  
      _removeEventHandlers() {
        if (Sidenav._sidenavs.length === 1) {
          document.body.removeEventListener('click', this._handleTriggerClick);
        }
  
        this.dragTarget.removeEventListener('touchmove', this._handleDragTargetDragBound);
        this.dragTarget.removeEventListener('touchend', this._handleDragTargetReleaseBound);
        this._overlay.removeEventListener('touchmove', this._handleCloseDragBound);
        this._overlay.removeEventListener('touchend', this._handleCloseReleaseBound);
        this.el.removeEventListener('touchmove', this._handleCloseDragBound);
        this.el.removeEventListener('touchend', this._handleCloseReleaseBound);
        this.el.removeEventListener('click', this._handleCloseTriggerClickBound);
  
        // Remove resize for side nav fixed
        if (this.isFixed) {
          window.removeEventListener('resize', this._handleWindowResizeBound);
        }
      }
  
      /**
       * Handle Trigger Click
       * @param {Event} e
       */
      _handleTriggerClick(e) {
        let $trigger = $(e.target).closest('.sidenav-trigger');
        if (e.target && $trigger.length) {
          let sidenavId = M.getIdFromTrigger($trigger[0]);
  
          let sidenavInstance = document.getElementById(sidenavId).M_Sidenav;
          if (sidenavInstance) {
            sidenavInstance.open($trigger);
          }
          e.preventDefault();
        }
      }
  
      /**
       * Set variables needed at the beginning of drag
       * and stop any current transition.
       * @param {Event} e
       */
      _startDrag(e) {
        let clientX = e.targetTouches[0].clientX;
        this.isDragged = true;
        this._startingXpos = clientX;
        this._xPos = this._startingXpos;
        this._time = Date.now();
        this._width = this.el.getBoundingClientRect().width;
        this._overlay.style.display = 'block';
        this._initialScrollTop = this.isOpen ? this.el.scrollTop : M.getDocumentScrollTop();
        this._verticallyScrolling = false;
        anim.remove(this.el);
        anim.remove(this._overlay);
      }
  
      /**
       * Set variables needed at each drag move update tick
       * @param {Event} e
       */
      _dragMoveUpdate(e) {
        let clientX = e.targetTouches[0].clientX;
        let currentScrollTop = this.isOpen ? this.el.scrollTop : M.getDocumentScrollTop();
        this.deltaX = Math.abs(this._xPos - clientX);
        this._xPos = clientX;
        this.velocityX = this.deltaX / (Date.now() - this._time);
        this._time = Date.now();
        if (this._initialScrollTop !== currentScrollTop) {
          this._verticallyScrolling = true;
        }
      }
  
      /**
       * Handles Dragging of Sidenav
       * @param {Event} e
       */
      _handleDragTargetDrag(e) {
        // Check if draggable
        if (!this.options.draggable || this._isCurrentlyFixed() || this._verticallyScrolling) {
          return;
        }
  
        // If not being dragged, set initial drag start variables
        if (!this.isDragged) {
          this._startDrag(e);
        }
  
        // Run touchmove updates
        this._dragMoveUpdate(e);
  
        // Calculate raw deltaX
        let totalDeltaX = this._xPos - this._startingXpos;
  
        // dragDirection is the attempted user drag direction
        let dragDirection = totalDeltaX > 0 ? 'right' : 'left';
  
        // Don't allow totalDeltaX to exceed Sidenav width or be dragged in the opposite direction
        totalDeltaX = Math.min(this._width, Math.abs(totalDeltaX));
        if (this.options.edge === dragDirection) {
          totalDeltaX = 0;
        }
  
        /**
         * transformX is the drag displacement
         * transformPrefix is the initial transform placement
         * Invert values if Sidenav is right edge
         */
        let transformX = totalDeltaX;
        let transformPrefix = 'translateX(-100%)';
        if (this.options.edge === 'right') {
          transformPrefix = 'translateX(100%)';
          transformX = -transformX;
        }
  
        // Calculate open/close percentage of sidenav, with open = 1 and close = 0
        this.percentOpen = Math.min(1, totalDeltaX / this._width);
  
        // Set transform and opacity styles
        this.el.style.transform = `${transformPrefix} translateX(${transformX}px)`;
        this._overlay.style.opacity = this.percentOpen;
      }
  
      /**
       * Handle Drag Target Release
       */
      _handleDragTargetRelease() {
        if (this.isDragged) {
          if (this.percentOpen > 0.2) {
            this.open();
          } else {
            this._animateOut();
          }
  
          this.isDragged = false;
          this._verticallyScrolling = false;
        }
      }
  
      /**
       * Handle Close Drag
       * @param {Event} e
       */
      _handleCloseDrag(e) {
        if (this.isOpen) {
          // Check if draggable
          if (!this.options.draggable || this._isCurrentlyFixed() || this._verticallyScrolling) {
            return;
          }
  
          // If not being dragged, set initial drag start variables
          if (!this.isDragged) {
            this._startDrag(e);
          }
  
          // Run touchmove updates
          this._dragMoveUpdate(e);
  
          // Calculate raw deltaX
          let totalDeltaX = this._xPos - this._startingXpos;
  
          // dragDirection is the attempted user drag direction
          let dragDirection = totalDeltaX > 0 ? 'right' : 'left';
  
          // Don't allow totalDeltaX to exceed Sidenav width or be dragged in the opposite direction
          totalDeltaX = Math.min(this._width, Math.abs(totalDeltaX));
          if (this.options.edge !== dragDirection) {
            totalDeltaX = 0;
          }
  
          let transformX = -totalDeltaX;
          if (this.options.edge === 'right') {
            transformX = -transformX;
          }
  
          // Calculate open/close percentage of sidenav, with open = 1 and close = 0
          this.percentOpen = Math.min(1, 1 - totalDeltaX / this._width);
  
          // Set transform and opacity styles
          this.el.style.transform = `translateX(${transformX}px)`;
          this._overlay.style.opacity = this.percentOpen;
        }
      }
  
      /**
       * Handle Close Release
       */
      _handleCloseRelease() {
        if (this.isOpen && this.isDragged) {
          if (this.percentOpen > 0.8) {
            this._animateIn();
          } else {
            this.close();
          }
  
          this.isDragged = false;
          this._verticallyScrolling = false;
        }
      }
  
      /**
       * Handles closing of Sidenav when element with class .sidenav-close
       */
      _handleCloseTriggerClick(e) {
        let $closeTrigger = $(e.target).closest('.sidenav-close');
        if ($closeTrigger.length && !this._isCurrentlyFixed()) {
          this.close();
        }
      }
  
      /**
       * Handle Window Resize
       */
      _handleWindowResize() {
        // Only handle horizontal resizes
        if (this.lastWindowWidth !== window.innerWidth) {
          if (window.innerWidth > 992) {
            this.open();
          } else {
            this.close();
          }
        }
  
        this.lastWindowWidth = window.innerWidth;
        this.lastWindowHeight = window.innerHeight;
      }
  
      _setupClasses() {
        if (this.options.edge === 'right') {
          this.el.classList.add('right-aligned');
          this.dragTarget.classList.add('right-aligned');
        }
      }
  
      _removeClasses() {
        this.el.classList.remove('right-aligned');
        this.dragTarget.classList.remove('right-aligned');
      }
  
      _setupFixed() {
        if (this._isCurrentlyFixed()) {
          this.open();
        }
      }
  
      _isCurrentlyFixed() {
        return this.isFixed && window.innerWidth > 992;
      }
  
      _createDragTarget() {
        let dragTarget = document.createElement('div');
        dragTarget.classList.add('drag-target');
        dragTarget.style.width = this.options.dragTargetWidth;
        document.body.appendChild(dragTarget);
        this.dragTarget = dragTarget;
      }
  
      _preventBodyScrolling() {
        let body = document.body;
        body.style.overflow = 'hidden';
      }
  
      _enableBodyScrolling() {
        let body = document.body;
        body.style.overflow = '';
      }
  
      open() {
        if (this.isOpen === true) {
          return;
        }
  
        this.isOpen = true;
  
        // Run onOpenStart callback
        if (typeof this.options.onOpenStart === 'function') {
          this.options.onOpenStart.call(this, this.el);
        }
  
        // Handle fixed Sidenav
        if (this._isCurrentlyFixed()) {
          anim.remove(this.el);
          anim({
            targets: this.el,
            translateX: 0,
            duration: 0,
            easing: 'easeOutQuad'
          });
          this._enableBodyScrolling();
          this._overlay.style.display = 'none';
  
          // Handle non-fixed Sidenav
        } else {
          if (this.options.preventScrolling) {
            this._preventBodyScrolling();
          }
  
          if (!this.isDragged || this.percentOpen != 1) {
            this._animateIn();
          }
        }
      }
  
      close() {
        if (this.isOpen === false) {
          return;
        }
  
        this.isOpen = false;
  
        // Run onCloseStart callback
        if (typeof this.options.onCloseStart === 'function') {
          this.options.onCloseStart.call(this, this.el);
        }
  
        // Handle fixed Sidenav
        if (this._isCurrentlyFixed()) {
          let transformX = this.options.edge === 'left' ? '-105%' : '105%';
          this.el.style.transform = `translateX(${transformX})`;
  
          // Handle non-fixed Sidenav
        } else {
          this._enableBodyScrolling();
  
          if (!this.isDragged || this.percentOpen != 0) {
            this._animateOut();
          } else {
            this._overlay.style.display = 'none';
          }
        }
      }
  
      _animateIn() {
        this._animateSidenavIn();
        this._animateOverlayIn();
      }
  
      _animateSidenavIn() {
        let slideOutPercent = this.options.edge === 'left' ? -1 : 1;
        if (this.isDragged) {
          slideOutPercent =
            this.options.edge === 'left'
              ? slideOutPercent + this.percentOpen
              : slideOutPercent - this.percentOpen;
        }
  
        anim.remove(this.el);
        anim({
          targets: this.el,
          translateX: [`${slideOutPercent * 100}%`, 0],
          duration: this.options.inDuration,
          easing: 'easeOutQuad',
          complete: () => {
            // Run onOpenEnd callback
            if (typeof this.options.onOpenEnd === 'function') {
              this.options.onOpenEnd.call(this, this.el);
            }
          }
        });
      }
  
      _animateOverlayIn() {
        let start = 0;
        if (this.isDragged) {
          start = this.percentOpen;
        } else {
          $(this._overlay).css({
            display: 'block'
          });
        }
  
        anim.remove(this._overlay);
        anim({
          targets: this._overlay,
          opacity: [start, 1],
          duration: this.options.inDuration,
          easing: 'easeOutQuad'
        });
      }
  
      _animateOut() {
        this._animateSidenavOut();
        this._animateOverlayOut();
      }
  
      _animateSidenavOut() {
        let endPercent = this.options.edge === 'left' ? -1 : 1;
        let slideOutPercent = 0;
        if (this.isDragged) {
          slideOutPercent =
            this.options.edge === 'left'
              ? endPercent + this.percentOpen
              : endPercent - this.percentOpen;
        }
  
        anim.remove(this.el);
        anim({
          targets: this.el,
          translateX: [`${slideOutPercent * 100}%`, `${endPercent * 105}%`],
          duration: this.options.outDuration,
          easing: 'easeOutQuad',
          complete: () => {
            // Run onOpenEnd callback
            if (typeof this.options.onCloseEnd === 'function') {
              this.options.onCloseEnd.call(this, this.el);
            }
          }
        });
      }
  
      _animateOverlayOut() {
        anim.remove(this._overlay);
        anim({
          targets: this._overlay,
          opacity: 0,
          duration: this.options.outDuration,
          easing: 'easeOutQuad',
          complete: () => {
            $(this._overlay).css('display', 'none');
          }
        });
      }
    }
  
    /**
     * @static
     * @memberof Sidenav
     * @type {Array.<Sidenav>}
     */
    Sidenav._sidenavs = [];
  
    M.Sidenav = Sidenav;
  
    if (M.jQueryLoaded) {
      M.initializeJqueryWrapper(Sidenav, 'sidenav', 'M_Sidenav');
    }
  })(cash, M.anime);
  