<!--
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->

<link rel="import" href="../polymer/polymer.html">
<link rel="import" href="../iron-media-query/iron-media-query.html">
<link rel="import" href="../iron-selector/iron-selector.html">

<!--
`paper-drawer-panel` contains a drawer panel and a main panel.  The drawer
and the main panel are side-by-side with drawer on the left.  When the browser
window size is smaller than the `responsiveWidth`, `paper-drawer-panel`
changes to narrow layout.  In narrow layout, the drawer will be stacked on top
of the main panel.  The drawer will slide in/out to hide/reveal the main
panel.

Use the attribute `drawer` to indicate that the element is the drawer panel and
`main` to indicate that the element is the main panel.

Example:

    <paper-drawer-panel>
      <div drawer> Drawer panel... </div>
      <div main> Main panel... </div>
    </paper-drawer-panel>

The drawer and the main panels are not scrollable.  You can set CSS overflow
property on the elements to make them scrollable or use `paper-header-panel`.

Example:

    <paper-drawer-panel>
      <paper-header-panel drawer>
        <paper-toolbar></paper-toolbar>
        <div> Drawer content... </div>
      </paper-header-panel>
      <paper-header-panel main>
        <paper-toolbar></paper-toolbar>
        <div> Main content... </div>
      </paper-header-panel>
    </paper-drawer-panel>

An element that should toggle the drawer will automatically do so if it's
given the `paper-drawer-toggle` attribute.  Also this element will automatically
be hidden in wide layout.

Example:

    <paper-drawer-panel>
      <paper-header-panel drawer>
        <paper-toolbar>
          <div>Application</div>
        </paper-toolbar>
        <div> Drawer content... </div>
      </paper-header-panel>
      <paper-header-panel main>
        <paper-toolbar>
          <paper-icon-button icon="menu" paper-drawer-toggle></paper-icon-button>
          <div>Title</div>
        </paper-toolbar>
        <div> Main content... </div>
      </paper-header-panel>
    </paper-drawer-panel>

To position the drawer to the right, add `right-drawer` attribute.

    <paper-drawer-panel right-drawer>
      <div drawer> Drawer panel... </div>
      <div main> Main panel... </div>
    </paper-drawer-panel>

Styling `paper-drawer-panel`

To change the main container:

    paper-drawer-panel {
      --paper-drawer-panel-main-container: {
        background-color: gray;
      };
    }

To change the drawer container when it's in the left side:

    paper-drawer-panel {
      --paper-drawer-panel-left-drawer-container: {
        background-color: white;
      };
    }

To change the drawer container when it's in the right side:

    paper-drawer-panel {
      --paper-drawer-panel-right-drawer-container: {
        background-color: white;
      };
    }

@group Paper elements
@element paper-drawer-panel
@demo demo/index.html
@hero hero.svg
-->

<dom-module id="paper-drawer-panel">
  <link rel="import" type="css" href="paper-drawer-panel.css">

  <template>
    <iron-media-query
        id="mq"
        on-query-matches-changed="_onQueryMatchesChanged"
        query="[[_computeMediaQuery(forceNarrow, responsiveWidth)]]">
    </iron-media-query>

    <iron-selector
        attr-for-selected="id"
        class$="[[_computeIronSelectorClass(narrow, transition, dragging, rightDrawer, peeking)]]"
        activate-event=""
        selected="[[selected]]">

      <div id="main" style$="[[_computeMainStyle(narrow, rightDrawer, drawerWidth)]]">
        <content select="[main]"></content>
        <div id="scrim" on-tap="closeDrawer"></div>
      </div>

      <div id="drawer" style$="[[_computeDrawerStyle(drawerWidth)]]">
        <content select="[drawer]"></content>
      </div>

    </iron-selector>
  </template>

</dom-module>

