UNPKG

@dialpad/dialtone

Version:

Dialpad's Dialtone design system monorepo

392 lines (391 loc) 11.5 kB
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