Browse Source

Add label selection, group selection, fixes some keyboard events

pull/2034/head
jlukic 9 years ago
parent
commit
3c4294fbb0
3 changed files with 272 additions and 247 deletions
  1. 3
      RELEASE-NOTES.md
  2. 515
      src/definitions/modules/dropdown.js
  3. 1
      src/definitions/modules/dropdown.less

3
RELEASE-NOTES.md

@ -4,9 +4,12 @@
**New UI** **New UI**
- **Container** - Containers are fixed width containers meant for holding page contents, and are a simpler alternative to `ui page grid`, view more [examples in docs](http://www.semantic-ui.com/elements/container.html#examples) - **Container** - Containers are fixed width containers meant for holding page contents, and are a simpler alternative to `ui page grid`, view more [examples in docs](http://www.semantic-ui.com/elements/container.html#examples)
- **Multiselect** - New dropdown component has been added for multi-select
- **Grid** - `Equal height` and `Equal width` `flexbox` grids now have mainstream support, works correctly with or without `row` wrapper, and support vertical alignment including `stretch` alignment for matching child element heights.
**Major Changes** **Major Changes**
- **Menu** - Menu now uses flexbox. This allows menu items to match each others heights regardless of each items content size. `right menu` content should now follow other menu content instead of preceding it (no longer uses float). - **Menu** - Menu now uses flexbox. This allows menu items to match each others heights regardless of each items content size. `right menu` content should now follow other menu content instead of preceding it (no longer uses float).
- **Transition** - Fallback javascript animations have been removed from most UI components to help reduce file size since all modern browsers now support css animations. The `transition` component should be included when using `dropdown`, `popup` etc.
**Enhancements** **Enhancements**
- **Build Tools** - Adjusting `site.variables` will now rebuild all UI, instead of just `site.less` - **Build Tools** - Adjusting `site.variables` will now rebuild all UI, instead of just `site.less`

515
src/definitions/modules/dropdown.js

@ -337,6 +337,11 @@ $.fn.dropdown = function(parameters) {
.on(module.get.inputEvent() + eventNamespace, selector.search, module.event.input) .on(module.get.inputEvent() + eventNamespace, selector.search, module.event.input)
; ;
} }
if( module.is.multiple() ) {
$document
.on('keydown' + elementNamespace, module.event.document.keydown)
;
}
}, },
touchEvents: function() { touchEvents: function() {
module.debug('Touch device detected binding additional touch events'); module.debug('Touch device detected binding additional touch events');
@ -356,16 +361,18 @@ $.fn.dropdown = function(parameters) {
module.verbose('Mouse detected binding mouse events'); module.verbose('Mouse detected binding mouse events');
if( module.is.searchSelection() ) { if( module.is.searchSelection() ) {
$module $module
.on('mousedown' + eventNamespace, selector.menu, module.event.menu.activate)
.on('mouseup' + eventNamespace, selector.menu, module.event.menu.deactivate)
.on('mousedown' + eventNamespace, selector.menu, module.event.menu.mousedown)
.on('mouseup' + eventNamespace, selector.menu, module.event.menu.mouseup)
.on('click' + eventNamespace, selector.search, module.show) .on('click' + eventNamespace, selector.search, module.show)
.on('focus' + eventNamespace, selector.search, module.event.searchFocus)
.on('blur' + eventNamespace, selector.search, module.event.searchBlur)
.on('click' + eventNamespace, selector.text, module.event.searchTextFocus)
.on('focus' + eventNamespace, selector.search, module.event.search.focus)
.on('blur' + eventNamespace, selector.search, module.event.search.blur)
.on('click' + eventNamespace, selector.text, module.event.text.focus)
; ;
if(module.is.multiple()) { if(module.is.multiple()) {
$module $module
.on('click' + eventNamespace, module.event.click)
.on('click' + eventNamespace, module.event.click)
.on('click' + eventNamespace, selector.label, module.event.label.click)
.on('click' + eventNamespace, selector.remove, module.event.remove.click)
; ;
} }
} }
@ -514,15 +521,28 @@ $.fn.dropdown = function(parameters) {
}, },
event: { event: {
focus: function() {
if(!activated && module.is.hidden()) {
module.show();
}
},
click: function(event) { click: function(event) {
var var
$target = $(event.target) $target = $(event.target)
; ;
// focus search // focus search
if(($target.is($module) || $target.is($icon)) && !(document.activeElement === $search[0])) {
if(($target.is($module) || $target.is($icon)) && !module.is.focusedOnSearch()) {
$search.focus(); $search.focus();
} }
}, },
blur: function(event) {
var
pageLostFocus = (document.activeElement === this)
;
if(!activated && !pageLostFocus) {
module.hide();
}
},
// prevents focus callback from occuring on mousedown // prevents focus callback from occuring on mousedown
mousedown: function() { mousedown: function() {
activated = true; activated = true;
@ -530,50 +550,239 @@ $.fn.dropdown = function(parameters) {
mouseup: function() { mouseup: function() {
activated = false; activated = false;
}, },
focus: function() {
if(!activated && module.is.hidden()) {
search: {
focus: function() {
activated = true;
module.show(); module.show();
},
blur: function(event) {
var
pageLostFocus = (document.activeElement === this)
;
if(!itemActivated && !pageLostFocus) {
if(module.is.multiple()) {
module.remove.activeLabel();
}
else if(settings.forceSelection) {
module.forceSelection();
}
else {
module.hide();
}
}
} }
}, },
blur: function(event) {
var
pageLostFocus = (document.activeElement === this)
;
if(!activated && !pageLostFocus) {
module.hide();
text: {
focus: function(event) {
activated = true;
$search.focus();
} }
}, },
searchFocus: function() {
activated = true;
module.show();
input: function(event) {
if(module.is.searchSelection()) {
module.set.filtered();
}
clearTimeout(module.timer);
module.timer = setTimeout(module.search, settings.delay.search);
}, },
searchBlur: function(event) {
var
pageLostFocus = (document.activeElement === this)
;
if(!itemActivated && !pageLostFocus) {
if(module.is.multiple()) {
module.remove.activeLabel();
label: {
click: function(event) {
var
$label = $(this),
$labels = $module.find(selector.label),
$activeLabels = $labels.filter('.' + className.active),
$nextActive = $label.nextAll('.' + className.active),
$prevActive = $label.prevAll('.' + className.active),
$range = ($nextActive.length > 0)
? $label.nextUntil($nextActive).add($activeLabels).add($label)
: $label.prevUntil($prevActive).add($activeLabels).add($label)
;
;
if(event.shiftKey) {
$activeLabels.removeClass(className.active);
$range.addClass(className.active);
} }
else if(settings.forceSelection) {
module.forceSelection();
else if(event.ctrlKey) {
$label.toggleClass(className.active);
} }
else { else {
module.hide();
$activeLabels.removeClass(className.active);
$label.addClass(className.active);
} }
settings.onLabelClick.apply(this, $labels.filter('.' + className.active));
} }
}, },
searchTextFocus: function(event) {
activated = true;
$search.focus();
remove: {
click: function() {
var
$label = $(this).parent()
;
if( $label.hasClass(className.active) ) {
// remove all selected labels
module.remove.labels();
}
else {
// remove this label only
module.remove.labels( $label );
}
}
}, },
input: function(event) {
if(module.is.searchSelection()) {
module.set.filtered();
test: {
toggle: function(event) {
if( module.determine.eventInMenu(event, module.toggle) ) {
event.preventDefault();
}
},
touch: function(event) {
module.determine.eventInMenu(event, function() {
if(event.type == 'touchstart') {
module.timer = setTimeout(module.hide, settings.delay.touch);
}
else if(event.type == 'touchmove') {
clearTimeout(module.timer);
}
});
event.stopPropagation();
},
hide: function(event) {
module.determine.eventInModule(event, module.hide);
} }
clearTimeout(module.timer);
module.timer = setTimeout(module.search, settings.delay.search);
}, },
menu: {
mousedown: function() {
itemActivated = true;
},
mouseup: function() {
itemActivated = false;
}
},
item: {
mouseenter: function(event) {
var
$subMenu = $(this).children(selector.menu),
$otherMenus = $(this).siblings(selector.item).children(selector.menu)
;
if( $subMenu.length > 0 ) {
clearTimeout(module.itemTimer);
module.itemTimer = setTimeout(function() {
module.verbose('Showing sub-menu', $subMenu);
$.each($otherMenus, function() {
module.animate.hide(false, $(this));
});
module.animate.show(false, $subMenu);
}, settings.delay.show);
event.preventDefault();
}
},
mouseleave: function(event) {
var
$subMenu = $(this).children(selector.menu)
;
if($subMenu.length > 0) {
clearTimeout(module.itemTimer);
module.itemTimer = setTimeout(function() {
module.verbose('Hiding sub-menu', $subMenu);
module.animate.hide(false, $subMenu);
}, settings.delay.hide);
}
},
click: function (event) {
var
$choice = $(this),
$target = (event)
? $(event.target)
: $(''),
$subMenu = $choice.find(selector.menu),
text = module.get.choiceText($choice),
value = module.get.choiceValue($choice, text),
callback = function() {
module.remove.searchTerm();
module.determine.selectAction(text, value);
},
hasSubMenu = ($subMenu.length > 0),
isBubbledEvent = ($subMenu.find($target).length > 0)
;
if(!isBubbledEvent && (!hasSubMenu || settings.allowCategorySelection)) {
callback();
}
}
},
document: {
// label selection should occur even when element has no focus
keydown: function(event) {
var
pressedKey = event.which,
keys = module.get.shortcutKeys(),
isShortcutKey = module.is.inObject(pressedKey, keys)
;
if(isShortcutKey) {
var
$label = $module.find(selector.label),
$activeLabel = $label.filter('.' + className.active),
activeValue = $activeLabel.data('value'),
labelIndex = $label.index($activeLabel),
labelCount = $label.length,
hasActiveLabel = ($activeLabel.length > 0),
isFirstLabel = (labelIndex == 0),
isLastLabel = (labelIndex + 1 == labelCount),
isFocusedOnSearch = module.is.focusedOnSearch(),
caretAtStart = (isFocusedOnSearch && module.get.caretPosition() == 0)
;
if(isFocusedOnSearch && (pressedKey == keys.delimiter)) {
// tokenize on comma
if(module.is.visible()) {
module.event.item.click.call($selectedItem, event);
event.preventDefault();
}
}
else if(pressedKey == keys.leftArrow) {
// activate previous label
if(caretAtStart && !hasActiveLabel) {
$label.last().addClass(className.active);
}
else if(hasActiveLabel && !isFirstLabel) {
if(!event.shiftKey) {
$label.removeClass(className.active)
}
$activeLabel.prev()
.addClass(className.active)
.end()
;
event.preventDefault();
}
}
else if(pressedKey == keys.rightArrow) {
// activate next label
if(hasActiveLabel) {
if(!event.shiftKey) {
$label.removeClass(className.active)
}
$activeLabel.next()
.addClass(className.active)
.end()
;
event.preventDefault();
}
}
else if(pressedKey == keys.deleteKey || pressedKey == keys.backspace) {
if(hasActiveLabel) {
$activeLabel.last().next().addClass(className.active);
module.remove.labels($activeLabel);
}
else if(caretAtStart && !hasActiveLabel && pressedKey == keys.backspace) {
$activeLabel = $label.last().addClass(className.active);
activeValue = $activeLabel.data('value');
module.remove.selected(activeValue);
}
}
else {
$activeLabel.removeClass(className.active);
}
}
}
},
keydown: function(event) { keydown: function(event) {
var var
pressedKey = event.which, pressedKey = event.which,
@ -716,152 +925,6 @@ $.fn.dropdown = function(parameters) {
} }
} }
// multiple selection shortcuts
if( module.is.multiple() ) {
var
$label = $module.find(selector.label),
$activeLabel = $label.filter('.' + className.active),
activeValue = $activeLabel.data('value'),
labelIndex = $label.index($activeLabel),
labelCount = $label.length,
hasActiveLabel = ($activeLabel.length > 0),
isFirstLabel = (labelIndex == 0),
isLastLabel = (labelIndex + 1 == labelCount),
caretAtStart = (module.get.caretPosition() == 0)
;
if(pressedKey == keys.delimiter) {
// tokenize on comma
if(module.is.visible()) {
module.event.item.click.call($selectedItem, event);
event.preventDefault();
}
}
else if(pressedKey == keys.leftArrow) {
// activate previous label
if(caretAtStart && !hasActiveLabel) {
$label.last().addClass(className.active);
}
else if(hasActiveLabel && !isFirstLabel) {
$activeLabel
.removeClass(className.active)
.prev()
.addClass(className.active)
.end()
;
event.preventDefault();
}
}
else if(pressedKey == keys.rightArrow) {
// activate next label
if(hasActiveLabel) {
$activeLabel
.removeClass(className.active)
.next()
.addClass(className.active)
.end()
;
event.preventDefault();
}
}
else if(pressedKey == keys.deleteKey || pressedKey == keys.backspace) {
if(caretAtStart && !hasActiveLabel) {
$activeLabel = $label.last().addClass(className.active);
activeValue = $activeLabel.data('value');
module.remove.selected(activeValue);
}
else if(hasActiveLabel) {
$activeLabel.next().addClass(className.active);
module.remove.selected(activeValue);
}
// delete tag if empty search and selected tag
}
else {
$activeLabel.removeClass(className.active);
}
}
}
},
test: {
toggle: function(event) {
if( module.determine.eventInMenu(event, module.toggle) ) {
event.preventDefault();
}
},
touch: function(event) {
module.determine.eventInMenu(event, function() {
if(event.type == 'touchstart') {
module.timer = setTimeout(module.hide, settings.delay.touch);
}
else if(event.type == 'touchmove') {
clearTimeout(module.timer);
}
});
event.stopPropagation();
},
hide: function(event) {
module.determine.eventInModule(event, module.hide);
}
},
menu: {
activate: function() {
itemActivated = true;
},
deactivate: function() {
itemActivated = false;
}
},
item: {
mouseenter: function(event) {
var
$subMenu = $(this).children(selector.menu),
$otherMenus = $(this).siblings(selector.item).children(selector.menu)
;
if( $subMenu.length > 0 ) {
clearTimeout(module.itemTimer);
module.itemTimer = setTimeout(function() {
module.verbose('Showing sub-menu', $subMenu);
$.each($otherMenus, function() {
module.animate.hide(false, $(this));
});
module.animate.show(false, $subMenu);
}, settings.delay.show);
event.preventDefault();
}
},
mouseleave: function(event) {
var
$subMenu = $(this).children(selector.menu)
;
if($subMenu.length > 0) {
clearTimeout(module.itemTimer);
module.itemTimer = setTimeout(function() {
module.verbose('Hiding sub-menu', $subMenu);
module.animate.hide(false, $subMenu);
}, settings.delay.hide);
}
},
click: function (event) {
var
$choice = $(this),
$target = (event)
? $(event.target)
: $(''),
$subMenu = $choice.find(selector.menu),
text = module.get.choiceText($choice),
value = module.get.choiceValue($choice, text),
callback = function() {
module.remove.searchTerm();
module.determine.selectAction(text, value);
},
hasSubMenu = ($subMenu.length > 0),
isBubbledEvent = ($subMenu.find($target).length > 0)
;
if(!isBubbledEvent && (!hasSubMenu || settings.allowCategorySelection)) {
callback();
}
} }
}, },
resetStyle: function() { resetStyle: function() {
@ -1516,9 +1579,12 @@ $.fn.dropdown = function(parameters) {
$labels = $module.find(selector.label), $labels = $module.find(selector.label),
$removedLabel = $labels.filter('[data-value="' + value +'"]'), $removedLabel = $labels.filter('[data-value="' + value +'"]'),
labelCount = $labels.length, labelCount = $labels.length,
shouldAnimate = (labelCount == 1 || $labels.index($removedLabel) + 1 == labelCount)
isLastLabel = ($labels.index($removedLabel) + 1 == labelCount),
isOnlyLabel = (labelCount == 1),
shouldAnimate = (isOnlyLabel || isLastLabel)
; ;
if(shouldAnimate) { if(shouldAnimate) {
module.verbose('Animating and removing label', $removedLabel);
$removedLabel $removedLabel
.transition(settings.label.transition, settings.label.duration, function() { .transition(settings.label.transition, settings.label.duration, function() {
$removedLabel.remove(); $removedLabel.remove();
@ -1526,9 +1592,19 @@ $.fn.dropdown = function(parameters) {
; ;
} }
else { else {
module.verbose('Removing label', $removedLabel);
$removedLabel.remove(); $removedLabel.remove();
} }
}, },
labels: function($activeLabels) {
$activeLabels = $activeLabels || $module.find(selector.label).filter('.' + className.active);
module.verbose('Removing active labels', $activeLabels);
$activeLabels
.each(function(){
module.remove.selected($(this).data('value'));
})
;
},
tabbable: function() { tabbable: function() {
if( module.has.search() ) { if( module.has.search() ) {
module.debug('Searchable dropdown initialized'); module.debug('Searchable dropdown initialized');
@ -1576,6 +1652,9 @@ $.fn.dropdown = function(parameters) {
: $menu.is(':animated') || $menu.transition && $menu.transition('is animating') : $menu.is(':animated') || $menu.transition && $menu.transition('is animating')
; ;
}, },
focusedOnSearch: function() {
return (document.activeElement === $search[0]);
},
allFiltered: function() { allFiltered: function() {
return ($item.filter('.' + className.filtered).length === $item.length); return ($item.filter('.' + className.filtered).length === $item.length);
}, },
@ -1688,36 +1767,6 @@ $.fn.dropdown = function(parameters) {
}) })
; ;
} }
else if(settings.transition == 'slide down') {
start();
$currentMenu
.hide()
.clearQueue()
.children()
.clearQueue()
.css('opacity', 0)
.delay(50)
.animate({
opacity : 1
}, settings.duration, 'easeOutQuad', module.event.resetStyle)
.end()
.slideDown(100, 'easeOutQuad', function() {
module.event.resetStyle.call(this);
callback.call(element);
})
;
}
else if(settings.transition == 'fade') {
start();
$currentMenu
.hide()
.clearQueue()
.fadeIn(settings.duration, function() {
module.event.resetStyle.call(this);
callback.call(element);
})
;
}
else { else {
module.error(error.transition, settings.transition); module.error(error.transition, settings.transition);
} }
@ -1773,36 +1822,6 @@ $.fn.dropdown = function(parameters) {
}) })
; ;
} }
else if(settings.transition == 'slide down') {
start();
$currentMenu
.show()
.clearQueue()
.children()
.clearQueue()
.css('opacity', 1)
.animate({
opacity : 0
}, 100, 'easeOutQuad', module.event.resetStyle)
.end()
.delay(50)
.slideUp(100, 'easeOutQuad', function() {
module.event.resetStyle.call(this);
callback.call(element);
})
;
}
else if(settings.transition == 'fade') {
start();
$currentMenu
.show()
.clearQueue()
.fadeOut(150, function() {
module.event.resetStyle.call(this);
callback.call(element);
})
;
}
else { else {
module.error(error.transition); module.error(error.transition);
} }
@ -2045,10 +2064,11 @@ $.fn.dropdown.settings = {
duration : 250, duration : 250,
/* Callbacks */ /* Callbacks */
onNoResults : function(searchTerm){},
onChange : function(value, text){},
onShow : function(){},
onHide : function(){},
onLabelClick : function($selectedLabels){},
onNoResults : function(searchTerm){},
onChange : function(value, text){},
onShow : function(){},
onHide : function(){},
/* Component */ /* Component */
@ -2076,6 +2096,7 @@ $.fn.dropdown.settings = {
input : '> input[type="hidden"], > select', input : '> input[type="hidden"], > select',
item : '.item', item : '.item',
label : '> .label', label : '> .label',
remove : '> .label > .delete.icon',
menu : '.menu', menu : '.menu',
menuIcon : '.dropdown.icon', menuIcon : '.dropdown.icon',
search : 'input.search, .menu > .search > input', search : 'input.search, .menu > .search > input',

1
src/definitions/modules/dropdown.less

@ -606,6 +606,7 @@ select.ui.dropdown {
/* Selection Label */ /* Selection Label */
.ui.multiple.dropdown > .label { .ui.multiple.dropdown > .label {
user-select: none;
display: inline-block; display: inline-block;
vertical-align: baseline; vertical-align: baseline;
padding: @labelPadding; padding: @labelPadding;

Loading…
Cancel
Save