]> git.ipfire.org Git - thirdparty/foundation/foundation-sites.git/commitdiff
updating parts of reveal modal to better be able to position itself on open and resiz...
authorChris Oyler <christopher.oyler@gmail.com>
Thu, 28 Jan 2016 17:22:46 +0000 (09:22 -0800)
committerChris Oyler <christopher.oyler@gmail.com>
Thu, 28 Jan 2016 17:22:46 +0000 (09:22 -0800)
js/foundation.new-reveal.js [new file with mode: 0644]
scss/components/_reveal.scss

diff --git a/js/foundation.new-reveal.js b/js/foundation.new-reveal.js
new file mode 100644 (file)
index 0000000..ecaa934
--- /dev/null
@@ -0,0 +1,522 @@
+/**
+ * Reveal module.
+ * @module foundation.reveal
+ * @requires foundation.util.keyboard
+ * @requires foundation.util.box
+ * @requires foundation.util.triggers
+ * @requires foundation.util.mediaQuery
+ * @requires foundation.util.motion if using animations
+ */
+!function(Foundation, $) {
+  'use strict';
+
+  /**
+   * Creates a new instance of Reveal.
+   * @class
+   * @param {jQuery} element - jQuery object to use for the modal.
+   * @param {Object} options - optional parameters.
+   */
+
+  function Reveal(element, options) {
+    this.$element = element;
+    this.options = $.extend({}, Reveal.defaults, this.$element.data(), options);
+    this._init();
+
+    Foundation.registerPlugin(this, 'Reveal');
+    // Foundation.Keyboard.register('Reveal', {
+    //   'ENTER': 'open',
+    //   'SPACE': 'open',
+    //   'ESCAPE': 'close',
+    //   'TAB': 'tab_forward',
+    //   'SHIFT_TAB': 'tab_backward'
+    // });
+  }
+
+  Reveal.defaults = {
+    /**
+     * Motion-UI class to use for animated elements. If none used, defaults to simple show/hide.
+     * @option
+     * @example 'slide-in-left'
+     */
+    animationIn: '',
+    /**
+     * Motion-UI class to use for animated elements. If none used, defaults to simple show/hide.
+     * @option
+     * @example 'slide-out-right'
+     */
+    animationOut: '',
+    /**
+     * Time, in ms, to delay the opening of a modal after a click if no animation used.
+     * @option
+     * @example 10
+     */
+    showDelay: 0,
+    /**
+     * Time, in ms, to delay the closing of a modal after a click if no animation used.
+     * @option
+     * @example 10
+     */
+    hideDelay: 0,
+    /**
+     * Allows a click on the body/overlay to close the modal.
+     * @option
+     * @example true
+     */
+    closeOnClick: true,
+    /**
+     * Allows the modal to close if the user presses the `ESCAPE` key.
+     * @option
+     * @example true
+     */
+    closeOnEsc: true,
+    /**
+     * If true, allows multiple modals to be displayed at once.
+     * @option
+     * @example false
+     */
+    multipleOpened: false,
+    /**
+     * Distance, in pixels, the modal should push down from the top of the screen.
+     * @option
+     * @example 100
+     */
+    vOffset: 100,
+    /**
+     * Distance, in pixels, the modal should push in from the side of the screen.
+     * @option
+     * @example 0
+     */
+    hOffset: 0,
+    /**
+     * Allows the modal to be fullscreen, completely blocking out the rest of the view. JS checks for this as well.
+     * @option
+     * @example false
+     */
+    fullScreen: false,
+    /**
+     * Percentage of screen height the modal should push up from the bottom of the view.
+     * @option
+     * @example 10
+     */
+    btmOffsetPct: 10,
+    /**
+     * Allows the modal to generate an overlay div, which will cover the view when modal opens.
+     * @option
+     * @example true
+     */
+    overlay: true,
+    /**
+     * Allows the modal to remove and reinject markup on close. Should be true if using video elements w/o using provider's api, otherwise, videos will continue to play in the background.
+     * @option
+     * @example false
+     */
+    resetOnClose: false,
+    /**
+     * Allows the modal to alter the url on open/close, and allows the use of the `back` button to close modals. ALSO, allows a modal to auto-maniacally open on page load IF the hash === the modal's user-set id.
+     * @option
+     * @example false
+     */
+    deepLink: false
+  };
+
+  /**
+   * Initializes the modal by adding the overlay and close buttons, (if selected).
+   * @private
+   */
+  Reveal.prototype._init = function(){
+    this.id = this.$element.attr('id');
+    this.isActive = false;
+
+    this.$anchor = $('[data-open="' + this.id + '"]').length ? $('[data-open="' + this.id + '"]') : $('[data-toggle="' + this.id + '"]');
+
+    if(this.$anchor.length){
+      var anchorId = this.$anchor[0].id || Foundation.GetYoDigits(6, 'reveal');
+
+      this.$anchor.attr({
+        'aria-controls': this.id,
+        'id': anchorId,
+        'aria-haspopup': true,
+        'tabindex': 0
+      });
+      this.$element.attr({'aria-labelledby': anchorId});
+    }
+
+    // this.options.fullScreen = this.$element.hasClass('full');
+    if(this.options.fullScreen || this.$element.hasClass('full')){
+      this.options.fullScreen = true;
+      this.options.overlay = false;
+    }
+    if(this.options.overlay && !this.$overlay){
+      this.$overlay = this._makeOverlay(this.id);
+    }
+
+    this.$element.attr({
+        'role': 'dialog',
+        'aria-hidden': true,
+        'data-yeti-box': this.id,
+        'data-resize': this.id
+    });
+
+    this._events();
+    if(this.options.deepLink && window.location.hash === ( '#' + this.id)){
+      $(window).one('load.zf.reveal', this.open.bind(this));
+    }
+  };
+
+  /**
+   * Creates an overlay div to display behind the modal.
+   * @private
+   */
+  Reveal.prototype._makeOverlay = function(id){
+    var $overlay = $('<div></div>')
+                    .addClass('reveal-overlay')
+                    .attr({'tabindex': -1, 'aria-hidden': true})
+                    .appendTo('body');
+    if(this.options.closeOnClick){
+      $overlay.attr({
+        'data-close': id
+      });
+    }
+    return $overlay;
+  };
+
+  /**
+   * Adds event handlers for the modal.
+   * @private
+   */
+  Reveal.prototype._events = function(){
+    var _this = this;
+
+    this.$element.on({
+      'open.zf.trigger': this.open.bind(this),
+      'close.zf.trigger': this.close.bind(this),
+      'toggle.zf.trigger': this.toggle.bind(this),
+      'resizeme.zf.trigger': function(){
+        if(_this.$element.is(':visible')){
+          _this._setPosition(function(){});
+        }
+      }
+    });
+
+    if(this.$anchor.length){
+      this.$anchor.on('keydown.zf.reveal', function(e){
+        if(e.which === 13 || e.which === 32){
+          e.stopPropagation();
+          e.preventDefault();
+          _this.open();
+        }
+      });
+    }
+
+
+    if(this.options.closeOnClick && this.options.overlay){
+      this.$overlay.off('.zf.reveal').on('click.zf.reveal', this.close.bind(this));
+    }
+    if(this.options.deepLink){
+      $(window).on('popstate.zf.reveal:' + this.id, this._handleState.bind(this));
+    }
+  };
+  /**
+   * Handles modal methods on back/forward button clicks or any other event that triggers popstate.
+   * @private
+   */
+  Reveal.prototype._handleState = function(e){
+    if(window.location.hash === ( '#' + this.id) && !this.isActive){ this.open(); }
+    else{ this.close(); }
+  };
+  /**
+   * Sets the position of the modal before opening
+   * @param {Function} cb - a callback function to execute when positioning is complete.
+   * @private
+   */
+  Reveal.prototype._setPosition = function(cb){
+    console.log('calling');
+    var winWidth = window.innerWidth,
+        parentOffset = this.$element.offsetParent().offset(),
+        x = Math.round((winWidth - this.$element[0].getBoundingClientRect().width) / 2 - (parentOffset.left > 0 ? parentOffset.left : 0)),
+        y = Math.round(window.pageYOffset - (parentOffset.top > 0 ? parentOffset.top : 0) + this.options.vOffset);
+        
+    this.$element.css('transform', 'translate(' + x + 'px, ' + y + 'px)');
+    if(cb) cb();
+    // var eleDims = Foundation.Box.GetDimensions(this.$element);
+    // var elePos = this.options.fullScreen ? 'reveal full' : (eleDims.height >= (0.5 * eleDims.windowDims.height)) ? 'reveal' : 'center';
+    //
+    // if(elePos === 'reveal full'){
+    //   //set to full height/width
+    //   this.$element
+    //       .offset(Foundation.Box.GetOffsets(this.$element, null, elePos, this.options.vOffset))
+    //       .css({
+    //         'height': eleDims.windowDims.height,
+    //         'width': eleDims.windowDims.width
+    //       });
+    // }else if(!Foundation.MediaQuery.atLeast('medium') || !Foundation.Box.ImNotTouchingYou(this.$element, null, true, false)){
+    //   //if smaller than medium, resize to 100% width minus any custom L/R margin
+    //   this.$element
+    //       .css({
+    //         'width': eleDims.windowDims.width - (this.options.hOffset * 2)
+    //       })
+    //       .offset(Foundation.Box.GetOffsets(this.$element, null, 'center', this.options.vOffset, this.options.hOffset));
+    //   //flag a boolean so we can reset the size after the element is closed.
+    //   this.changedSize = true;
+    // }else{
+    //   this.$element
+    //       .css({
+    //         'max-height': eleDims.windowDims.height - (this.options.vOffset * (this.options.btmOffsetPct / 100 + 1)),
+    //         'width': ''
+    //       })
+    //       .offset(Foundation.Box.GetOffsets(this.$element, null, elePos, this.options.vOffset));
+    //       //the max height based on a percentage of vertical offset plus vertical offset
+    // }
+    //
+    // cb();
+  };
+
+  /**
+   * Opens the modal controlled by `this.$anchor`, and closes all others by default.
+   * @function
+   * @fires Reveal#closeme
+   * @fires Reveal#open
+   */
+  Reveal.prototype.open = function(){
+    if(this.options.deepLink){
+      var hash = '#' + this.id;
+    
+      if(window.history.pushState){
+        window.history.pushState(null, null, hash);
+      }else{
+        window.location.hash = hash;
+      }
+    }
+
+    var _this = this;
+    this.isActive = true;
+    //make element invisible, but remove display: none so we can get size and positioning
+    this.$element
+        .css({'visibility': 'hidden'})
+        .show()
+        // .scrollTop(0);
+
+    this._setPosition(function(){
+      _this.$element.hide()
+                   .css({'visibility': ''});
+      if(!_this.options.multipleOpened){
+        /**
+         * Fires immediately before the modal opens.
+         * Closes any other modals that are currently open
+         * @event Reveal#closeme
+         */
+        _this.$element.trigger('closeme.zf.reveal', _this.id);
+      }
+      if(_this.options.animationIn){
+        if(_this.options.overlay){
+          Foundation.Motion.animateIn(_this.$overlay, 'fade-in', function(){
+            Foundation.Motion.animateIn(_this.$element, _this.options.animationIn, function(){
+              _this.focusableElements = Foundation.Keyboard.findFocusable(_this.$element);
+            });
+          });
+        }else{
+          Foundation.Motion.animateIn(_this.$element, _this.options.animationIn, function(){
+            _this.focusableElements = Foundation.Keyboard.findFocusable(_this.$element);
+          });
+        }
+      }else{
+        if(_this.options.overlay){
+          _this.$overlay.show(0, function(){
+            _this.$element.show(_this.options.showDelay, function(){
+            });
+          });
+        }else{
+          _this.$element.show(_this.options.showDelay, function(){
+          });
+        }
+      }
+    });
+
+
+    // handle accessibility
+    this.$element.attr({'aria-hidden': false}).attr('tabindex', -1).focus()
+    /**
+     * Fires when the modal has successfully opened.
+     * @event Reveal#open
+     */
+                 .trigger('open.zf.reveal');
+
+    $('body').addClass('is-reveal-open')
+             .attr({'aria-hidden': (this.options.overlay || this.options.fullScreen) ? true : false});
+    setTimeout(function(){
+      _this._extraHandlers();
+    }, 0);
+  };
+
+  /**
+   * Adds extra event handlers for the body and window if necessary.
+   * @private
+   */
+  Reveal.prototype._extraHandlers = function(){
+    var _this = this;
+    this.focusableElements = Foundation.Keyboard.findFocusable(this.$element);
+
+    if(!this.options.overlay && this.options.closeOnClick && !this.options.fullScreen){
+      $('body').on('click.zf.reveal', function(e){
+        if(e.target === _this.$element[0] || $.contains(_this.$element[0], e.target)){ return; }
+        _this.close();
+      });
+    }
+    if(this.options.closeOnEsc){
+      $(window).on('keydown.zf.reveal', function(e){
+        Foundation.Keyboard.handleKey(e, 'Reveal', {
+          close: function() {
+            if (_this.options.closeOnEsc) {
+              _this.close();
+              _this.$anchor.focus();
+            }
+          }
+        });
+        if (_this.focusableElements.length === 0) { // no focusable elements inside the modal at all, prevent tabbing in general
+          e.preventDefault();
+        }
+      });
+    }
+
+    // lock focus within modal while tabbing
+    this.$element.on('keydown.zf.reveal', function(e) {
+      var $target = $(this);
+      // handle keyboard event with keyboard util
+      Foundation.Keyboard.handleKey(e, 'Reveal', {
+        tab_forward: function() {
+          if (_this.$element.find(':focus').is(_this.focusableElements.eq(-1))) { // left modal downwards, setting focus to first element
+            _this.focusableElements.eq(0).focus();
+            e.preventDefault();
+          }
+        },
+        tab_backward: function() {
+          if (_this.$element.find(':focus').is(_this.focusableElements.eq(0)) || _this.$element.is(':focus')) { // left modal upwards, setting focus to last element
+            _this.focusableElements.eq(-1).focus();
+            e.preventDefault();
+          }
+        },
+        open: function() {
+          if (_this.$element.find(':focus').is(_this.$element.find('[data-close]'))) {
+            setTimeout(function() { // set focus back to anchor if close button has been activated
+              _this.$anchor.focus();
+            }, 1);
+          } else if ($target.is(_this.focusableElements)) { // dont't trigger if acual element has focus (i.e. inputs, links, ...)
+            _this.open();
+          }
+        },
+        close: function() {
+          if (_this.options.closeOnEsc) {
+            _this.close();
+            _this.$anchor.focus();
+          }
+        }
+      });
+    });
+
+  };
+
+  /**
+   * Closes the modal.
+   * @function
+   * @fires Reveal#closed
+   */
+  Reveal.prototype.close = function(){
+    if(!this.isActive || !this.$element.is(':visible')){
+      return false;
+    }
+    var _this = this;
+
+    if(this.options.animationOut){
+      Foundation.Motion.animateOut(this.$element, this.options.animationOut, function(){
+        if(_this.options.overlay){
+          Foundation.Motion.animateOut(_this.$overlay, 'fade-out', finishUp);
+        }else{ finishUp(); }
+      });
+    }else{
+      this.$element.hide(_this.options.hideDelay, function(){
+        if(_this.options.overlay){
+          _this.$overlay.hide(0, finishUp);
+        }else{ finishUp(); }
+      });
+    }
+    //conditionals to remove extra event listeners added on open
+    if(this.options.closeOnEsc){
+      $(window).off('keydown.zf.reveal');
+    }
+    if(!this.options.overlay && this.options.closeOnClick){
+      $('body').off('click.zf.reveal');
+    }
+    this.$element.off('keydown.zf.reveal');
+    function finishUp(){
+      //if the modal changed size, reset it
+      if(_this.changedSize){
+        _this.$element.css({
+          'height': '',
+          'width': ''
+        });
+      }
+      $('body').removeClass('is-reveal-open').attr({'aria-hidden': false, 'tabindex': ''});
+      _this.$element.attr({'aria-hidden': true})
+      /**
+      * Fires when the modal is done closing.
+      * @event Reveal#closed
+      */
+      .trigger('closed.zf.reveal');
+    }
+
+
+    /**
+    * Resets the modal content
+    * This prevents a running video to keep going in the background
+    */
+    // if(this.options.resetOnClose) {
+    //   this.$element.html(this.$element.html());
+    // }
+
+    this.isActive = false;
+     if(_this.options.deepLink){
+       if(window.history.replaceState){
+         window.history.replaceState("", document.title, window.location.pathname);
+       }else{
+         window.location.hash = '';
+       }
+     }
+  };
+  /**
+   * Toggles the open/closed state of a modal.
+   * @function
+   */
+  Reveal.prototype.toggle = function(){
+    if(this.isActive){
+      this.close();
+    }else{
+      this.open();
+    }
+  };
+
+  /**
+   * Destroys an instance of a modal.
+   * @function
+   */
+  Reveal.prototype.destroy = function() {
+    if(this.options.overlay){
+      this.$overlay.hide().off().remove();
+    }
+    this.$element.hide().off();
+    this.$anchor.off('.zf');
+    $(window).off('.zf.reveal:' + this.id);
+
+    Foundation.unregisterPlugin(this);
+  };
+
+  Foundation.plugin(Reveal, 'Reveal');
+
+  // Exports for AMD/Browserify
+  if (typeof module !== 'undefined' && typeof module.exports !== 'undefined')
+    module.exports = Reveal;
+  if (typeof define === 'function')
+    define(['foundation'], function() {
+      return Reveal;
+    });
+
+}(Foundation, jQuery);
index ffa382234c248afb120b94d9699af6cc76368574..5c52ec746067c50bdd89012c3ed14160fe3ba471 100644 (file)
@@ -60,10 +60,12 @@ $reveal-overlay-background: rgba($black, 0.45) !default;
 @mixin reveal-modal-base {
   @include disable-mouse-outline;
   display: none;
+  top: 0;
+  left: 0;
   z-index: $reveal-zindex + 1;
   padding: $reveal-padding;
   border: $reveal-border;
-  margin: $reveal-offset auto 0;
+  // margin: $reveal-offset auto 0;
   background-color: $reveal-background;
   border-radius: $reveal-radius;