@dialpad/dialtone
Version:
Dialpad's Dialtone design system monorepo
449 lines (448 loc) • 14.2 kB
JavaScript
"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