@dialpad/dialtone
Version:
Dialpad's Dialtone design system monorepo
359 lines (358 loc) • 11.7 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 common_utils = require("../../common/utils.cjs");
const combobox_loadingList = require("./combobox_loading-list.vue.cjs");
const combobox_emptyList = require("./combobox_empty-list.vue.cjs");
const combobox_constants = require("./combobox_constants.cjs");
const vue = require("vue");
const _pluginVue_exportHelper = require("../../_virtual/_plugin-vue_export-helper.cjs");
const _sfc_main = {
compatConfig: { MODE: 3 },
name: "DtCombobox",
components: {
ComboboxLoadingList: combobox_loadingList.default,
ComboboxEmptyList: combobox_emptyList.default
},
mixins: [
keyboard_list_navigation.default({
indexKey: "highlightIndex",
idKey: "highlightId",
listElementKey: "getListElement",
afterHighlightMethod: "afterHighlight",
beginningOfListMethod: "beginningOfListMethod",
endOfListMethod: "endOfListMethod",
activeItemKey: "activeItemEl"
})
],
props: {
/**
* String to use for the input label.
*/
label: {
type: String,
required: true
},
/**
* Determines visibility of input label.
* @values true, false
*/
labelVisible: {
type: Boolean,
default: true
},
/**
* Size of the input, one of `xs`, `sm`, `md`, `lg`, `xl`
* @values null, xs, sm, md, lg, xl
*/
size: {
type: String,
default: null,
validator: (t) => Object.values(combobox_constants.LABEL_SIZES).includes(t)
},
/**
* Description for the input
*/
description: {
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();
}
},
/**
* 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
},
/**
* Determines when to show the list element and also controls the aria-expanded attribute.
* @values true, false
*/
showList: {
type: Boolean,
default: false
},
/**
* If the list is rendered outside the component, like when using popover as the list wrapper.
* @values true, false
*/
listRenderedOutside: {
type: Boolean,
default: false
},
/**
* Determines when to show the skeletons and also controls aria-busy attribute.
* @values true, false
*/
loading: {
type: Boolean,
default: false
},
/**
* Sets the list to an empty state, and displays the message from prop `emptyStateMessage`.
* @values true, false
*/
emptyList: {
type: Boolean,
default: false
},
/**
* Message to show when the list is empty
*/
emptyStateMessage: {
type: String,
default: ""
},
/**
* Additional class name for the empty list element.
* Can accept all of String, Object, and Array, i.e. has the
* same api as Vue's built-in handling of the class attribute.
*/
emptyStateClass: {
type: [String, Object, Array],
default: ""
},
/**
* Programmatically click on the active list item element when a selection
* comes from keyboard navigation, i.e. pressing the "Enter" key.
* @values true, false
*/
clickOnSelect: {
type: Boolean,
default: false
}
},
emits: [
/**
* Event fired when item selected
*
* @event select
* @type {Number}
*/
"select",
/**
* Event fired when pressing escape
*
* @event escape
*/
"escape",
/**
* Event fired when the highlight changes
*
* @event highlight
* @type {Number}
*/
"highlight",
/**
* Event fired when list is shown or hidden
*
* @event opened
* @type {Boolean}
*/
"opened"
],
data() {
return {
// If the list is rendered at the root, rather than as a child
// of this component, this is the ref to that dom element. Set
// by the onOpen method.
outsideRenderedListRef: null,
hasSlotContent: common_utils.hasSlotContent
};
},
computed: {
inputProps() {
return {
label: this.label,
labelVisible: this.labelVisible,
size: this.size,
description: this.description,
role: "combobox",
"aria-label": this.label,
"aria-expanded": this.showList.toString(),
"aria-owns": this.listId,
"aria-haspopup": "listbox",
"aria-activedescendant": this.activeItemId,
"aria-controls": this.listId
};
},
listProps() {
return {
role: "listbox",
id: this.listId,
// The list has to be positioned relatively so that the auto-scroll can
// calculate the correct offset for the list items.
class: "d-ps-relative",
"aria-label": this.label
};
},
beginningOfListMethod() {
return this.onBeginningOfList || this.jumpToEnd;
},
endOfListMethod() {
return this.onEndOfList || this.jumpToBeginning;
},
activeItemId() {
if (!this.showList || this.highlightIndex < 0 || this.loading) {
return;
}
return this.highlightId;
},
activeItemEl() {
if (!this.highlightId) return "";
return this.getListElement().querySelector("#" + this.highlightId);
}
},
watch: {
showList(showList) {
if (!this.listRenderedOutside) {
this.setInitialHighlightIndex();
this.$emit("opened", showList);
}
if (!showList && this.outsideRenderedListRef) {
this.outsideRenderedListRef.removeEventListener("mousemove", this.onMouseHighlight);
this.outsideRenderedListRef = null;
}
},
loading(loading) {
this.$nextTick(() => {
this.setInitialHighlightIndex();
});
},
$props: {
deep: true,
immediate: true,
handler() {
this.validateEmptyListProps();
}
}
},
created() {
this.validateEmptyListProps();
},
methods: {
onMouseHighlight(e) {
if (this.loading) return;
const liElement = e.target.closest("li");
if (liElement && this.highlightId !== liElement.id) {
this.setHighlightId(liElement.id);
}
},
getListElement() {
var _a;
return this.outsideRenderedListRef ?? ((_a = this.$refs.listWrapper) == null ? void 0 : _a.querySelector(`#${this.listId}`));
},
clearHighlightIndex() {
if (this.showList) {
this.setHighlightIndex(-1);
}
},
afterHighlight() {
if (this.loading) return;
this.$emit("highlight", this.highlightIndex);
},
onEnterKey() {
var _a;
if (this.loading || this.emptyList) return;
if (this.highlightIndex >= 0) {
this.$emit("select", this.highlightIndex);
if (this.clickOnSelect) {
(_a = this.activeItemEl) == null ? void 0 : _a.click();
}
}
},
onEscapeKey() {
this.$emit("escape");
},
onOpen(open, contentRef) {
var _a;
this.outsideRenderedListRef = contentRef;
(_a = this.outsideRenderedListRef) == null ? void 0 : _a.addEventListener("mousemove", this.onMouseHighlight);
this.$emit("opened", open);
if (open) {
this.setInitialHighlightIndex();
}
},
onKeyValidation(e, eventHandler) {
if (!this.showList || !this.getListElement()) return;
this[eventHandler](e);
},
setInitialHighlightIndex() {
if (!this.showList) return;
this.$nextTick(() => {
this.setHighlightIndex(this.loading ? -1 : 0);
});
},
validateEmptyListProps() {
if (this.$slots.emptyListItem) {
return;
}
if (this.emptyList && !this.emptyStateMessage) {
console.error(`Invalid props: you must pass both props emptyList and emptyStateMessage to show the
empty message.`);
}
}
}
};
const _hoisted_1 = { "data-qa": "dt-combobox-input-wrapper" };
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
const _component_combobox_loading_list = vue.resolveComponent("combobox-loading-list");
const _component_combobox_empty_list = vue.resolveComponent("combobox-empty-list");
return vue.openBlock(), vue.createElementBlock("div", {
onKeydown: [
_cache[3] || (_cache[3] = vue.withKeys(vue.withModifiers(($event) => $options.onKeyValidation($event, "onEscapeKey"), ["stop"]), ["esc"])),
_cache[4] || (_cache[4] = vue.withKeys(vue.withModifiers(($event) => $options.onKeyValidation($event, "onEnterKey"), ["exact"]), ["enter"])),
_cache[5] || (_cache[5] = vue.withKeys(vue.withModifiers(($event) => $options.onKeyValidation($event, "onUpKey"), ["stop", "prevent"]), ["up"])),
_cache[6] || (_cache[6] = vue.withKeys(vue.withModifiers(($event) => $options.onKeyValidation($event, "onDownKey"), ["stop", "prevent"]), ["down"])),
_cache[7] || (_cache[7] = vue.withKeys(vue.withModifiers(($event) => $options.onKeyValidation($event, "onHomeKey"), ["stop", "prevent"]), ["home"])),
_cache[8] || (_cache[8] = vue.withKeys(vue.withModifiers(($event) => $options.onKeyValidation($event, "onEndKey"), ["stop", "prevent"]), ["end"]))
]
}, [
vue.createElementVNode("div", _hoisted_1, [
vue.renderSlot(_ctx.$slots, "input", { inputProps: $options.inputProps })
]),
$props.showList ? (vue.openBlock(), vue.createElementBlock("div", {
key: 0,
ref: "listWrapper",
"data-qa": "dt-combobox-list-wrapper",
onMouseleave: _cache[0] || (_cache[0] = (...args) => $options.clearHighlightIndex && $options.clearHighlightIndex(...args)),
onFocusout: _cache[1] || (_cache[1] = (...args) => $options.clearHighlightIndex && $options.clearHighlightIndex(...args)),
onMousemoveCapture: _cache[2] || (_cache[2] = (...args) => $options.onMouseHighlight && $options.onMouseHighlight(...args))
}, [
$props.loading && !$props.listRenderedOutside ? (vue.openBlock(), vue.createBlock(_component_combobox_loading_list, vue.normalizeProps(vue.mergeProps({ key: 0 }, $options.listProps)), null, 16)) : $props.emptyList && ($props.emptyStateMessage || $data.hasSlotContent(_ctx.$slots.emptyListItem)) && !$props.listRenderedOutside ? (vue.openBlock(), vue.createBlock(_component_combobox_empty_list, vue.mergeProps({ key: 1 }, $options.listProps, {
message: $props.emptyStateMessage,
"item-class": $props.emptyStateClass
}), {
default: vue.withCtx(() => [
vue.renderSlot(_ctx.$slots, "emptyListItem")
]),
_: 3
}, 16, ["message", "item-class"])) : vue.renderSlot(_ctx.$slots, "list", {
key: 2,
listProps: $options.listProps,
opened: $options.onOpen,
clearHighlightIndex: $options.clearHighlightIndex
})
], 544)) : vue.createCommentVNode("", true)
], 32);
}
const DtCombobox = /* @__PURE__ */ _pluginVue_exportHelper.default(_sfc_main, [["render", _sfc_render]]);
exports.default = DtCombobox;
//# sourceMappingURL=combobox.vue.cjs.map