@gitlab/ui
Version:
GitLab UI Components
406 lines (347 loc) • 11.3 kB
JavaScript
import _uniqueId from 'lodash/uniqueId';
import _clamp from 'lodash/clamp';
import { stopEvent } from '../../../../utils/utils';
import { GL_DROPDOWN_SHOWN, GL_DROPDOWN_HIDDEN, HOME, END, ARROW_UP, ARROW_DOWN } from '../constants';
import { buttonCategoryOptions, dropdownVariantOptions, buttonSizeOptions } from '../../../../utils/constants';
import GlBaseDropdown from '../base_dropdown/base_dropdown';
import GlListboxItem from './listbox_item';
import GlListboxGroup from './listbox_group';
import { itemsValidator, isOption, flattenedOptions } from './utils';
import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';
const ITEM_SELECTOR = '[role="option"]';
const GROUP_TOP_BORDER_CLASSES = ['gl-border-t', 'gl-pt-3', 'gl-mt-3'];
var script = {
events: {
GL_DROPDOWN_SHOWN,
GL_DROPDOWN_HIDDEN
},
components: {
GlBaseDropdown,
GlListboxItem,
GlListboxGroup
},
model: {
prop: 'selected',
event: 'select'
},
props: {
/**
* Items to display in the dropdown
*/
items: {
type: Array,
required: false,
default: () => [],
validator: itemsValidator
},
/**
* array of selected items values for multi-select and selected item value for single-select
*/
selected: {
type: [Array, String, Number],
required: false,
default: () => []
},
/**
* Allow multi-selection
*/
multiple: {
type: Boolean,
required: false,
default: false
},
/**
* Toggle button text
*/
toggleText: {
type: String,
required: false,
default: ''
},
/**
* Toggle text to be read by screen readers only
*/
textSrOnly: {
type: Boolean,
required: false,
default: false
},
/**
* Styling option - dropdown's toggle category
*/
category: {
type: String,
required: false,
default: buttonCategoryOptions.primary,
validator: value => Object.keys(buttonCategoryOptions).includes(value)
},
/**
* Styling option - dropdown's toggle variant
*/
variant: {
type: String,
required: false,
default: dropdownVariantOptions.default,
validator: value => Object.keys(dropdownVariantOptions).includes(value)
},
/**
* The size of the dropdown toggle
*/
size: {
type: String,
required: false,
default: buttonSizeOptions.medium,
validator: value => Object.keys(buttonSizeOptions).includes(value)
},
/**
* Icon name that will be rendered in the toggle button
*/
icon: {
type: String,
required: false,
default: ''
},
/**
* Set to "true" to disable the dropdown
*/
disabled: {
type: Boolean,
required: false,
default: false
},
/**
* Set to "true" when dropdown content (items) is loading
*/
loading: {
type: Boolean,
required: false,
default: false
},
/**
* Additional CSS classes to customize toggle appearance
*/
toggleClass: {
type: [String, Array, Object],
required: false,
default: null
},
/**
* Set to "true" to hide the caret
*/
noCaret: {
type: Boolean,
required: false,
default: false
},
/**
* Right align listbox menu with respect to the toggle button
*/
right: {
type: Boolean,
required: false,
default: false
},
/**
* Center selected item checkmark
*/
isCheckCentered: {
type: Boolean,
required: false,
default: false
},
/**
* The `aria-labelledby` attribute value for the toggle button
*/
ariaLabelledby: {
type: String,
required: false,
default: null
}
},
data() {
return {
selectedValues: [],
toggleId: _uniqueId('dropdown-toggle-btn-'),
nextFocusedItemIndex: null
};
},
computed: {
listboxTag() {
if (this.items.length === 0 || isOption(this.items[0])) return 'ul';
return 'div';
},
flattenedOptions() {
return flattenedOptions(this.items);
},
listboxToggleText() {
if (!this.toggleText) {
if (!this.multiple && this.selectedValues.length) {
var _this$flattenedOption;
return (_this$flattenedOption = this.flattenedOptions.find(_ref => {
let {
value
} = _ref;
return value === this.selectedValues[0];
})) === null || _this$flattenedOption === void 0 ? void 0 : _this$flattenedOption.text;
}
return '';
}
return this.toggleText;
},
selectedIndices() {
return this.selectedValues.map(selected => this.flattenedOptions.findIndex(_ref2 => {
let {
value
} = _ref2;
return value === selected;
})).sort();
}
},
watch: {
selected: {
immediate: true,
handler(newSelected) {
if (Array.isArray(newSelected)) {
if (!this.multiple && newSelected.length) {
throw new Error('To allow multi-selection, please, set "multiple" property to "true"');
}
this.selectedValues = [...newSelected];
} else {
this.selectedValues = [newSelected];
}
}
}
},
methods: {
groupClasses(index) {
return index === 0 ? null : GROUP_TOP_BORDER_CLASSES;
},
onShow() {
this.$nextTick(() => {
var _this$selectedIndices;
this.focusItem((_this$selectedIndices = this.selectedIndices[0]) !== null && _this$selectedIndices !== void 0 ? _this$selectedIndices : 0, this.getFocusableListItemElements());
/**
* Emitted when dropdown is shown
*
* @event shown
*/
this.$emit(GL_DROPDOWN_SHOWN);
});
},
onHide() {
/**
* Emitted when dropdown is hidden
*
* @event hidden
*/
this.$emit(GL_DROPDOWN_HIDDEN);
this.nextFocusedItemIndex = null;
},
onKeydown(event) {
const {
code
} = event;
const elements = this.getFocusableListItemElements();
if (elements.length < 1) return;
let stop = true;
if (code === HOME) {
this.focusItem(0, elements);
} else if (code === END) {
this.focusItem(elements.length - 1, elements);
} else if (code === ARROW_UP) {
this.focusNextItem(event, elements, -1);
} else if (code === ARROW_DOWN) {
this.focusNextItem(event, elements, 1);
} else {
stop = false;
}
if (stop) {
stopEvent(event);
}
},
getFocusableListItemElements() {
const items = this.$refs.list.querySelectorAll(ITEM_SELECTOR);
return Array.from(items);
},
focusNextItem(event, elements, offset) {
const {
target
} = event;
const currentIndex = elements.indexOf(target);
const nextIndex = _clamp(currentIndex + offset, 0, elements.length - 1);
this.focusItem(nextIndex, elements);
},
focusItem(index, elements) {
this.nextFocusedItemIndex = index;
this.$nextTick(() => {
var _elements$index;
(_elements$index = elements[index]) === null || _elements$index === void 0 ? void 0 : _elements$index.focus();
});
},
onSelect(_ref3, isSelected) {
let {
value
} = _ref3;
if (this.multiple) {
this.onMultiSelect(value, isSelected);
} else {
this.onSingleSelect(value, isSelected);
}
},
isSelected(item) {
return this.selectedValues.some(value => value === item.value);
},
isFocused(item) {
return this.nextFocusedItemIndex === this.flattenedOptions.indexOf(item);
},
onSingleSelect(value, isSelected) {
if (isSelected) {
/**
* Emitted when selection is changed
*
* @event select
*/
this.$emit('select', value);
}
this.$refs.baseDropdown.closeAndFocus();
},
onMultiSelect(value, isSelected) {
if (isSelected) {
this.$emit('select', [...this.selectedValues, value]);
} else {
this.$emit('select', this.selectedValues.filter(selectedValue => selectedValue !== value));
}
},
isOption
}
};
/* script */
const __vue_script__ = script;
/* template */
var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('gl-base-dropdown',{ref:"baseDropdown",attrs:{"aria-haspopup":"listbox","aria-labelledby":_vm.ariaLabelledby,"toggle-id":_vm.toggleId,"toggle-text":_vm.listboxToggleText,"toggle-class":_vm.toggleClass,"text-sr-only":_vm.textSrOnly,"category":_vm.category,"variant":_vm.variant,"size":_vm.size,"icon":_vm.icon,"disabled":_vm.disabled,"loading":_vm.loading,"no-caret":_vm.noCaret,"right":_vm.right},on:_vm._d({},[_vm.$options.events.GL_DROPDOWN_SHOWN,_vm.onShow,_vm.$options.events.GL_DROPDOWN_HIDDEN,_vm.onHide])},[_vm._t("header"),_vm._v(" "),_c(_vm.listboxTag,{ref:"list",tag:"component",staticClass:"gl-new-dropdown-contents gl-list-style-none gl-pl-0 gl-mb-0",attrs:{"aria-labelledby":_vm.toggleId,"role":"listbox","tabindex":"-1"},on:{"keydown":_vm.onKeydown}},[_vm._l((_vm.items),function(item,index){return [(_vm.isOption(item))?[_c('gl-listbox-item',{key:item.value,attrs:{"is-selected":_vm.isSelected(item),"is-focused":_vm.isFocused(item),"is-check-centered":_vm.isCheckCentered},on:{"select":function($event){return _vm.onSelect(item, $event)}}},[_vm._t("list-item",function(){return [_vm._v("\n "+_vm._s(item.text)+"\n ")]},{"item":item})],2)]:[_c('gl-listbox-group',{key:item.text,class:_vm.groupClasses(index),attrs:{"name":item.text},scopedSlots:_vm._u([(_vm.$scopedSlots['group-label'])?{key:"group-label",fn:function(){return [_vm._t("group-label",null,{"group":item})]},proxy:true}:null],null,true)},[_vm._v(" "),_vm._l((item.options),function(option){return _c('gl-listbox-item',{key:option.value,attrs:{"is-selected":_vm.isSelected(option),"is-focused":_vm.isFocused(option),"is-check-centered":_vm.isCheckCentered},on:{"select":function($event){return _vm.onSelect(option, $event)}}},[_vm._t("list-item",function(){return [_vm._v("\n "+_vm._s(option.text)+"\n ")]},{"item":option})],2)})],2)]]})],2),_vm._v(" "),_vm._t("footer")],2)};
var __vue_staticRenderFns__ = [];
/* style */
const __vue_inject_styles__ = undefined;
/* scoped */
const __vue_scope_id__ = undefined;
/* module identifier */
const __vue_module_identifier__ = undefined;
/* functional template */
const __vue_is_functional_template__ = false;
/* style inject */
/* style inject SSR */
/* style inject shadow dom */
const __vue_component__ = __vue_normalize__(
{ render: __vue_render__, staticRenderFns: __vue_staticRenderFns__ },
__vue_inject_styles__,
__vue_script__,
__vue_scope_id__,
__vue_is_functional_template__,
__vue_module_identifier__,
false,
undefined,
undefined,
undefined
);
export default __vue_component__;
export { ITEM_SELECTOR };