UNPKG

@dialpad/dialtone

Version:

Dialpad's Dialtone design system monorepo

449 lines (448 loc) 14.2 kB
"use strict"; Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } }); const keyboard_list_navigation = require("../../common/mixins/keyboard_list_navigation.cjs"); const dropdown_constants = require("./dropdown_constants.cjs"); const common_utils = require("../../common/utils.cjs"); const common_constants = require("../../common/constants.cjs"); const sr_only_close_button$1 = require("../../common/mixins/sr_only_close_button.cjs"); const sr_only_close_button = require("../../common/sr_only_close_button.vue.cjs"); const vue = require("vue"); const _pluginVue_exportHelper = require("../../_virtual/_plugin-vue_export-helper.cjs"); const popover = require("../popover/popover.vue.cjs"); const list_item_constants = require("../list_item/list_item_constants.cjs"); const popover_constants = require("../popover/popover_constants.cjs"); const _sfc_main = { compatConfig: { MODE: 3 }, name: "DtDropdown", components: { DtPopover: popover.default, SrOnlyCloseButton: sr_only_close_button.default }, mixins: [ keyboard_list_navigation.default({ indexKey: "highlightIndex", idKey: "highlightId", listElementKey: "getListElement", listItemRole: "menuitem", afterHighlightMethod: "afterHighlight", beginningOfListMethod: "beginningOfListMethod", endOfListMethod: "endOfListMethod", activeItemKey: "activeItemEl", focusOnKeyboardNavigation: true }), sr_only_close_button$1.default ], inheritAttrs: false, 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: false }, /** * Vertical padding size around the list element. * @values none, small, large */ padding: { type: String, default: "small", validator: (padding) => { return Object.keys(dropdown_constants.DROPDOWN_PADDING_CLASSES).some((item) => item === padding); } }, /** * Determines modal state, dropdown has a modal overlay preventing interaction with elements * below it, but it is invisible. */ modal: { type: Boolean, default: true }, /** * 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 common_utils.getUniqueString(); } }, /** * 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: list_item_constants.LIST_ITEM_NAVIGATION_TYPES.ARROW_KEYS, validator: (t) => Object.values(list_item_constants.LIST_ITEM_NAVIGATION_TYPES).includes(t) }, /** * 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: () => { return ["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: (appendTo) => { return popover_constants.POPOVER_APPEND_TO_VALUES.includes(appendTo) || appendTo 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: true }, /** * 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: list_item_constants.LIST_ITEM_NAVIGATION_TYPES, DROPDOWN_PADDING_CLASSES: dropdown_constants.DROPDOWN_PADDING_CLASSES, EVENT_KEYNAMES: common_constants.EVENT_KEYNAMES, openedWithKeyboard: false, isOpen: null }; }, computed: { dropdownListeners() { return { opened: (isPopoverOpen) => { this.updateInitialHighlightIndex(isPopoverOpen); }, keydown: (event) => { const eventCode = event.code; switch (eventCode) { case common_constants.EVENT_KEYNAMES.up: case common_constants.EVENT_KEYNAMES.arrowup: this.onUpKeyPress(event); event.stopPropagation(); event.preventDefault(); break; case common_constants.EVENT_KEYNAMES.down: case common_constants.EVENT_KEYNAMES.arrowdown: this.onDownKeyPress(event); event.stopPropagation(); event.preventDefault(); break; case common_constants.EVENT_KEYNAMES.space: case common_constants.EVENT_KEYNAMES.spacebar: this.onSpaceKey(); break; case common_constants.EVENT_KEYNAMES.enter: this.onEnterKey(); break; case common_constants.EVENT_KEYNAMES.home: this.onHomeKeyPress(event); event.stopPropagation(); event.preventDefault(); break; case common_constants.EVENT_KEYNAMES.end: this.onEndKeyPress(event); event.stopPropagation(); event.preventDefault(); break; default: this.onKeyPress(event); break; } this.$emit("keydown", event); } }; }, 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", dropdown_constants.DROPDOWN_PADDING_CLASSES[this.padding], this.listClass, { "d-context-menu-list": this.openOnContext } ]; }, shouldOpenWithArrowKeys() { return !this.openOnContext; } }, methods: { onMouseHighlight(e) { const liElement = e.target.closest("li"); if (liElement && liElement.role && this.highlightId !== liElement.id) { this.setHighlightId(liElement.id); liElement.focus(); } }, getListElement() { return this.$refs.listWrapper; }, clearHighlightIndex() { this.setHighlightIndex(-1); }, afterHighlight() { if (this.visuallyHiddenClose && this.highlightIndex === this._itemsLength() - 1) { return; } this.$emit("highlight", this.highlightIndex); }, updateInitialHighlightIndex(isPopoverOpen) { this.isOpen = isPopoverOpen; if (isPopoverOpen) { if (this.openedWithKeyboard && this.isArrowKeyNav) { this.setHighlightIndex(0); } this.$emit("opened", true); } else { this.clearHighlightIndex(); this.openedWithKeyboard = false; this.$emit("opened", false); } }, onSpaceKey() { if (!this.open) { this.openedWithKeyboard = true; } }, onEnterKey() { if (!this.open) { this.openedWithKeyboard = true; } }, onUpKeyPress() { if (!this.isOpen) { this.openedWithKeyboard = true; return; } if (this.isArrowKeyNav) { return this.onUpKey(); } }, onDownKeyPress() { if (!this.isOpen) { this.openedWithKeyboard = true; return; } if (this.isArrowKeyNav) { return this.onDownKey(); } }, onHomeKeyPress() { if (!this.isOpen || !this.isArrowKeyNav) { return; } return this.onHomeKey(); }, onEndKeyPress() { if (!this.isOpen || !this.isArrowKeyNav) { return; } return this.onEndKey(); }, onKeyPress(e) { if (!this.isOpen || !this.isArrowKeyNav || !this.isValidLetter(e.key)) { return; } e.stopPropagation(); e.preventDefault(); return this.onNavigationKey(e.key); } } }; const _hoisted_1 = ["id"]; function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) { const _component_sr_only_close_button = vue.resolveComponent("sr-only-close-button"); const _component_dt_popover = vue.resolveComponent("dt-popover"); return vue.openBlock(), vue.createBlock(_component_dt_popover, vue.mergeProps({ ref: "popover", "content-width": $props.contentWidth, open: $props.open, placement: $props.placement, "initial-focus-element": $data.openedWithKeyboard ? "first" : "dialog", "fallback-placements": $props.fallbackPlacements, padding: "none", role: "menu", "append-to": $props.appendTo, modal: $props.modal, "max-height": $props.maxHeight, "max-width": $props.maxWidth, "open-with-arrow-keys": $options.shouldOpenWithArrowKeys, "open-on-context": $props.openOnContext }, _ctx.$attrs, { tether: $props.tether, transition: $props.transition }, vue.toHandlers($options.dropdownListeners)), { anchor: vue.withCtx(({ attrs }) => [ vue.renderSlot(_ctx.$slots, "anchor", vue.mergeProps({ ref: "anchor" }, attrs)) ]), content: vue.withCtx(({ close }) => [ vue.createElementVNode("ul", { id: $props.listId, ref: "listWrapper", class: vue.normalizeClass($options.listClasses), "data-qa": "dt-dropdown-list-wrapper", onMouseleave: _cache[0] || (_cache[0] = (...args) => $options.clearHighlightIndex && $options.clearHighlightIndex(...args)), onMousemoveCapture: _cache[1] || (_cache[1] = (...args) => $options.onMouseHighlight && $options.onMouseHighlight(...args)) }, [ vue.renderSlot(_ctx.$slots, "list", { close }), _ctx.showVisuallyHiddenClose ? (vue.openBlock(), vue.createBlock(_component_sr_only_close_button, { key: 0, "visually-hidden-close-label": _ctx.visuallyHiddenCloseLabel, tabindex: $options.isArrowKeyNav ? -1 : 0, onClose: close }, null, 8, ["visually-hidden-close-label", "tabindex", "onClose"])) : vue.createCommentVNode("", true) ], 42, _hoisted_1) ]), footerContent: vue.withCtx(({ close }) => [ vue.renderSlot(_ctx.$slots, "footer", { close }) ]), _: 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 DtDropdown = /* @__PURE__ */ _pluginVue_exportHelper.default(_sfc_main, [["render", _sfc_render]]); exports.default = DtDropdown; //# sourceMappingURL=dropdown.vue.cjs.map