UNPKG

@gitlab/ui

Version:
406 lines (347 loc) • 11.3 kB
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 };