@dialpad/dialtone
Version:
Dialpad's Dialtone design system monorepo
392 lines (391 loc) • 11.5 kB
JavaScript
import f from "../../common/mixins/keyboard-list-navigation.js";
import { DROPDOWN_PADDING_CLASSES as a } from "./dropdown-constants.js";
import { getUniqueString as m } from "../../common/utils/index.js";
import { EVENT_KEYNAMES as n } from "../../common/constants/index.js";
import { resolveComponent as y, createBlock as c, openBlock as K, mergeProps as p, toHandlers as w, withCtx as h, renderSlot as l, createElementVNode as O, normalizeClass as E } from "vue";
import { _ as I } from "../../_plugin-vue_export-helper-CHgC5LLL.js";
import { LIST_ITEM_NAVIGATION_TYPES as d } from "../list-item/list-item-constants.js";
import b from "../popover/popover.js";
import { POPOVER_APPEND_TO_VALUES as x } from "../popover/popover-constants.js";
const P = {
compatConfig: { MODE: 3 },
name: "DtDropdown",
components: {
DtPopover: b
},
mixins: [
f({
indexKey: "highlightIndex",
idKey: "highlightId",
listElementKey: "getListElement",
listItemRole: "menuitem",
afterHighlightMethod: "afterHighlight",
beginningOfListMethod: "beginningOfListMethod",
endOfListMethod: "endOfListMethod",
activeItemKey: "activeItemEl",
focusOnKeyboardNavigation: !0
})
],
inheritAttrs: !1,
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 v-model
*/
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(a).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 m();
}
},
/**
* 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: d.ARROW_KEYS,
validator: (e) => Object.values(d).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) => x.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: [
/**
* Native keydown event
*
* @event keydown
* @type {KeyboardEvent}
*/
"keydown",
/**
* Event fired when the highlight changes
*
* @event highlight
* @type {Number}
*/
"highlight",
/**
* Event fired to sync the open prop with the parent component
* @event update:open
*/
"update:open",
/**
* 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: d,
DROPDOWN_PADDING_CLASSES: a,
EVENT_KEYNAMES: n,
openedWithKeyboard: !1,
isOpen: null
};
},
computed: {
dropdownListeners() {
return {
opened: (e) => {
this.updateInitialHighlightIndex(e);
},
keydown: (e) => {
switch (e.code) {
case n.up:
case n.arrowup:
this.onUpKeyPress(e), e.stopPropagation(), e.preventDefault();
break;
case n.down:
case n.arrowdown:
this.onDownKeyPress(e), e.stopPropagation(), e.preventDefault();
break;
case n.space:
case n.spacebar:
this.onSpaceKey();
break;
case n.enter:
this.onEnterKey();
break;
case n.home:
this.onHomeKeyPress(e), e.stopPropagation(), e.preventDefault();
break;
case n.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",
a[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);
}
}
}, S = ["id"];
function A(e, t, i, H, u, o) {
const g = y("dt-popover");
return K(), c(g, p({
ref: "popover",
"content-width": i.contentWidth,
open: i.open,
placement: i.placement,
"initial-focus-element": u.openedWithKeyboard ? "first" : "dialog",
"fallback-placements": i.fallbackPlacements,
padding: "none",
role: "menu",
"append-to": i.appendTo,
modal: i.modal,
"max-height": i.maxHeight,
"max-width": i.maxWidth,
"open-with-arrow-keys": o.shouldOpenWithArrowKeys,
"open-on-context": i.openOnContext
}, e.$attrs, {
tether: i.tether,
transition: i.transition
}, w(o.dropdownListeners)), {
anchor: h(({ attrs: r }) => [
l(e.$slots, "anchor", p({ ref: "anchor" }, r))
]),
content: h(({ close: r }) => [
O("ul", {
id: i.listId,
ref: "listWrapper",
class: E(o.listClasses),
"data-qa": "dt-dropdown-list-wrapper",
onMouseleave: t[0] || (t[0] = (...s) => o.clearHighlightIndex && o.clearHighlightIndex(...s)),
onMousemoveCapture: t[1] || (t[1] = (...s) => o.onMouseHighlight && o.onMouseHighlight(...s))
}, [
l(e.$slots, "list", { close: r })
], 42, S)
]),
footerContent: h(({ close: r }) => [
l(e.$slots, "footer", { close: r })
]),
_: 3
}, 16, ["content-width", "open", "placement", "initial-focus-element", "fallback-placements", "append-to", "modal", "max-height", "max-width", "open-with-arrow-keys", "open-on-context", "tether", "transition"]);
}
const T = /* @__PURE__ */ I(P, [["render", A]]);
export {
T as default
};
//# sourceMappingURL=dropdown.js.map