UNPKG

semantic-ui-riot

Version:
928 lines (812 loc) 25.5 kB
let index = 0; const keys = { enter: 13, escape: 27, upArrow: 38, downArrow: 40, }; const reservedClasses = [ 'ui', 'selection', 'dropdown', 'search', 'multiple', 'active', 'visible', 'upward', ]; // =================================================================================== // Lifecycle // ========= function onBeforeMount(props, state) { this.su_id = `su-dropdown-${index++}`; if (props.items && props.items.length > 0) { state.label = props.items[0].label; state.value = props.items[0].value; state.defaultFlg = props.items[0].default; } } function onMounted(props, state) { if (typeof props.value !== 'undefined') { state.value = props.value; this.lastPropValue = props.value; } if (props.multiple) { props.items.forEach(item => item.selected = false); props.items .filter(item => state.value && state.value.indexOf(item.value) >= 0) .forEach(item => item.selected = true); state.value = this.props.items.filter(item => item.selected).map(item => item.value); this.state.selectedFlg = this.props.items.some(item => item.selected); } state.defaultValue = state.value; if (props.search) { this.$('.search').addEventListener('compositionstart', () => this.compositionStarted = true); this.$('.search').addEventListener('compositionend', () => this.compositionStarted = false); } this.update(); } function onBeforeUpdate(props, state) { if (this.lastPropValue !== props.value) { state.value = props.value; this.lastPropValue = props.value; } state.classes = prepareClasses(this); prepareItemClasses(props.items, this.value, this.filtered); this.readonly = this.root.classList.contains('read-only'); this.disabled = this.root.classList.contains('disabled'); this.tabindex = props.tabindex || '0'; if (props.multiple) { props.items.forEach(item => item.selected = false); props.items.filter(item => state.value && state.value.indexOf(item.value) >= 0).forEach(item => item.selected = true); selectMultiTarget(this, true); this.viewValue = state.value.join(','); } else if (props.items) { const selected = props.items.filter(item => item.value == state.value); if (selected && selected.length > 0) { const target = selected[0]; if (state.label !== target.label) { selectTarget(this, target, true); } } else if (props.items && props.items.length > 0) { if (state.value !== props.items[0].value) { state.value = props.items[0].value; state.defaultFlg = props.items[0].default; } if (state.label != props.items[0].label) { state.label = props.items[0].label; } } } if (props.multiple) { const value = state.value ? state.value : []; const defaultValue = state.defaultValue ? state.defaultValue : []; this.changed = value.toString() !== defaultValue.toString(); } else { this.changed = state.value !== state.defaultValue; } } // =================================================================================== // Events // ====== function onToggle() { if (!this.visibleFlg) { open(this); } else { close(this); } } function onFocus() { open(this); } function onMousedown() { this.itemActivated = true; } function onMouseup() { this.itemActivated = false; } function onBlur() { if (!this.itemActivated) { close(this); } } function onItemClick(event, item) { event.stopPropagation(); if (!this.isItem(item)) { return } if (this.props.multiple) { if (!item.default) { item.selected = true; } selectMultiTarget(this); return } selectTarget(this, item); close(this); } function onKeydown(event) { const keyCode = event.keyCode; if (keyCode == keys.escape) { close(this); } if (keyCode == keys.downArrow) { open(this); } if (keyCode != keys.upArrow && keyCode != keys.downArrow) { return } event.preventDefault(); const searchedItems = this.props.items.filter(item => { if (this.props.search && !item.searched) { return false } if (this.props.multiple && (item.default || item.selected)) { return false } return true }); if (searchedItems.length == 0) { return } if (searchedItems.every(item => !item.active)) { searchedItems[0].active = true; this.update(); return } const activeIndex = parseInt(searchedItems.map((item, index) => item.active ? index : -1).filter(index => index >= 0)); if (keyCode == keys.upArrow) { const nextActiveItem = searchedItems.filter((item, index) => index < activeIndex && !item.header && !item.divider); if (nextActiveItem.length > 0) { searchedItems[activeIndex].active = false; nextActiveItem[nextActiveItem.length - 1].active = true; } } else if (keyCode == keys.downArrow) { const nextActiveItem = searchedItems.filter((item, index) => index > activeIndex && !item.header && !item.divider); if (nextActiveItem.length > 0) { searchedItems[activeIndex].active = false; nextActiveItem[0].active = true; } } this.update(); scrollPosition(this); } function onKeyup(event) { const keyCode = event.keyCode; if (keyCode != keys.enter || this.compositionStarted) { return } const searchedItems = this.props.items.filter(item => item.searched && !item.selected); const index = parseInt(searchedItems.map((item, index) => item.active ? index : -1).filter(index => index >= 0)); const activeItem = searchedItems[index]; if (!activeItem) { return } if (this.props.multiple) { activeItem.selected = true; activeItem.active = false; if (index < searchedItems.length - 1) { searchedItems[index + 1].active = true; } else if (index > 0) { searchedItems[index - 1].active = true; } selectMultiTarget(this); } else { activeItem.active = false; selectTarget(this, activeItem); close(this); } } function stopPropagation(event) { event.stopPropagation(); } // ----------------------------------------------------- // search option // ------------- function onInput(event) { const value = event.target.value; this.filtered = value.length > 0; search(this, value); } // ----------------------------------------------------- // multiple option // --------------- function onUnselect(event, target) { event.stopPropagation(); target.selected = false; selectMultiTarget(this); } // =================================================================================== // Logic // ===== function open(tag) { if (tag.opening || tag.closing || tag.visibleFlg || tag.readonly || tag.disabled) { return } tag.opening = true; search(tag, ''); tag.upward = isUpward(tag); tag.state.transitionStatus = `visible animating in slide ${tag.upward ? 'up' : 'down'}`; tag.props.items.forEach(item => item.active = false); setTimeout(() => { tag.opening = false; tag.visibleFlg = true; tag.state.transitionStatus = 'visible'; tag.update(); }, 300); if (tag.props.search) { tag.$('.search').focus(); } tag.update(); scrollPosition(tag); tag.dispatch('open'); } function close(tag) { if (tag.closing || !tag.visibleFlg) { return } tag.closing = true; tag.state.transitionStatus = `visible animating out slide ${tag.upward ? 'up' : 'down'}`; setTimeout(() => { tag.closing = false; tag.visibleFlg = false; tag.state.transitionStatus = 'hidden'; tag.update(); }, 300); if (tag.props.search) { tag.$('.search').blur(); if (tag.filtered && tag.filteredItems.length > 0) { selectTarget(tag, tag.filteredItems[0]); } else { tag.$('.search').value = ''; tag.filtered = false; } } tag.update(); tag.dispatch('close'); } function selectTarget(tag, target, updating) { if (tag.state.value == target.value && tag.state.label == target.label && tag.state.defaultFlg == target.default) { if (!updating) { tag.dispatch('select', target); } return } tag.state.value = target.value; tag.state.label = target.label; tag.state.defaultFlg = target.default; if (tag.props.search) { tag.$('.search').value = ''; tag.filtered = false; } if (!updating) { tag.update(); parentUpdate(tag); tag.dispatch('select', target); tag.dispatch('change', target); } } function selectMultiTarget(tag, updating) { if (JSON.stringify(tag.state.value) == JSON.stringify(tag.props.items.filter(item => item.selected).map(item => item.value)) && tag.state.selectedFlg == tag.props.items.some(item => item.selected)) { if (!updating) { tag.dispatch('select', tag.props.items.filter(item => item.selected)); } return } tag.state.value = tag.props.items.filter(item => item.selected).map(item => item.value); tag.state.selectedFlg = tag.props.items.some(item => item.selected); if (!updating) { tag.update(); parentUpdate(tag); tag.dispatch('select', tag.props.items.filter(item => item.selected)); tag.dispatch('change', tag.props.items.filter(item => item.selected)); } } function search(tag, target) { const convert = tag.props.searchKeyConvert || toLowerCase; tag.props.items.forEach(item => { const searchKeys = prepareSearchKey(item); item.searched = searchKeys.some(key => convert(key).indexOf(convert(target)) >= 0); }); tag.filteredItems = tag.props.items.filter(item => { return item.searched }); tag.update(); tag.dispatch('search'); } function prepareSearchKey(item) { if (typeof item.searchKey === 'undefined') { return [item.label] } if (Array.isArray(item.searchKey)) { return [item.label, ...item.searchKey] } return [item.label, item.searchKey] } function toLowerCase(target) { return target.toLowerCase() } function scrollPosition(tag) { const menu = tag.root.querySelector('.menu'); const item = tag.root.querySelector('.item.hover'); if (menu && item) { const menuScroll = menu.scrollTop; const itemOffset = item.offsetTop; const itemHeight = parseInt(document.defaultView.getComputedStyle(item, null).height.replace('px', '')); const menuHeight = parseInt(document.defaultView.getComputedStyle(menu, null).height.replace('px', '')); const belowPage = menuScroll + menuHeight < itemOffset + itemHeight; const abovePage = itemOffset < menuScroll; if (abovePage || belowPage) { menu.scrollTop = itemOffset; } } } function parentUpdate(tag) { tag.obs.trigger(`${tag.props.suParentId}-update`); } function prepareClasses(tag) { const classes = tag.props.class ? tag.props.class.split(' ').filter(propClass => !reservedClasses.includes(propClass)) : []; if (tag.props.search) { classes.push('search'); } if (tag.props.multiple) { classes.push('multiple'); } if (isActive(tag)) { classes.push('active visible'); } if (tag.upward) { classes.push('upward'); } return classes.join(' ') } function prepareItemClasses(items, value, filtered) { items.forEach(item => { const classes = []; if (isItem(item)) { classes.push('item'); } if (item.header && !filtered) { classes.push('header'); } if (item.divider && !filtered) { classes.push('divider'); } if (item.default) { classes.push('default'); } if (item.active) { classes.push('hover'); } if (item.value == value) { classes.push('active selected'); } if (item.disabled) { classes.push('disabled'); } item.classes = classes.join(' '); }); } function isUpward(tag) { if (tag.props.direction == 'upward') { return true } if (tag.props.direction == 'downward') { return false } const dropdown = tag.root.getBoundingClientRect(); const windowHeight = document.documentElement.offsetHeight || document.body.offsetHeight; const menuHeight = tag.root.querySelector('.menu').getBoundingClientRect().height; const above = menuHeight <= dropdown.top; const below = windowHeight >= dropdown.top + dropdown.height + menuHeight; if (below) { return false } if (!below && !above) { return false } return true } function isItem(item) { return item.searched && !item.header && !item.divider } function isActive(tag) { if (tag.closing) { return false } return tag.opening || tag.visibleFlg } function isVisible(item) { if (this.props.multiple && item.default) { return false } if (item.selected) { return false } return item.searched || item.divider || item.header } var suDropdown = { 'css': `su-dropdown.ui.dropdown .menu>.item.default,[is="su-dropdown"].ui.dropdown .menu>.item.default{ color: rgba(0, 0, 0, 0.4) } su-dropdown.ui.dropdown .menu>.item.hover,[is="su-dropdown"].ui.dropdown .menu>.item.hover{ background: rgba(0, 0, 0, .05); color: rgba(0, 0, 0, .95); } su-dropdown.ui.dropdown .menu,[is="su-dropdown"].ui.dropdown .menu{ display: block; }`, 'exports': { state: { defaultValue: '', filtered: false, label: '', selectedFlg: false, transitionStatus: 'hidden', value: '', classes: '', }, lastPropValue: '', changed: false, visibleFlg: false, compositionStarted: false, onBeforeMount, onMounted, onBeforeUpdate, onBlur, onFocus, onInput, onItemClick, onKeydown, onKeyup, onMousedown, onMouseup, onToggle, onUnselect, stopPropagation, isItem, isVisible }, 'template': function(template, expressionTypes, bindingTypes, getComponent) { return template( '<i class="dropdown icon"></i><input expr15="expr15" class="search" autocomplete="off"/><a expr16="expr16" class="ui label transition visible" style="display: inline-block !important;"></a><div expr18="expr18"></div><div expr19="expr19" tabindex="-1"><div expr20="expr20"></div><div expr25="expr25" class="message"></div></div>', [{ 'expressions': [{ 'type': expressionTypes.ATTRIBUTE, 'name': 'class', 'evaluate': function(scope) { return ['ui selection dropdown ', scope.state.classes].join(''); } }, { 'type': expressionTypes.EVENT, 'name': 'onclick', 'evaluate': function(scope) { return scope.onToggle; } }, { 'type': expressionTypes.EVENT, 'name': 'onfocus', 'evaluate': function(scope) { return scope.onFocus; } }, { 'type': expressionTypes.EVENT, 'name': 'onmousedown', 'evaluate': function(scope) { return scope.onMousedown; } }, { 'type': expressionTypes.EVENT, 'name': 'onmouseup', 'evaluate': function(scope) { return scope.onMouseup; } }, { 'type': expressionTypes.EVENT, 'name': 'onblur', 'evaluate': function(scope) { return scope.onBlur; } }, { 'type': expressionTypes.EVENT, 'name': 'onkeydown', 'evaluate': function(scope) { return scope.onKeydown; } }, { 'type': expressionTypes.EVENT, 'name': 'onkeyup', 'evaluate': function(scope) { return scope.onKeyup; } }, { 'type': expressionTypes.ATTRIBUTE, 'name': 'tabindex', 'evaluate': function(scope) { return scope.props.search ? -1 : scope.tabindex; } }, { 'type': expressionTypes.ATTRIBUTE, 'name': 'value', 'evaluate': function(scope) { return scope.props.multiple ? scope.viewValue : scope.state.value; } }, { 'type': expressionTypes.ATTRIBUTE, 'name': 'label', 'evaluate': function(scope) { return scope.state.label; } }, { 'type': expressionTypes.ATTRIBUTE, 'name': 'changed', 'evaluate': function(scope) { return scope.changed; } }, { 'type': expressionTypes.ATTRIBUTE, 'name': 'id', 'evaluate': function(scope) { return scope.su_id; } }] }, { 'type': bindingTypes.IF, 'evaluate': function(scope) { return scope.props.search; }, 'redundantAttribute': 'expr15', 'selector': '[expr15]', 'template': template(null, [{ 'expressions': [{ 'type': expressionTypes.ATTRIBUTE, 'name': 'tabindex', 'evaluate': function(scope) { return scope.tabindex; } }, { 'type': expressionTypes.EVENT, 'name': 'oninput', 'evaluate': function(scope) { return scope.onInput; } }, { 'type': expressionTypes.EVENT, 'name': 'onclick', 'evaluate': function(scope) { return scope.stopPropagation; } }, { 'type': expressionTypes.EVENT, 'name': 'onfocus', 'evaluate': function(scope) { return scope.onFocus; } }, { 'type': expressionTypes.EVENT, 'name': 'onblur', 'evaluate': function(scope) { return scope.onBlur; } }, { 'type': expressionTypes.ATTRIBUTE, 'name': 'readonly', 'evaluate': function(scope) { return scope.readonly; } }] }]) }, { 'type': bindingTypes.EACH, 'getKey': null, 'condition': function(scope) { return scope.item.selected; }, 'template': template(' <i expr17="expr17" class="delete icon"></i>', [{ 'expressions': [{ 'type': expressionTypes.TEXT, 'childNodeIndex': 0, 'evaluate': function(scope) { return [scope.item.label].join(''); } }, { 'type': expressionTypes.EVENT, 'name': 'onclick', 'evaluate': function(scope) { return scope.stopPropagation; } }] }, { 'redundantAttribute': 'expr17', 'selector': '[expr17]', 'expressions': [{ 'type': expressionTypes.EVENT, 'name': 'onclick', 'evaluate': function(scope) { return event => scope.onUnselect(event, scope.item); } }] }]), 'redundantAttribute': 'expr16', 'selector': '[expr16]', 'itemName': 'item', 'indexName': null, 'evaluate': function(scope) { return scope.props.items; } }, { 'type': bindingTypes.IF, 'evaluate': function(scope) { return !scope.props.multiple || !scope.state.selectedFlg; }, 'redundantAttribute': 'expr18', 'selector': '[expr18]', 'template': template(' ', [{ 'expressions': [{ 'type': expressionTypes.TEXT, 'childNodeIndex': 0, 'evaluate': function(scope) { return [scope.state.label].join(''); } }, { 'type': expressionTypes.ATTRIBUTE, 'name': 'class', 'evaluate': function(scope) { return [ scope.state.defaultFlg && 'default', ' text ', scope.filtered && 'filtered' ].join(''); } }] }]) }, { 'redundantAttribute': 'expr19', 'selector': '[expr19]', 'expressions': [{ 'type': expressionTypes.ATTRIBUTE, 'name': 'class', 'evaluate': function(scope) { return ['menu transition ', scope.state.transitionStatus].join(''); } }, { 'type': expressionTypes.EVENT, 'name': 'onmousedown', 'evaluate': function(scope) { return scope.onMousedown; } }, { 'type': expressionTypes.EVENT, 'name': 'onmouseup', 'evaluate': function(scope) { return scope.onMouseup; } }, { 'type': expressionTypes.EVENT, 'name': 'onblur', 'evaluate': function(scope) { return scope.onBlur; } }] }, { 'type': bindingTypes.EACH, 'getKey': null, 'condition': function(scope) { return scope.isVisible(scope.item); }, 'template': template( '<i expr21="expr21"></i><img expr22="expr22" class="ui avatar image"/><span expr23="expr23" class="description"></span><span expr24="expr24" class="text"> </span>', [{ 'expressions': [{ 'type': expressionTypes.ATTRIBUTE, 'name': 'value', 'evaluate': function(scope) { return scope.item.value; } }, { 'type': expressionTypes.ATTRIBUTE, 'name': 'default', 'evaluate': function(scope) { return scope.item.default; } }, { 'type': expressionTypes.EVENT, 'name': 'onmousedown', 'evaluate': function(scope) { return scope.onMousedown; } }, { 'type': expressionTypes.EVENT, 'name': 'onmouseup', 'evaluate': function(scope) { return scope.onMouseup; } }, { 'type': expressionTypes.ATTRIBUTE, 'name': 'class', 'evaluate': function(scope) { return scope.item.classes; } }, { 'type': expressionTypes.EVENT, 'name': 'onclick', 'evaluate': function(scope) { return event => scope.onItemClick(event, scope.item); } }] }, { 'type': bindingTypes.IF, 'evaluate': function(scope) { return scope.item.icon; }, 'redundantAttribute': 'expr21', 'selector': '[expr21]', 'template': template(null, [{ 'expressions': [{ 'type': expressionTypes.ATTRIBUTE, 'name': 'class', 'evaluate': function(scope) { return [scope.item.icon, ' icon'].join(''); } }] }]) }, { 'type': bindingTypes.IF, 'evaluate': function(scope) { return scope.item.image; }, 'redundantAttribute': 'expr22', 'selector': '[expr22]', 'template': template(null, [{ 'expressions': [{ 'type': expressionTypes.ATTRIBUTE, 'name': 'src', 'evaluate': function(scope) { return scope.item.image; } }] }]) }, { 'type': bindingTypes.IF, 'evaluate': function(scope) { return scope.item.description; }, 'redundantAttribute': 'expr23', 'selector': '[expr23]', 'template': template(' ', [{ 'expressions': [{ 'type': expressionTypes.TEXT, 'childNodeIndex': 0, 'evaluate': function(scope) { return scope.item.description; } }] }]) }, { 'redundantAttribute': 'expr24', 'selector': '[expr24]', 'expressions': [{ 'type': expressionTypes.TEXT, 'childNodeIndex': 0, 'evaluate': function(scope) { return scope.item.label; } }] }] ), 'redundantAttribute': 'expr20', 'selector': '[expr20]', 'itemName': 'item', 'indexName': null, 'evaluate': function(scope) { return scope.props.items; } }, { 'type': bindingTypes.IF, 'evaluate': function(scope) { return scope.filtered && scope.filteredItems.length == 0; }, 'redundantAttribute': 'expr25', 'selector': '[expr25]', 'template': template('No results found.', []) }] ); }, 'name': 'su-dropdown' }; export default suDropdown;