@dialpad/dialtone-vue
Version:
Vue component library for Dialpad's design system Dialtone
352 lines (351 loc) • 10.7 kB
JavaScript
import a from "../../common/mixins/keyboard-list-navigation.js";
import { DROPDOWN_PADDING_CLASSES as o } from "./dropdown-constants.js";
import { getUniqueString as l } from "../../common/utils/index.js";
import { EVENT_KEYNAMES as i } from "../../common/constants/index.js";
import { n as h } from "../../_plugin-vue2_normalizer-DSLOjnn3.js";
import { LIST_ITEM_NAVIGATION_TYPES as r } from "../list-item/list-item-constants.js";
import d from "../popover/popover.js";
import { POPOVER_APPEND_TO_VALUES as p } from "../popover/popover-constants.js";
const u = {
name: "DtDropdown",
components: {
DtPopover: d
},
mixins: [
a({
indexKey: "highlightIndex",
idKey: "highlightId",
listElementKey: "getListElement",
listItemRole: "menuitem",
afterHighlightMethod: "afterHighlight",
beginningOfListMethod: "beginningOfListMethod",
endOfListMethod: "endOfListMethod",
activeItemKey: "activeItemEl",
focusOnKeyboardNavigation: !0
})
],
props: {
/**
* Controls whether the dropdown is shown. Leaving this null will have the dropdown trigger on click by default.
* If you set this value, the default trigger behavior will be disabled and you can control it as you need.
* Supports .sync modifier
*/
open: {
type: Boolean,
default: null
},
/**
* Opens the dropdown on right click (context menu). If you set this value to `true`,
* the default trigger behavior will be disabled.
*/
openOnContext: {
type: Boolean,
default: !1
},
/**
* Vertical padding size around the list element.
* @values none, small, large
*/
padding: {
type: String,
default: "small",
validator: (e) => Object.keys(o).some((t) => t === e)
},
/**
* Determines modal state, dropdown has a modal overlay preventing interaction with elements
* below it, but it is invisible.
*/
modal: {
type: Boolean,
default: !0
},
/**
* Width configuration for the popover content. When its value is 'anchor',
* the popover content will have the same width as the anchor.
* @values null, anchor
*/
contentWidth: {
type: String,
default: null
},
/**
* Determines maximum height for the popover before overflow.
* Possible units rem|px|em
*/
maxHeight: {
type: String,
default: ""
},
/**
* Determines maximum width for the popover before overflow.
* Possible units rem|px|%|em
*/
maxWidth: {
type: String,
default: ""
},
/**
* Sets an ID on the list element of the component. Used by several aria attributes
* as well as when deriving the IDs for each item.
*/
listId: {
type: String,
default() {
return l();
}
},
/**
* The type of navigation that this component should support.
* - "arrow-keys" for items that are navigated with UP/DOWN keys.
* - "tab" for items that are navigated using the TAB key.
* - "none" for static items that are not interactive.
* @values arrow-keys, tab, none
*/
navigationType: {
type: String,
default: r.ARROW_KEYS,
validator: (e) => Object.values(r).includes(e)
},
/**
* If the dropdown does not fit in the direction described by "placement",
* it will attempt to change it's direction to the "fallbackPlacements".
*
* @values top, top-start, top-end,
* right, right-start, right-end,
* left, left-start, left-end,
* bottom, bottom-start, bottom-end,
* auto, auto-start, auto-end
* */
fallbackPlacements: {
type: Array,
default: () => ["auto"]
},
/**
* The direction the dropdown displays relative to the anchor.
*/
placement: {
type: String,
default: "bottom"
},
/**
* A method that will be called when the selection goes past the beginning of the list.
*/
onBeginningOfList: {
type: Function,
default: null
},
/**
* A method that will be called when the selection goes past the end of the list.
*/
onEndOfList: {
type: Function,
default: null
},
/**
* Additional class for the wrapper list element.
*/
listClass: {
type: [String, Array, Object],
default: ""
},
/**
* Sets the element to which the popover is going to append to.
* 'body' will append to the nearest body (supports shadow DOM).
* @values 'body', 'parent', HTMLElement,
*/
appendTo: {
type: [HTMLElement, String],
default: "body",
validator: (e) => p.includes(e) || e instanceof HTMLElement
},
/**
* If set to false the dialog will display over top of the anchor when there is insufficient space.
* If set to true it will never move from its position relative to the anchor and will clip instead.
* <a
* class="d-link"
* href="https://popper.js.org/docs/v2/modifiers/prevent-overflow/#tether"
* target="_blank"
* >
* Popper.js docs
* </a>
* @values true, false
*/
tether: {
type: Boolean,
default: !0
},
/**
* Named transition when the content display is toggled.
* @see DtLazyShow
*/
transition: {
type: String,
default: "fade"
}
},
emits: [
/**
* Event fired when the highlight changes
*
* @event highlight
* @type {Number}
*/
"highlight",
/**
* Event fired when dropdown is shown or hidden
*
* @event opened
* @type {Boolean | Array}
*/
"opened",
/**
* Event fired to sync the open prop with the parent component
* @event update:open
*/
"update:open"
],
data() {
return {
LIST_ITEM_NAVIGATION_TYPES: r,
DROPDOWN_PADDING_CLASSES: o,
EVENT_KEYNAMES: i,
openedWithKeyboard: !1,
isOpen: null
};
},
computed: {
dropdownListeners() {
return {
...this.$listeners,
opened: (e) => {
this.updateInitialHighlightIndex(e);
},
keydown: (e) => {
switch (e.code) {
case i.up:
case i.arrowup:
this.onUpKeyPress(e), e.stopPropagation(), e.preventDefault();
break;
case i.down:
case i.arrowdown:
this.onDownKeyPress(e), e.stopPropagation(), e.preventDefault();
break;
case i.space:
case i.spacebar:
this.onSpaceKey();
break;
case i.enter:
this.onEnterKey();
break;
case i.home:
this.onHomeKeyPress(e), e.stopPropagation(), e.preventDefault();
break;
case i.end:
this.onEndKeyPress(e), e.stopPropagation(), e.preventDefault();
break;
default:
this.onKeyPress(e);
break;
}
this.$emit("keydown", e);
}
};
},
beginningOfListMethod() {
return this.onBeginningOfList || this.jumpToEnd;
},
endOfListMethod() {
return this.onEndOfList || this.jumpToBeginning;
},
activeItemEl() {
return this.getListElement().querySelector("#" + this.highlightId);
},
isArrowKeyNav() {
return this.navigationType === this.LIST_ITEM_NAVIGATION_TYPES.ARROW_KEYS;
},
listClasses() {
return [
"d-dropdown-list",
o[this.padding],
this.listClass,
{ "d-context-menu-list": this.openOnContext }
];
},
shouldOpenWithArrowKeys() {
return !this.openOnContext;
}
},
methods: {
onMouseHighlight(e) {
const t = e.target.closest("li");
t && t.role && this.highlightId !== t.id && (this.setHighlightId(t.id), t.focus());
},
getListElement() {
return this.$refs.listWrapper;
},
clearHighlightIndex() {
this.setHighlightIndex(-1);
},
afterHighlight() {
this.highlightIndex !== this._itemsLength() - 1 && this.$emit("highlight", this.highlightIndex);
},
updateInitialHighlightIndex(e) {
this.isOpen = e, e ? (this.openedWithKeyboard && this.isArrowKeyNav && this.setHighlightIndex(0), this.$emit("opened", !0)) : (this.clearHighlightIndex(), this.openedWithKeyboard = !1, this.$emit("opened", !1));
},
onSpaceKey() {
this.open || (this.openedWithKeyboard = !0);
},
onEnterKey() {
this.open || (this.openedWithKeyboard = !0);
},
onUpKeyPress() {
if (!this.isOpen) {
this.openedWithKeyboard = !0;
return;
}
if (this.isArrowKeyNav)
return this.onUpKey();
},
onDownKeyPress() {
if (!this.isOpen) {
this.openedWithKeyboard = !0;
return;
}
if (this.isArrowKeyNav)
return this.onDownKey();
},
onHomeKeyPress() {
if (!(!this.isOpen || !this.isArrowKeyNav))
return this.onHomeKey();
},
onEndKeyPress() {
if (!(!this.isOpen || !this.isArrowKeyNav))
return this.onEndKey();
},
onKeyPress(e) {
if (!(!this.isOpen || !this.isArrowKeyNav || !this.isValidLetter(e.key)))
return e.stopPropagation(), e.preventDefault(), this.onNavigationKey(e.key);
}
}
};
var g = function() {
var t = this, s = t._self._c;
return s("dt-popover", t._g({ ref: "popover", attrs: { "content-width": t.contentWidth, open: t.open, placement: t.placement, "initial-focus-element": t.openedWithKeyboard ? "first" : "dialog", "fallback-placements": t.fallbackPlacements, padding: "none", role: "menu", "append-to": t.appendTo, modal: t.modal, "max-height": t.maxHeight, "max-width": t.maxWidth, "open-with-arrow-keys": t.shouldOpenWithArrowKeys, "open-on-context": t.openOnContext, tether: t.tether, transition: t.transition }, scopedSlots: t._u([{ key: "anchor", fn: function({ attrs: n }) {
return [t._t("anchor", null, null, n)];
} }, { key: "content", fn: function({ close: n }) {
return [s("ul", { ref: "listWrapper", class: t.listClasses, attrs: { id: t.listId, "data-qa": "dt-dropdown-list-wrapper" }, on: { mouseleave: t.clearHighlightIndex, "!mousemove": function(y) {
return t.onMouseHighlight.apply(null, arguments);
} } }, [t._t("list", null, { close: n })], 2)];
} }, { key: "footerContent", fn: function({ close: n }) {
return [t._t("footer", null, { close: n })];
} }], null, !0) }, t.dropdownListeners));
}, f = [], m = /* @__PURE__ */ h(
u,
g,
f
);
const x = m.exports;
export {
x as default
};
//# sourceMappingURL=dropdown.js.map