Browse Source

Merge branch 'popup-fixes' of github.com:Semantic-Org/Semantic-UI into next

pull/2369/head
jlukic 9 years ago
parent
commit
18b9c912c0
2 changed files with 234 additions and 143 deletions
  1. 339
      src/definitions/modules/popup.js
  2. 38
      src/definitions/modules/popup.less

339
src/definitions/modules/popup.js

@ -17,6 +17,8 @@ $.fn.popup = function(parameters) {
var var
$allModules = $(this), $allModules = $(this),
$document = $(document), $document = $(document),
$window = $(window),
$body = $('body'),
moduleSelector = $allModules.selector || '', moduleSelector = $allModules.selector || '',
@ -52,13 +54,12 @@ $.fn.popup = function(parameters) {
? $(settings.target) ? $(settings.target)
: $module, : $module,
$window = $(window),
$body = $('body'),
$popup, $popup,
$offsetParent, $offsetParent,
searchDepth = 0, searchDepth = 0,
triedPositions = false, triedPositions = false,
openedWithTouch = false,
element = this, element = this,
instance = $module.data(moduleNamespace), instance = $module.data(moduleNamespace),
@ -166,10 +167,24 @@ $.fn.popup = function(parameters) {
clearTimeout(module.showTimer); clearTimeout(module.showTimer);
module.hideTimer = setTimeout(module.hide, delay); module.hideTimer = setTimeout(module.hide, delay);
}, },
touchstart: function(event) {
openedWithTouch = true;
module.event.start();
},
resize: function() { resize: function() {
if( module.is.visible() ) { if( module.is.visible() ) {
module.set.position(); module.set.position();
} }
},
hideGracefully: function(event) {
// don't close on clicks inside popup
if(event && $(event.target).closest(selector.popup).length === 0) {
module.debug('Click occurred outside popup hiding popup');
module.hide();
}
else {
module.debug('Click was inside popup, keeping popup open');
}
} }
}, },
@ -262,7 +277,7 @@ $.fn.popup = function(parameters) {
}, },
show: function(callback) { show: function(callback) {
callback = $.isFunction(callback) ? callback : function(){};
callback = callback || function(){};
module.debug('Showing pop-up', settings.transition); module.debug('Showing pop-up', settings.transition);
if(module.is.hidden() && !( module.is.active() && module.is.dropdown()) ) { if(module.is.hidden() && !( module.is.active() && module.is.dropdown()) ) {
@ -288,7 +303,7 @@ $.fn.popup = function(parameters) {
hide: function(callback) { hide: function(callback) {
callback = $.isFunction(callback) ? callback : function(){};
callback = callback || function(){};
if( module.is.visible() || module.is.animating() ) { if( module.is.visible() || module.is.animating() ) {
if(settings.onHide.call($popup, element) === false) { if(settings.onHide.call($popup, element) === false) {
module.debug('onHide callback returned false, cancelling popup animation'); module.debug('onHide callback returned false, cancelling popup animation');
@ -312,18 +327,6 @@ $.fn.popup = function(parameters) {
}) })
; ;
}, },
hideGracefully: function(event) {
// don't close on clicks inside popup
if(event && $(event.target).closest(selector.popup).length === 0) {
module.debug('Click occurred outside popup hiding popup');
module.hide();
}
else {
module.debug('Click was inside popup, keeping popup open');
}
},
exists: function() { exists: function() {
if(!$popup) { if(!$popup) {
return false; return false;
@ -438,15 +441,73 @@ $.fn.popup = function(parameters) {
$module.removeData(metadata.variation); $module.removeData(metadata.variation);
return $module.data(metadata.variation) || settings.variation; return $module.data(metadata.variation) || settings.variation;
}, },
calculations: function() {
var
targetElement = $target[0],
targetPosition = (settings.inline || settings.popup)
? $target.position()
: $target.offset(),
calculations = {},
screen
;
calculations = {
// element which is launching popup
target : {
element : $target[0],
width : $target.outerWidth(),
height : $target.outerHeight(),
top : targetPosition.top,
left : targetPosition.left,
margin : {}
},
// popup itself
popup : {
width : $popup.outerWidth(),
height : $popup.outerHeight()
},
// offset container (or 3d context)
parent : {
width : $offsetParent.outerWidth(),
height : $offsetParent.outerHeight()
},
// screen boundaries
screen : {
scroll: {
top : $window.scrollTop(),
left : $window.scrollLeft()
},
width : $window.width(),
height : $window.height()
}
};
// add in margins if inline
calculations.target.margin.top = (settings.inline)
? parseInt( window.getComputedStyle(targetElement).getPropertyValue('margin-top'), 10)
: 0
;
calculations.target.margin.left = (settings.inline)
? module.is.rtl()
? parseInt( window.getComputedStyle(targetElement).getPropertyValue('margin-right'), 10)
: parseInt( window.getComputedStyle(targetElement).getPropertyValue('margin-left') , 10)
: 0
;
// calculate screen boundaries
screen = calculations.screen;
calculations.boundary = {
top : screen.scroll.top,
bottom : screen.scroll.top + screen.height,
left : screen.scroll.left,
right : screen.scroll.left + screen.width
};
return calculations;
},
id: function() { id: function() {
return id; return id;
}, },
startEvent: function() { startEvent: function() {
if(settings.on == 'hover') { if(settings.on == 'hover') {
return (hasTouch && settings.addTouchEvents)
? 'touchstart mouseenter'
: 'mouseenter'
;
return 'mouseenter';
} }
else if(settings.on == 'focus') { else if(settings.on == 'focus') {
return 'focus'; return 'focus';
@ -454,10 +515,7 @@ $.fn.popup = function(parameters) {
return false; return false;
}, },
scrollEvent: function() { scrollEvent: function() {
return (hasTouch && settings.addTouchEvents)
? 'touchmove scroll'
: 'scroll'
;
return 'scroll';
}, },
endEvent: function() { endEvent: function() {
if(settings.on == 'hover') { if(settings.on == 'hover') {
@ -495,37 +553,28 @@ $.fn.popup = function(parameters) {
: $() : $()
; ;
}, },
offstagePosition: function(position) {
offstagePosition: function(position, calculations) {
var var
screen = {
top : $(window).scrollTop(),
left : $(window).scrollLeft(),
width : $(window).width(),
height : $(window).height()
},
boundary = {
top : screen.top,
bottom : screen.top + screen.height,
left : screen.left,
right : screen.left + screen.width
},
popup = {
width : $popup.width(),
height : $popup.height(),
offset : $popup.offset()
},
offstage = {},
offstagePositions = []
offset = $popup.offset(),
offstage = {},
offstagePositions = [],
popup,
boundary
; ;
position = position || false;
if(popup.offset && position) {
module.verbose('Checking if outside viewable area', popup.offset);
position = position || false;
calculations = calculations || module.get.calculations();
// shorthand
popup = calculations.popup;
boundary = calculations.boundary;
if(offset && position) {
offstage = { offstage = {
top : (popup.offset.top < boundary.top),
bottom : (popup.offset.top + popup.height > boundary.bottom),
right : (popup.offset.left + popup.width > boundary.right),
left : (popup.offset.left < boundary.left)
top : (offset.top < boundary.top),
bottom : (offset.top + popup.height > boundary.bottom),
right : (offset.left + popup.width > boundary.right),
left : (offset.left < boundary.left)
}; };
module.verbose('Offstage positions determined', offset, offstage);
} }
// return only boundaries that have been surpassed // return only boundaries that have been surpassed
$.each(offstage, function(direction, isOffstage) { $.each(offstage, function(direction, isOffstage) {
@ -610,7 +659,7 @@ $.fn.popup = function(parameters) {
}, },
set: { set: {
position: function(position, arrowOffset) {
position: function(position, calculations) {
// exit conditions // exit conditions
if($target.length === 0 || $popup.length === 0) { if($target.length === 0 || $popup.length === 0) {
@ -618,140 +667,125 @@ $.fn.popup = function(parameters) {
return; return;
} }
var var
windowWidth = $(window).width(),
windowHeight = $(window).height(),
targetWidth = $target.outerWidth(),
targetHeight = $target.outerHeight(),
popupWidth = $popup.outerWidth(),
popupHeight = $popup.outerHeight(),
parentWidth = $offsetParent.outerWidth(),
parentHeight = $offsetParent.outerHeight(),
distanceAway = settings.distanceAway,
targetElement = $target[0],
marginTop = (settings.inline)
? parseInt( window.getComputedStyle(targetElement).getPropertyValue('margin-top'), 10)
: 0,
marginLeft = (settings.inline)
? module.is.rtl()
? parseInt( window.getComputedStyle(targetElement).getPropertyValue('margin-right'), 10)
: parseInt( window.getComputedStyle(targetElement).getPropertyValue('margin-left') , 10)
: 0,
target = (settings.inline || settings.popup)
? $target.position()
: $target.offset(),
offset,
distanceAway,
target,
popup,
parent,
computedPosition, computedPosition,
positioning, positioning,
offstagePosition offstagePosition
; ;
position = position || $module.data(metadata.position) || settings.position;
arrowOffset = arrowOffset || $module.data(metadata.offset) || settings.offset;
calculations = calculations || module.get.calculations();
position = position || $module.data(metadata.position) || settings.position;
offset = $module.data(metadata.offset) || settings.offset;
distanceAway = settings.distanceAway;
// shorthand
target = calculations.target;
popup = calculations.popup;
parent = calculations.parent;
if(target.top === 0 && target.left === 0) { if(target.top === 0 && target.left === 0) {
module.debug('Popup target is hidden, no action taken'); module.debug('Popup target is hidden, no action taken');
return false; return false;
} }
if(searchDepth == settings.maxSearchDepth && settings.lastResort) {
module.debug('Using last resort position to display', settings.lastResort);
position = settings.lastResort;
}
if(settings.inline) { if(settings.inline) {
module.debug('Adding targets margin to calculation');
module.debug('Adding margin to calculation', target.margin);
if(position == 'left center' || position == 'right center') { if(position == 'left center' || position == 'right center') {
arrowOffset += marginTop;
distanceAway += -marginLeft;
offset += target.margin.top;
distanceAway += -target.margin.left;
} }
else if (position == 'top left' || position == 'top center' || position == 'top right') { else if (position == 'top left' || position == 'top center' || position == 'top right') {
arrowOffset += marginLeft;
distanceAway -= marginTop;
offset += target.margin.left;
distanceAway -= target.margin.top;
} }
else { else {
arrowOffset += marginLeft;
distanceAway += marginTop;
offset += target.margin.left;
distanceAway += target.margin.top;
} }
} }
module.debug('Calculating popup positioning', position);
computedPosition = position;
module.debug('Determining popup position from calculations', position, calculations);
if (module.is.rtl()) { if (module.is.rtl()) {
computedPosition = computedPosition.replace(/left|right/g, function (match) {
position = position.replace(/left|right/g, function (match) {
return (match == 'left') return (match == 'left')
? 'right' ? 'right'
: 'left' : 'left'
; ;
}); });
module.debug('RTL: Popup positioning updated', computedPosition);
module.debug('RTL: Popup position updated', position);
} }
switch (computedPosition) {
if(searchDepth == settings.maxSearchDepth && settings.lastResort) {
module.debug('Using "last resort" position to display', settings.lastResort);
position = settings.lastResort;
}
switch (position) {
case 'top left': case 'top left':
positioning = { positioning = {
top : 'auto', top : 'auto',
bottom : parentHeight - target.top + distanceAway,
left : target.left + arrowOffset,
bottom : parent.height - target.top + distanceAway,
left : target.left + offset,
right : 'auto' right : 'auto'
}; };
break; break;
case 'top center': case 'top center':
positioning = { positioning = {
bottom : parentHeight - target.top + distanceAway,
left : target.left + (targetWidth / 2) - (popupWidth / 2) + arrowOffset,
bottom : parent.height - target.top + distanceAway,
left : target.left + (target.width / 2) - (popup.width / 2) + offset,
top : 'auto', top : 'auto',
right : 'auto' right : 'auto'
}; };
break; break;
case 'top right': case 'top right':
positioning = { positioning = {
bottom : parentHeight - target.top + distanceAway,
right : parentWidth - target.left - targetWidth - arrowOffset,
bottom : parent.height - target.top + distanceAway,
right : parent.width - target.left - target.width - offset,
top : 'auto', top : 'auto',
left : 'auto' left : 'auto'
}; };
break; break;
case 'left center': case 'left center':
positioning = { positioning = {
top : target.top + (targetHeight / 2) - (popupHeight / 2) + arrowOffset,
right : parentWidth - target.left + distanceAway,
top : target.top + (target.height / 2) - (popup.height / 2) + offset,
right : parent.width - target.left + distanceAway,
left : 'auto', left : 'auto',
bottom : 'auto' bottom : 'auto'
}; };
break; break;
case 'right center': case 'right center':
positioning = { positioning = {
top : target.top + (targetHeight / 2) - (popupHeight / 2) + arrowOffset,
left : target.left + targetWidth + distanceAway,
top : target.top + (target.height / 2) - (popup.height / 2) + offset,
left : target.left + target.width + distanceAway,
bottom : 'auto', bottom : 'auto',
right : 'auto' right : 'auto'
}; };
break; break;
case 'bottom left': case 'bottom left':
positioning = { positioning = {
top : target.top + targetHeight + distanceAway,
left : target.left + arrowOffset,
top : target.top + target.height + distanceAway,
left : target.left + offset,
bottom : 'auto', bottom : 'auto',
right : 'auto' right : 'auto'
}; };
break; break;
case 'bottom center': case 'bottom center':
positioning = { positioning = {
top : target.top + targetHeight + distanceAway,
left : target.left + (targetWidth / 2) - (popupWidth / 2) + arrowOffset,
top : target.top + target.height + distanceAway,
left : target.left + (target.width / 2) - (popup.width / 2) + offset,
bottom : 'auto', bottom : 'auto',
right : 'auto' right : 'auto'
}; };
break; break;
case 'bottom right': case 'bottom right':
positioning = { positioning = {
top : target.top + targetHeight + distanceAway,
right : parentWidth - target.left - targetWidth - arrowOffset,
top : target.top + target.height + distanceAway,
right : parent.width - target.left - target.width - offset,
left : 'auto', left : 'auto',
bottom : 'auto' bottom : 'auto'
}; };
@ -771,7 +805,7 @@ $.fn.popup = function(parameters) {
.addClass(className.loading) .addClass(className.loading)
; ;
// check if is offstage // check if is offstage
offstagePosition = module.get.offstagePosition(position);
offstagePosition = module.get.offstagePosition(position, calculations);
// recursively find new positioning // recursively find new positioning
if(offstagePosition) { if(offstagePosition) {
@ -781,7 +815,7 @@ $.fn.popup = function(parameters) {
position = module.get.nextPosition(position); position = module.get.nextPosition(position);
module.debug('Trying new position', position); module.debug('Trying new position', position);
return ($popup) return ($popup)
? module.set.position(position)
? module.set.position(position, calculations)
: false : false
; ;
} }
@ -797,14 +831,15 @@ $.fn.popup = function(parameters) {
module.debug('Position is on stage', position); module.debug('Position is on stage', position);
module.remove.attempts(); module.remove.attempts();
module.set.fluidWidth();
module.set.fluidWidth(calculations);
module.remove.loading(); module.remove.loading();
return true; return true;
}, },
fluidWidth: function() {
fluidWidth: function(calculations) {
calculations = calculations || module.get.calculations();
if( settings.setFluidWidth && $popup.hasClass(className.fluid) ) { if( settings.setFluidWidth && $popup.hasClass(className.fluid) ) {
$popup.css('width', $offsetParent.width());
$popup.css('width', calculations.parent.width);
} }
}, },
@ -835,6 +870,11 @@ $.fn.popup = function(parameters) {
.on('click' + eventNamespace, module.toggle) .on('click' + eventNamespace, module.toggle)
; ;
} }
if(settings.on == 'hover' && hasTouch) {
$module
.on('touchstart' + eventNamespace, module.event.touchstart)
;
}
else if( module.get.startEvent() ) { else if( module.get.startEvent() ) {
$module $module
.on(module.get.startEvent() + eventNamespace, module.event.start) .on(module.get.startEvent() + eventNamespace, module.event.start)
@ -855,21 +895,30 @@ $.fn.popup = function(parameters) {
; ;
} }
}, },
close:function() {
if(settings.hideOnScroll === true || settings.hideOnScroll == 'auto' && settings.on != 'click') {
close: function() {
if(settings.hideOnScroll === true || (settings.hideOnScroll == 'auto' && settings.on != 'click')) {
$document $document
.one(module.get.scrollEvent() + elementNamespace, module.hideGracefully)
.one(module.get.scrollEvent() + elementNamespace, module.event.hideGracefully)
; ;
$context $context
.one(module.get.scrollEvent() + elementNamespace, module.hideGracefully)
.one(module.get.scrollEvent() + elementNamespace, module.event.hideGracefully)
;
}
if(settings.on == 'hover' && openedWithTouch) {
module.verbose('Binding popup close event to document');
$document
.on('touchstart' + elementNamespace, function(event) {
module.verbose('Touched away from popup');
module.event.hideGracefully.call(element, event);
})
; ;
} }
if(settings.on == 'click' && settings.closable) { if(settings.on == 'click' && settings.closable) {
module.verbose('Binding popup close event to document'); module.verbose('Binding popup close event to document');
$document $document
.on('click' + elementNamespace, function(event) { .on('click' + elementNamespace, function(event) {
module.verbose('Pop-up clickaway intent detected');
module.hideGracefully.call(element, event);
module.verbose('Clicked away from popup');
module.event.hideGracefully.call(element, event);
}) })
; ;
} }
@ -878,7 +927,7 @@ $.fn.popup = function(parameters) {
unbind: { unbind: {
close: function() { close: function() {
if(settings.hideOnScroll === true || settings.hideOnScroll == 'auto' && settings.on != 'click') {
if(settings.hideOnScroll === true || (settings.hideOnScroll == 'auto' && settings.on != 'click')) {
$document $document
.off('scroll' + elementNamespace, module.hide) .off('scroll' + elementNamespace, module.hide)
; ;
@ -886,6 +935,12 @@ $.fn.popup = function(parameters) {
.off('scroll' + elementNamespace, module.hide) .off('scroll' + elementNamespace, module.hide)
; ;
} }
if(settings.on == 'hover' && openedWithTouch) {
$document
.off('touchstart' + elementNamespace)
;
openedWithTouch = false;
}
if(settings.on == 'click' && settings.closable) { if(settings.on == 'click' && settings.closable) {
module.verbose('Removing close event from document'); module.verbose('Removing close event from document');
$document $document
@ -1123,14 +1178,19 @@ $.fn.popup.settings = {
// callback only when element added to dom // callback only when element added to dom
onCreate : function(){}, onCreate : function(){},
// callback before element removed from dom // callback before element removed from dom
onRemove : function(){}, onRemove : function(){},
// callback before show animation // callback before show animation
onShow : function(){}, onShow : function(){},
// callback after show animation // callback after show animation
onVisible : function(){}, onVisible : function(){},
// callback before hide animation // callback before hide animation
onHide : function(){}, onHide : function(){},
// callback after hide animation // callback after hide animation
onHidden : function(){}, onHidden : function(){},
@ -1142,36 +1202,49 @@ $.fn.popup.settings = {
// default position relative to element // default position relative to element
position : 'top left', position : 'top left',
// name of variation to use // name of variation to use
variation : '', variation : '',
// whether popup should be moved to context // whether popup should be moved to context
movePopup : true, movePopup : true,
// element which popup should be relative to // element which popup should be relative to
target : false, target : false,
// jq selector or element that should be used as popup // jq selector or element that should be used as popup
popup : false, popup : false,
// popup should remain inline next to activator // popup should remain inline next to activator
inline : false, inline : false,
// popup should be removed from page on hide // popup should be removed from page on hide
preserve : false, preserve : false,
// popup should not close when being hovered on // popup should not close when being hovered on
hoverable : false, hoverable : false,
// explicitly set content // explicitly set content
content : false, content : false,
// explicitly set html // explicitly set html
html : false, html : false,
// explicitly set title // explicitly set title
title : false, title : false,
// whether automatically close on clickaway when on click // whether automatically close on clickaway when on click
closable : true, closable : true,
// automatically hide on scroll // automatically hide on scroll
hideOnScroll : 'auto', hideOnScroll : 'auto',
// hide other popups on show // hide other popups on show
exclusive : false, exclusive : false,
// context to attach popups // context to attach popups
context : 'body', context : 'body',
// position to prefer when calculating new position // position to prefer when calculating new position
prefer : 'opposite', prefer : 'opposite',
@ -1187,7 +1260,6 @@ $.fn.popup.settings = {
// whether fluid variation should assign width explicitly // whether fluid variation should assign width explicitly
setFluidWidth : true, setFluidWidth : true,
// transition settings // transition settings
duration : 200, duration : 200,
transition : 'scale', transition : 'scale',
@ -1198,7 +1270,7 @@ $.fn.popup.settings = {
// offset on aligning axis from calculated position // offset on aligning axis from calculated position
offset : 0, offset : 0,
// maximum times to look for a position before failing
// maximum times to look for a position before failing (9 positions total)
maxSearchDepth : 20, maxSearchDepth : 20,
error: { error: {
@ -1277,12 +1349,5 @@ $.fn.popup.settings = {
}; };
// Adds easing
$.extend( $.easing, {
easeOutQuad: function (x, t, b, c, d) {
return -c *(t/=d)*(t-2) + b;
}
});
})( jQuery, window , document ); })( jQuery, window , document );

38
src/definitions/modules/popup.less

@ -82,17 +82,43 @@
.ui.popup { .ui.popup {
margin: 0em; margin: 0em;
} }
.ui.popup.bottom {
margin: @popupDistanceAway 0em 0em;
}
.ui.popup.top {
/* Extending from Top */
.ui.top.popup {
margin: 0em 0em @popupDistanceAway; margin: 0em 0em @popupDistanceAway;
} }
.ui.popup.left.center {
.ui.top.left.popup {
transform-origin: bottom left;
}
.ui.top.center.popup {
transform-origin: bottom center;
}
.ui.top.right.popup {
transform-origin: bottom right;
}
/* Extending from Vertical Center */
.ui.left.center.popup {
margin: 0em @popupDistanceAway 0em 0em; margin: 0em @popupDistanceAway 0em 0em;
transform-origin: right 50%;
} }
.ui.popup.right.center {
.ui.right.center.popup {
margin: 0em 0em 0em @popupDistanceAway; margin: 0em 0em 0em @popupDistanceAway;
transform-origin: left 50%;
}
/* Extending from Bottom */
.ui.bottom.popup {
margin: @popupDistanceAway 0em 0em;
}
.ui.bottom.left.popup {
transform-origin: top left;
}
.ui.bottom.center.popup {
transform-origin: top center;
}
.ui.bottom.right.popup {
transform-origin: top right;
} }
/*-------------- /*--------------

Loading…
Cancel
Save