@gitlab/ui
Version:
GitLab UI Components
382 lines (328 loc) • 9.8 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 __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';
const ITEM_SELECTOR = '[role="option"]';
var script = {
events: {
GL_DROPDOWN_SHOWN,
GL_DROPDOWN_HIDDEN
},
components: {
GlBaseDropdown,
GlListboxItem
},
model: {
prop: 'selected',
event: 'select'
},
props: {
/**
* Items to display in the dropdown
*/
items: {
type: Array,
required: false,
default: () => [],
validator: items => {
return items.every(_ref => {
let {
value
} = _ref;
return typeof value === 'string';
});
}
},
/**
* 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
},
/**
* 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: {
listboxToggleText() {
if (!this.toggleText) {
if (!this.multiple && this.selectedValues.length) {
var _this$items$find;
return (_this$items$find = this.items.find(_ref2 => {
let {
value
} = _ref2;
return value === this.selectedValues[0];
})) === null || _this$items$find === void 0 ? void 0 : _this$items$find.text;
}
return '';
}
return this.toggleText;
},
selectedIndices() {
return this.selectedValues.map(selected => this.items.findIndex(_ref3 => {
let {
value
} = _ref3;
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: {
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(_ref4, isSelected) {
let {
value
} = _ref4;
if (this.multiple) {
this.onMultiSelect(value, isSelected);
} else {
this.onSingleSelect(value, isSelected);
}
},
isSelected(item) {
return this.selectedValues.some(value => value === item.value);
},
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));
}
}
}
};
/* 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('ul',{ref:"list",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 _c('gl-listbox-item',{key:item.value,attrs:{"is-selected":_vm.isSelected(item),"is-focused":_vm.nextFocusedItemIndex === index},on:{"select":function($event){return _vm.onSelect(item, $event)}}},[_vm._t("list-item",[_vm._v("\n "+_vm._s(item.text)+"\n ")],{"item":item})],2)}),1),_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 };