<script>

  (function() {

    'use strict';

   // this would be the only `paper-drawer-panel` in
   // the whole app that can be in `dragging` state
    var sharedPanel = null;

    function classNames(obj) {
      var classes = [];
      for (var key in obj) {
        if (obj.hasOwnProperty(key) && obj[key]) {
          classes.push(key);
        }
      }

      return classes.join(' ');
    }

    Polymer({

      is: 'paper-drawer-panel',

      /**
       * Fired when the narrow layout changes.
       *
       * @event paper-responsive-change {{narrow: boolean}} detail -
       *     narrow: true if the panel is in narrow layout.
       */

      /**
       * Fired when the a panel is selected.
       *
       * Listening for this event is an alternative to observing changes in the `selected` attribute.
       * This event is fired both when a panel is selected.
       *
       * @event iron-select {{item: Object}} detail -
       *     item: The panel that the event refers to.
       */

      /**
       * Fired when a panel is deselected.
       *
       * Listening for this event is an alternative to observing changes in the `selected` attribute.
       * This event is fired both when a panel is deselected.
       *
       * @event iron-deselect {{item: Object}} detail -
       *     item: The panel that the event refers to.
       */
      properties: {

        /**
         * The panel to be selected when `paper-drawer-panel` changes to narrow
         * layout.
         */
        defaultSelected: {
          type: String,
          value: 'main'
        },

        /**
         * If true, swipe from the edge is disable.
         */
        disableEdgeSwipe: {
          type: Boolean,
          value: false
        },

        /**
         * If true, swipe to open/close the drawer is disabled.
         */
        disableSwipe: {
          type: Boolean,
          value: false
        },

        /**
         * Whether the user is dragging the drawer interactively.
         */
        dragging: {
          type: Boolean,
          value: false,
          readOnly: true,
          notify: true
        },

        /**
         * Width of the drawer panel.
         */
        drawerWidth: {
          type: String,
          value: '256px'
        },

        /**
         * How many pixels on the side of the screen are sensitive to edge
         * swipes and peek.
         */
        edgeSwipeSensitivity: {
          type: Number,
          value: 30
        },

        /**
         * If true, ignore `responsiveWidth` setting and force the narrow layout.
         */
        forceNarrow: {
          type: Boolean,
          value: false
        },

        /**
         * Whether the browser has support for the transform CSS property.
         */
        hasTransform: {
          type: Boolean,
          value: function() {
            return 'transform' in this.style;
          }
        },

        /**
         * Whether the browser has support for the will-change CSS property.
         */
        hasWillChange: {
          type: Boolean,
          value: function() {
            return 'willChange' in this.style;
          }
        },

        /**
         * Returns true if the panel is in narrow layout.  This is useful if you
         * need to show/hide elements based on the layout.
         */
        narrow: {
          reflectToAttribute: true,
          type: Boolean,
          value: false,
          readOnly: true,
          notify: true
        },

        /**
         * Whether the drawer is peeking out from the edge.
         */
        peeking: {
          type: Boolean,
          value: false,
          readOnly: true,
          notify: true
        },

        /**
         * Max-width when the panel changes to narrow layout.
         */
        responsiveWidth: {
          type: String,
          value: '640px'
        },

        /**
         * If true, position the drawer to the right.
         */
        rightDrawer: {
          type: Boolean,
          value: false
        },

        /**
         * The panel that is being selected. `drawer` for the drawer panel and
         * `main` for the main panel.
         */
        selected: {
          reflectToAttribute: true,
          notify: true,
          type: String,
          value: null
        },

        /**
         * The attribute on elements that should toggle the drawer on tap, also elements will
         * automatically be hidden in wide layout.
         */
        drawerToggleAttribute: {
          type: String,
          value: 'paper-drawer-toggle'
        },

        /**
         * Whether the transition is enabled.
         */
        transition: {
          type: Boolean,
          value: false
        },

      },

      listeners: {
        tap: '_onTap',
        track: '_onTrack',
        down: '_downHandler',
        up: '_upHandler'
      },

      observers: [
        '_forceNarrowChanged(forceNarrow, defaultSelected)'
      ],

      /**
       * Toggles the panel open and closed.
       *
       * @method togglePanel
       */
      togglePanel: function() {
        if (this._isMainSelected()) {
          this.openDrawer();
        } else {
          this.closeDrawer();
        }
      },

      /**
       * Opens the drawer.
       *
       * @method openDrawer
       */
      openDrawer: function() {
        this.selected = 'drawer';
      },

      /**
       * Closes the drawer.
       *
       * @method closeDrawer
       */
      closeDrawer: function() {
        this.selected = 'main';
      },

      ready: function() {
        // Avoid transition at the beginning e.g. page loads and enable
        // transitions only after the element is rendered and ready.
        this.transition = true;
      },

      _computeIronSelectorClass: function(narrow, transition, dragging, rightDrawer, peeking) {
        return classNames({
          dragging: dragging,
          'narrow-layout': narrow,
          'right-drawer': rightDrawer,
          'left-drawer': !rightDrawer,
          transition: transition,
          peeking: peeking
        });
      },

      _computeDrawerStyle: function(drawerWidth) {
        return 'width:' + drawerWidth + ';';
      },

      _computeMainStyle: function(narrow, rightDrawer, drawerWidth) {
        var style = '';

        style += 'left:' + ((narrow || rightDrawer) ? '0' : drawerWidth) + ';';

        if (rightDrawer) {
          style += 'right:' + (narrow ? '' : drawerWidth) + ';';
        }

        return style;
      },

      _computeMediaQuery: function(forceNarrow, responsiveWidth) {
        return forceNarrow ? '' : '(max-width: ' + responsiveWidth + ')';
      },

      _computeSwipeOverlayHidden: function(narrow, disableEdgeSwipe) {
        return !narrow || disableEdgeSwipe;
      },

      _onTrack: function(event) {
        if (sharedPanel && this !== sharedPanel) {
          return;
        }
        switch (event.detail.state) {
          case 'start':
            this._trackStart(event);
            break;
          case 'track':
            this._trackX(event);
            break;
          case 'end':
            this._trackEnd(event);
            break;
        }

      },

      _responsiveChange: function(narrow) {
        this._setNarrow(narrow);

        if (this.narrow) {
          this.selected = this.defaultSelected;
        }

        this.setScrollDirection(this._swipeAllowed() ? 'y' : 'all');
        this.fire('paper-responsive-change', {narrow: this.narrow});
      },

      _onQueryMatchesChanged: function(event) {
        this._responsiveChange(event.detail.value);
      },

      _forceNarrowChanged: function() {
        // set the narrow mode only if we reached the `responsiveWidth`
        this._responsiveChange(this.forceNarrow || this.$.mq.queryMatches);
      },

      _swipeAllowed: function() {
        return this.narrow && !this.disableSwipe;
      },

      _isMainSelected: function() {
        return this.selected === 'main';
      },

      _startEdgePeek: function() {
        this.width = this.$.drawer.offsetWidth;
        this._moveDrawer(this._translateXForDeltaX(this.rightDrawer ?
            -this.edgeSwipeSensitivity : this.edgeSwipeSensitivity));
        this._setPeeking(true);
      },

      _stopEdgePeek: function() {
        if (this.peeking) {
          this._setPeeking(false);
          this._moveDrawer(null);
        }
      },

      _downHandler: function(event) {
        if (!this.dragging && this._isMainSelected() && this._isEdgeTouch(event) && !sharedPanel) {
          this._startEdgePeek();
          // cancel selection
          event.preventDefault();
          // grab this panel
          sharedPanel = this;
        }
      },

      _upHandler: function() {
        this._stopEdgePeek();
        // release the panel
        sharedPanel = null;
      },

      _onTap: function(event) {
        var targetElement = Polymer.dom(event).localTarget;
        var isTargetToggleElement = targetElement &&
          this.drawerToggleAttribute &&
          targetElement.hasAttribute(this.drawerToggleAttribute);

        if (isTargetToggleElement) {
          this.togglePanel();
        }
      },

      _isEdgeTouch: function(event) {
        var x = event.detail.x;

        return !this.disableEdgeSwipe && this._swipeAllowed() &&
          (this.rightDrawer ?
            x >= this.offsetWidth - this.edgeSwipeSensitivity :
            x <= this.edgeSwipeSensitivity);
      },

      _trackStart: function(event) {
        if (this._swipeAllowed()) {
          sharedPanel = this;
          this._setDragging(true);

          if (this._isMainSelected()) {
            this._setDragging(this.peeking || this._isEdgeTouch(event));
          }

          if (this.dragging) {
            this.width = this.$.drawer.offsetWidth;
            this.transition = false;
          }
        }
      },

      _translateXForDeltaX: function(deltaX) {
        var isMain = this._isMainSelected();

        if (this.rightDrawer) {
          return Math.max(0, isMain ? this.width + deltaX : deltaX);
        } else {
          return Math.min(0, isMain ? deltaX - this.width : deltaX);
        }
      },

      _trackX: function(event) {
        if (this.dragging) {
          var dx = event.detail.dx;

          if (this.peeking) {
            if (Math.abs(dx) <= this.edgeSwipeSensitivity) {
              // Ignore trackx until we move past the edge peek.
              return;
            }
            this._setPeeking(false);
          }

          this._moveDrawer(this._translateXForDeltaX(dx));
        }
      },

      _trackEnd: function(event) {
        if (this.dragging) {
          var xDirection = event.detail.dx > 0;

          this._setDragging(false);
          this.transition = true;
          sharedPanel = null;
          this._moveDrawer(null);

          if (this.rightDrawer) {
            this[xDirection ? 'closeDrawer' : 'openDrawer']();
          } else {
            this[xDirection ? 'openDrawer' : 'closeDrawer']();
          }
        }
      },

      _transformForTranslateX: function(translateX) {
        if (translateX === null) {
          return '';
        }

        return this.hasWillChange ? 'translateX(' + translateX + 'px)' :
            'translate3d(' + translateX + 'px, 0, 0)';
      },

      _moveDrawer: function(translateX) {
        this.transform(this._transformForTranslateX(translateX), this.$.drawer);
      }

    });

  }());

</script>