@dialpad/dialtone
Version:
Dialpad's Dialtone design system monorepo
444 lines (443 loc) • 13.8 kB
JavaScript
import ComboboxLoadingList from "../../../components/combobox/combobox_loading-list.vue.js";
import ComboboxEmptyList from "../../../components/combobox/combobox_empty-list.vue.js";
import { getUniqueString, hasSlotContent } from "../../../common/utils.js";
import SrOnlyCloseButtonMixin from "../../../common/mixins/sr_only_close_button.js";
import { resolveComponent, openBlock, createBlock, mergeProps, withCtx, createElementVNode, withKeys, renderSlot, createVNode, createSlots, normalizeClass, normalizeProps } from "vue";
import _export_sfc from "../../../_virtual/_plugin-vue_export-helper.js";
import DtCombobox from "../../../components/combobox/combobox.vue.js";
import DtPopover from "../../../components/popover/popover.vue.js";
import { LABEL_SIZES } from "../../../components/combobox/combobox_constants.js";
import { DROPDOWN_PADDING_CLASSES } from "../../../components/dropdown/dropdown_constants.js";
import { POPOVER_CONTENT_WIDTHS, POPOVER_APPEND_TO_VALUES } from "../../../components/popover/popover_constants.js";
const _sfc_main = {
compatConfig: { MODE: 3 },
name: "DtRecipeComboboxWithPopover",
components: {
DtCombobox,
DtPopover,
ComboboxLoadingList,
ComboboxEmptyList
},
mixins: [SrOnlyCloseButtonMixin],
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(LABEL_SIZES).includes(t)
},
/**
* Description for the input
*/
description: {
type: String,
default: ""
},
/**
* Determines when to show the list element and also controls the aria-expanded attribute.
* Leaving this null will have the combobox trigger on input focus by default.
* If you set this value, the default trigger behavior will be disabled and you can
* control it as you need.
*/
showList: {
type: Boolean,
default: null
},
/**
* 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 getUniqueString();
}
},
/**
* Additional class for the wrapper list element.
*/
listClass: {
type: [String, Array, Object],
default: ""
},
/**
* 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 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: ""
},
/**
* Vertical padding size around the list element.
*/
padding: {
type: String,
default: "small",
validator: (padding) => {
return Object.keys(DROPDOWN_PADDING_CLASSES).some((item) => item === padding);
}
},
/**
* Width configuration for the popover content. When its value is 'anchor',
* the popover content will have the same width as the anchor.
*/
contentWidth: {
type: String,
default: null,
validator: (contentWidth) => POPOVER_CONTENT_WIDTHS.includes(contentWidth)
},
/**
* If the list should be shown by pressing up or down arrow key on the input element.
* This can be set when not passing showList prop.
*/
openWithArrowKeys: {
type: Boolean,
default: false
},
/**
* Displaces the popover content box from its anchor element
* by the specified number of pixels.
*/
popoverOffset: {
type: Array,
default: () => [0, 4]
},
/**
* If the popover sticks to the input.
*/
popoverSticky: {
type: [Boolean, String],
default: false
},
/**
* Displays the list when the combobox is focused, before the user has typed anything.
* When this is enabled the list will not close after selection.
*/
hasSuggestionList: {
type: Boolean,
default: true
},
/**
* Determines when to show the skeletons and also controls aria-busy attribute.
*/
loading: {
type: Boolean,
default: false
},
/**
* Sets the list to an empty state, and displays the message from prop `emptyStateMessage`.
*/
emptyList: {
type: Boolean,
default: false
},
/**
* Message to show when the list is empty
*/
emptyStateMessage: {
type: String,
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_APPEND_TO_VALUES.includes(appendTo) || appendTo instanceof HTMLElement;
}
},
/**
* Named transition when the content display is toggled.
* @see DtLazyShow
*/
transition: {
type: String,
default: "fade"
}
},
emits: [
/**
* Event fired when item selected
*
* @event select
* @type {Number}
*/
"select",
/**
* Event fired when 'escape' key is pressed
*
* @event escape
*/
"escape",
/**
* Event fired when an item is highlighted
*
* @event highlight
* @type {Number}
*/
"highlight",
/**
* Emitted when items are shown or hidden
*
* @event opened
* @type {Boolean | Array}
*/
"opened"
],
data() {
return {
DROPDOWN_PADDING_CLASSES,
isListShown: false,
isInputFocused: false,
isListFocused: false,
externalAnchor: getUniqueString(),
hasSlotContent
};
},
computed: {
comboboxListeners() {
return {
...this.$attrs,
onSelect: this.onSelect,
onEscape: this.onEscape,
onHighlight: this.onHighlight
};
}
},
watch: {
showList: {
handler: function(show) {
if (show !== null) {
this.isListShown = show;
}
},
immediate: true
},
isListShown(val) {
if (val) {
window.addEventListener("mousedown", this.onFocusOut);
} else {
window.removeEventListener("mousedown", this.onFocusOut);
}
this.onOpened(val);
}
},
methods: {
handleDisplayList(value) {
if (!this.hasSuggestionList && value) {
this.showComboboxList();
}
if (!this.hasSuggestionList && !value) {
this.closeComboboxList();
}
},
showComboboxList() {
if (this.showList != null) {
return;
}
this.isListShown = true;
},
closeComboboxList() {
if (this.showList != null) {
return;
}
this.isListShown = false;
},
onSelect(highlightIndex) {
if (this.loading) return;
this.$emit("select", highlightIndex);
if (!this.hasSuggestionList) {
this.closeComboboxList();
}
},
onEscape() {
this.$emit("escape");
this.closeComboboxList();
},
onHighlight(highlightIndex) {
if (this.loading) return;
this.$emit("highlight", highlightIndex);
},
onOpened(opened) {
this.$emit("opened", opened);
},
onFocusIn(e) {
var _a;
if (this.hasSuggestionList && e && ((_a = this.$refs.input) == null ? void 0 : _a.querySelector("input")) === e.target) {
this.showComboboxList();
}
},
onFocusOut(e) {
var _a, _b, _c;
const popoverEl = (_b = (_a = this.$refs.popover) == null ? void 0 : _a.tip) == null ? void 0 : _b.popper;
const comboboxEl = this.$refs.input;
if ((_c = e.composedPath()) == null ? void 0 : _c.some((el) => [popoverEl, comboboxEl].includes(el))) return;
this.closeComboboxList();
},
openOnArrowKeyPress() {
if (this.showList !== null || this.isListShown || !this.openWithArrowKeys) {
return;
}
this.showComboboxList();
}
}
};
const _hoisted_1 = ["id"];
const _hoisted_2 = { ref: "header" };
const _hoisted_3 = ["onMouseleave", "onFocusout"];
const _hoisted_4 = { ref: "footer" };
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
const _component_combobox_loading_list = resolveComponent("combobox-loading-list");
const _component_combobox_empty_list = resolveComponent("combobox-empty-list");
const _component_dt_popover = resolveComponent("dt-popover");
const _component_dt_combobox = resolveComponent("dt-combobox");
return openBlock(), createBlock(_component_dt_combobox, mergeProps({
ref: "combobox",
loading: $props.loading,
label: $props.label,
"label-visible": $props.labelVisible,
size: $props.size,
description: $props.description,
"empty-list": $props.emptyList,
"empty-state-message": $props.emptyStateMessage,
"show-list": $data.isListShown,
"on-beginning-of-list": $props.onBeginningOfList,
"on-end-of-list": $props.onEndOfList,
"list-rendered-outside": true,
"list-id": $props.listId,
"data-qa": "dt-combobox"
}, $options.comboboxListeners), {
input: withCtx(({ inputProps }) => [
createElementVNode("div", {
id: $data.externalAnchor,
ref: "input",
onFocusin: _cache[0] || (_cache[0] = (...args) => $options.onFocusIn && $options.onFocusIn(...args)),
onKeydown: [
_cache[1] || (_cache[1] = withKeys(($event) => $options.openOnArrowKeyPress($event), ["up"])),
_cache[2] || (_cache[2] = withKeys(($event) => $options.openOnArrowKeyPress($event), ["down"]))
]
}, [
renderSlot(_ctx.$slots, "input", {
inputProps,
onInput: $options.handleDisplayList
})
], 40, _hoisted_1)
]),
list: withCtx(({ opened, listProps, clearHighlightIndex }) => [
createVNode(_component_dt_popover, {
ref: "popover",
open: $data.isListShown,
"onUpdate:open": _cache[3] || (_cache[3] = ($event) => $data.isListShown = $event),
"hide-on-click": false,
"max-height": $props.maxHeight,
"max-width": $props.maxWidth,
offset: $props.popoverOffset,
sticky: $props.popoverSticky,
placement: "bottom-start",
"initial-focus-element": "none",
padding: "none",
role: "listbox",
"external-anchor": $data.externalAnchor,
"content-width": $props.contentWidth,
"content-appear": true,
"content-tabindex": null,
modal: false,
"auto-focus": false,
"append-to": $props.appendTo,
transition: $props.transition,
"visually-hidden-close-label": _ctx.visuallyHiddenCloseLabel,
"visually-hidden-close": _ctx.visuallyHiddenClose,
onOpened: opened
}, createSlots({
content: withCtx(() => [
createElementVNode("div", {
ref: "listWrapper",
class: normalizeClass([
"d-recipe-combobox-with-popover__list",
$data.DROPDOWN_PADDING_CLASSES[$props.padding],
$props.listClass
]),
onMouseleave: clearHighlightIndex,
onFocusout: clearHighlightIndex
}, [
$props.loading ? (openBlock(), createBlock(_component_combobox_loading_list, normalizeProps(mergeProps({ key: 0 }, listProps)), null, 16)) : $props.emptyList && $props.emptyStateMessage ? (openBlock(), createBlock(_component_combobox_empty_list, mergeProps({ key: 1 }, listProps, { message: $props.emptyStateMessage }), null, 16, ["message"])) : renderSlot(_ctx.$slots, "list", {
key: 2,
listProps
})
], 42, _hoisted_3)
]),
_: 2
}, [
$data.hasSlotContent(_ctx.$slots.header) ? {
name: "headerContent",
fn: withCtx(() => [
createElementVNode("div", _hoisted_2, [
renderSlot(_ctx.$slots, "header")
], 512)
]),
key: "0"
} : void 0,
$data.hasSlotContent(_ctx.$slots.footer) ? {
name: "footerContent",
fn: withCtx(() => [
createElementVNode("div", _hoisted_4, [
renderSlot(_ctx.$slots, "footer")
], 512)
]),
key: "1"
} : void 0
]), 1032, ["open", "max-height", "max-width", "offset", "sticky", "external-anchor", "content-width", "append-to", "transition", "visually-hidden-close-label", "visually-hidden-close", "onOpened"])
]),
_: 3
}, 16, ["loading", "label", "label-visible", "size", "description", "empty-list", "empty-state-message", "show-list", "on-beginning-of-list", "on-end-of-list", "list-id"]);
}
const DtRecipeComboboxWithPopover = /* @__PURE__ */ _export_sfc(_sfc_main, [["render", _sfc_render]]);
export {
DtRecipeComboboxWithPopover as default
};
//# sourceMappingURL=combobox_with_popover.vue.js.map