@inkline/inkline
Version:
Inkline is the Vue.js UI/UX Library built for creating your next design system
299 lines • 9.51 kB
JavaScript
/* eslint-disable no-case-declarations */
import { defineComponent } from 'vue';
import { PopupMixin, PopupControlsMixin, sizePropValidator, colorVariantClass, defaultPropValue } from '../../mixins/index.mjs';
import { ClickOutside } from '../../directives/index.mjs';
import { on, off, isFocusable, isKey } from '../../helpers/index.mjs';
/**
* Slot for dropdown trigger
* @name default
* @kind slot
*/
/**
* Slot for dropdown header content
* @name header
* @kind slot
*/
/**
* Slot for dropdown body content
* @name body
* @kind slot
*/
/**
* Slot for dropdown footer content
* @name footer
* @kind slot
*/
const componentName = 'IDropdown';
export default defineComponent({
name: componentName,
directives: {
ClickOutside
},
mixins: [
PopupMixin,
PopupControlsMixin
],
provide() {
return {
dropdown: this
};
},
props: {
/**
* The duration of the hide and show animation
* @type Number
* @default 300
* @name animationDuration
*/
animationDuration: {
type: Number,
default: 300
},
/**
* The color variant of the dropdown
* @type light | dark
* @default light
* @name color
*/
color: {
type: String,
default: defaultPropValue(componentName, 'color')
},
/**
* The disabled state of the dropdown
* @type Boolean
* @default false
* @name disabled
*/
disabled: {
type: Boolean,
default: false
},
/**
* Used to hide the dropdown when clicking or selecting a dropdown item
* @type Boolean
* @default false
* @name hideOnItemClick
*/
hideOnItemClick: {
type: Boolean,
default: true
},
/**
* The keydown events bound to the trigger element
* @type string[]
* @default [up, down, enter, space, tab, esc]
* @name keydownTrigger
*/
keydownTrigger: {
type: Array,
default: () => ['up', 'down', 'enter', 'space', 'tab', 'esc']
},
/**
* The keydown events bound to the dropdown item elements
* @type string[]
* @default [up, down, enter, space, tab, esc]
* @name keydownItem
*/
keydownItem: {
type: Array,
default: () => ['up', 'down', 'enter', 'space', 'tab', 'esc']
},
/**
* Used to manually control the visibility of the dropdown
* @type Boolean
* @default false
* @name modelValue
*/
modelValue: {
type: Boolean,
default: false
},
/**
* Displays an arrow on the dropdown pointing to the trigger element
* @type Boolean
* @default true
* @name arrow
*/
arrow: {
type: Boolean,
default: true
},
/**
* The placement of the dropdown
* @type top | top-start | top-end | bottom | bottom-start | bottom-end | left | left-start | left-end | right | right-start | right-end
* @default false
* @name placement
*/
placement: {
type: String,
default: 'bottom'
},
/**
* The events used to trigger the dropdown
* @type hover | focus | click | manual
* @default [click]
* @name trigger
*/
trigger: {
type: [String, Array],
default: () => ['click']
},
/**
* The offset of the dropdown relative to the trigger element
* @type Number
* @default 6
* @name offset
*/
offset: {
type: Number,
default: 6
},
/**
* Used to override the popper.js options used for creating the dropdown
* @type Object
* @default {}
* @name popperOptions
*/
popperOptions: {
type: Object,
default: () => ({})
},
/**
* The size variant of the dropdown
* @type sm | md | lg
* @default md
* @name size
*/
size: {
type: String,
default: defaultPropValue(componentName, 'size'),
validator: sizePropValidator
}
},
emits: [
/**
* Event emitted for setting the modelValue
* @event update:modelValue
*/
'update:modelValue'
],
computed: {
classes() {
return {
...colorVariantClass(this),
[`-${this.size}`]: Boolean(this.size)
};
}
},
mounted() {
for (const child of this.$refs.trigger.children) {
on(child, 'keydown', this.onTriggerKeyDown);
}
on(this.$refs.popup, 'keydown', this.onItemKeyDown);
},
beforeUnmount() {
for (const child of this.$refs.trigger.children) {
off(child, 'keydown', this.onTriggerKeyDown);
}
off(this.$refs.popup, 'keydown', this.onItemKeyDown);
},
methods: {
onEscape() {
this.visible = false;
this.$emit('update:modelValue', false);
},
handleClickOutside() {
this.visible = false;
this.$emit('update:modelValue', false);
this.onClickOutside();
},
getFocusableItems() {
const focusableItems = [];
for (const child of this.$refs.body.children) {
if (isFocusable(child)) {
focusableItems.push(child);
}
}
return focusableItems;
},
onTriggerKeyDown(event) {
if (this.keydownTrigger.length === 0) {
return;
}
const focusableItems = this.getFocusableItems();
const activeIndex = focusableItems.findIndex((item) => item.active);
const initialIndex = activeIndex > -1 ? activeIndex : 0;
const focusTarget = focusableItems[initialIndex];
switch (true) {
case isKey('up', event) && this.keydownTrigger.includes('up'):
case isKey('down', event) && this.keydownTrigger.includes('down'):
this.show();
setTimeout(() => {
focusTarget.focus();
}, this.visible ? 0 : this.animationDuration);
event.preventDefault();
event.stopPropagation();
break;
case isKey('enter', event) && this.keydownTrigger.includes('enter'):
case isKey('space', event) && this.keydownTrigger.includes('space'):
this.onClick();
if (!this.visible) {
setTimeout(() => {
focusTarget.focus();
}, this.animationDuration);
}
event.preventDefault();
break;
case isKey('tab', event) && this.keydownTrigger.includes('tab'):
case isKey('esc', event) && this.keydownTrigger.includes('esc'):
this.hide();
break;
}
},
onItemKeyDown(event) {
if (this.keydownItem.length === 0) {
return;
}
switch (true) {
case isKey('up', event) && this.keydownItem.includes('up'):
case isKey('down', event) && this.keydownItem.includes('down'):
const focusableItems = this.getFocusableItems();
const currentIndex = focusableItems.findIndex((item) => item === event.target);
const maxIndex = focusableItems.length - 1;
let nextIndex;
if (isKey('up', event)) {
nextIndex = currentIndex > 0 ? currentIndex - 1 : 0;
}
else {
nextIndex = currentIndex < maxIndex ? currentIndex + 1 : maxIndex;
}
focusableItems[nextIndex].focus();
event.preventDefault();
event.stopPropagation();
break;
case isKey('enter', event) && this.keydownItem.includes('enter'):
case isKey('space', event) && this.keydownItem.includes('space'):
event.target.click();
if (this.hideOnItemClick) {
this.hide();
}
this.focusTrigger();
event.preventDefault();
break;
case isKey('tab', event) && this.keydownItem.includes('tab'):
case isKey('esc', event) && this.keydownItem.includes('esc'):
this.hide();
this.focusTrigger();
event.preventDefault();
break;
}
},
onItemClick() {
if (this.hideOnItemClick) {
this.hide();
}
}
}
});
//# sourceMappingURL=script.mjs.map