@dialpad/dialtone-vue
Version:
Vue component library for Dialpad's design system Dialtone
515 lines (514 loc) • 16.9 kB
JavaScript
import p from "../combobox-with-popover/combobox-with-popover.js";
import l from "../input/input.js";
import h from "../chip/chip.js";
import u from "../validation-messages/validation-messages.js";
import { validationMessageValidator as c } from "../../common/validators/index.js";
import { POPOVER_APPEND_TO_VALUES as d } from "../popover/popover-constants.js";
import { CHIP_TOP_POSITION as f, CHIP_SIZES as g, MULTI_SELECT_SIZES as m } from "./combobox-multi-select-constants.js";
import { getUniqueString as b } from "../../common/utils/index.js";
import { n as y } from "../../_plugin-vue2_normalizer-DSLOjnn3.js";
const C = {
name: "DtRecipeComboboxMultiSelect",
components: {
DtRecipeComboboxWithPopover: p,
DtInput: l,
DtChip: h,
DtValidationMessages: u
},
props: {
/**
* String to use for the input label.
*/
label: {
type: String,
required: !0
},
/**
* Determines visibility of input label.
* @values true, false
*/
labelVisible: {
type: Boolean,
default: !0
},
/**
* Description for the input
*/
description: {
type: String,
default: ""
},
/**
* Input placeholder
*/
placeholder: {
type: String,
default: "Select one or start typing"
},
/**
* Input validation messages
*/
inputMessages: {
type: Array,
default: () => [],
validator: (t) => c(t)
},
/**
* Show input validation message
*/
showInputMessages: {
type: Boolean,
default: !0
},
// @TODO: https://dialpad.atlassian.net/browse/DP-52324
// type: {
// type: String,
// values: ['input', 'select'],
// default: 'select',
// },
/**
* Determines if the list is loading
*/
loading: {
type: Boolean,
default: !1
},
/**
* The message when the list is loading
*/
loadingMessage: {
type: String,
default: "loading..."
},
/**
* 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
},
/**
* Determines maximum height for the popover before overflow.
* Possible units rem|px|em
*/
listMaxHeight: {
type: String,
default: "300px"
},
/**
* The selected items
*/
selectedItems: {
type: Array,
default: function() {
return [];
}
},
/**
* Would be the maximum number of selections you can make. 0 is unlimited
*/
maxSelected: {
type: Number,
default: 0
},
/**
* Max select message when the max selections is exceeded with the structure:
* `[{"message": string, "type": VALIDATION_MESSAGE_TYPES }]`
*/
maxSelectedMessage: {
type: Array,
default: function() {
return [];
}
},
/**
* 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: !0
},
/**
* Size of the chip, one of `xs`, `sm`, `md`
*/
size: {
type: String,
default: "md",
validator: (t) => Object.values(m).includes(t)
},
/**
* 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: (t) => d.includes(t) || t instanceof HTMLElement
},
/**
* Named transition when the content display is toggled.
* @see DtLazyShow
*/
transition: {
type: String,
default: "fade"
},
/**
* Determines whether the combobox should collapse to a single when losing focus.
* @type {boolean}
*/
collapseOnFocusOut: {
type: Boolean,
default: !1
},
/**
* Determines maximum width for the popover before overflow.
* Possible units rem|px|em
*/
listMaxWidth: {
type: String,
default: ""
},
/**
* Amount of reserved space (in px) on the right side of the input
* before the chips and the input caret jump to the next line.
* default is 64
*/
reservedRightSpace: {
type: Number,
default: 64
},
/**
* Determines the maximum width of a single chip. If the text within this chip exceeds the value
* it will be truncated with ellipses.
* Possible units rem|px|em
*/
chipMaxWidth: {
type: String,
default: ""
},
/**
* Additional class name for the input element.
* Can accept String, Object, and Array, i.e. has the
* same API as Vue's built-in handling of the class attribute.
*/
inputClass: {
type: [String, Object, Array],
default: ""
},
/**
* Additional class name for the input wrapper 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.
*/
inputWrapperClass: {
type: [String, Object, Array],
default: ""
},
/**
* When true, disables the underlying input.
*/
disabled: {
type: Boolean,
default: !1
}
},
emits: [
/**
* Native input event
*
* @event input
* @type {String }
*/
"input",
/**
* Event fired when item selected
*
* @event select
* @type {Number}
*/
"select",
/**
* Event fired when item removed
*
* @event remove
* @type {String}
*/
"remove",
/**
* Event fired when max selected items limit is reached
*
* @event max-selected
* @type {Object}
*/
"max-selected",
/**
* Native keyup event
*
* @event keyup
* @type {KeyboardEvent}
*/
"keyup",
/**
* Native keydown event
*
* @event keydown
* @type {KeyboardEvent}
*/
"keydown",
/**
* Event fired when combobox item is highlighted
*
* @event combobox-highlight
* @type {Object}
*/
"combobox-highlight"
],
data() {
return {
value: "",
popoverOffset: [0, 4],
showValidationMessages: !1,
resizeWindowObserver: null,
initialInputHeight: null,
CHIP_SIZES: g,
inputFocused: !1,
hideInputText: !1
};
},
computed: {
inputPlaceHolder() {
var t;
return ((t = this.selectedItems) == null ? void 0 : t.length) > 0 ? "" : this.placeholder;
},
chipListeners() {
return {
...this.$listeners,
keydown: (t) => {
this.onChipKeyDown(t), this.$emit("keydown", t);
}
};
},
inputListeners() {
return {
...this.$listeners,
input: (t) => {
this.$emit("input", t), this.hasSuggestionList && this.showComboboxList();
},
keydown: (t) => {
this.onInputKeyDown(t);
},
keyup: (t) => {
this.$emit("keyup", t);
},
click: () => {
this.hasSuggestionList && this.showComboboxList();
}
};
},
selectedItemsWithKeys() {
return this.selectedItems.map((t) => ({
item: t,
key: b(t)
}));
},
chipWrapperClass() {
return {
[`d-recipe-combobox-multi-select__chip-wrapper-${this.size}--collapsed`]: !this.inputFocused && this.collapseOnFocusOut
};
}
},
watch: {
selectedItems: {
async handler() {
this.initSelectedItems();
}
},
chipMaxWidth: {
async handler() {
this.initSelectedItems();
}
},
async label() {
await this.$nextTick(), this.setChipsTopPosition();
},
async description() {
await this.$nextTick(), this.setChipsTopPosition();
},
size: {
async handler() {
await this.$nextTick();
const t = this.getInput();
this.revertInputPadding(t), this.initialInputHeight = t.getBoundingClientRect().height, this.setInputPadding(), this.setChipsTopPosition();
}
}
},
mounted() {
this.setInitialInputHeight(), this.resizeWindowObserver = new ResizeObserver(async () => {
this.setChipsTopPosition(), this.setInputPadding();
}), this.resizeWindowObserver.observe(document.body), this.initSelectedItems();
},
beforeDestroy() {
var t;
(t = this.resizeWindowObserver) == null || t.unobserve(document.body);
},
methods: {
comboboxHighlight(t) {
this.$emit("combobox-highlight", t);
},
async initSelectedItems() {
await this.$nextTick(), this.setInputPadding(), this.setChipsTopPosition(), this.setInputMinWidth(), this.checkMaxSelected();
},
onChipRemove(t) {
var e;
this.$emit("remove", t), (e = this.$refs.input) == null || e.focus();
},
onComboboxSelect(t) {
this.loading || (this.value = "", this.$emit("select", t));
},
showComboboxList() {
var t;
this.showList == null && ((t = this.$refs.comboboxWithPopover) == null || t.showComboboxList());
},
closeComboboxList() {
var t;
this.showList == null && ((t = this.$refs.comboboxWithPopover) == null || t.closeComboboxList());
},
getChipButtons() {
return this.$refs.chips && this.$refs.chips.map((t) => t.$el.querySelector("button"));
},
getChips() {
return this.$refs.chips && this.$refs.chips.map((t) => t.$el);
},
getLastChipButton() {
return this.$refs.chips && this.getChipButtons()[this.getChipButtons().length - 1];
},
getLastChip() {
return this.$refs.chips && this.getChips()[this.getChips().length - 1];
},
getFirstChip() {
return this.$refs.chips && this.getChips()[0];
},
getInput() {
var t;
return (t = this.$refs.input) == null ? void 0 : t.$refs.input;
},
onChipKeyDown(t) {
var i;
const e = (i = t.code) == null ? void 0 : i.toLowerCase();
e === "arrowleft" ? this.navigateBetweenChips(t.target, !0) : e === "arrowright" && (t.target.id === this.getLastChipButton().id ? this.moveFromChipToInput() : this.navigateBetweenChips(t.target, !1));
},
onInputKeyDown(t) {
var i;
const e = (i = t.code) == null ? void 0 : i.toLowerCase();
if (this.selectedItems.length > 0 && t.target.selectionStart === 0) {
if (t.target.selectionEnd !== t.target.selectionStart)
return;
(e === "backspace" || e === "arrowleft") && this.moveFromInputToChip();
}
},
moveFromInputToChip() {
var t;
this.getLastChipButton().focus(), (t = this.$refs.input) == null || t.blur(), this.closeComboboxList();
},
moveFromChipToInput() {
var t;
this.getLastChipButton().blur(), (t = this.$refs.input) == null || t.focus(), this.showComboboxList();
},
navigateBetweenChips(t, e) {
var s;
const i = this.getChipButtons().indexOf(t), o = e ? i - 1 : i + 1;
o < 0 || o >= ((s = this.$refs.chips) == null ? void 0 : s.length) || (this.getChipButtons()[i].blur(), this.getChipButtons()[o].focus(), this.closeComboboxList());
},
setChipsTopPosition() {
const t = this.getInput();
if (!t) return;
const e = this.$refs.inputSlotWrapper, i = t.getBoundingClientRect().top - e.getBoundingClientRect().top, o = this.$refs.chipsWrapper;
o.style.top = i - f[this.size] + "px";
},
setInputPadding() {
const t = this.getLastChip(), e = this.getInput(), i = this.$refs.chipsWrapper;
if (!e || (this.revertInputPadding(e), this.popoverOffset = [0, 4], !t) || this.collapseOnFocusOut && !this.inputFocused) return;
const o = t.offsetLeft + this.getFullWidth(t), s = e.getBoundingClientRect().width - o;
s > this.reservedRightSpace ? e.style.paddingLeft = o + "px" : e.style.paddingLeft = "4px";
const r = i.getBoundingClientRect().height - 4, n = t.getBoundingClientRect().height - 4, a = s > this.reservedRightSpace ? t.offsetTop + 2 : r + n - 9;
e.style.paddingTop = `${a}px`;
},
revertInputPadding(t) {
t.style.paddingLeft = "", t.style.paddingTop = "", t.style.paddingBottom = "";
},
getFullWidth(t) {
const e = window.getComputedStyle(t);
return t.offsetWidth + parseInt(e.marginLeft) + parseInt(e.marginRight);
},
setInputMinWidth() {
const t = this.getFirstChip(), e = this.getInput();
e && (t ? e.style.minWidth = this.getFullWidth(t) + 4 + "px" : e.style.minWidth = "");
},
checkMaxSelected() {
this.maxSelected !== 0 && (this.selectedItems.length > this.maxSelected ? (this.showValidationMessages = !0, this.$emit("max-selected")) : this.showValidationMessages = !1);
},
setInitialInputHeight() {
const t = this.getInput();
t && (this.initialInputHeight = t.getBoundingClientRect().height);
},
async handleInputFocusIn() {
this.inputFocused = !0, this.collapseOnFocusOut && (this.hideInputText = !1, await this.$nextTick(), this.setInputPadding());
},
async handleInputFocusOut() {
if (this.inputFocused = !1, this.collapseOnFocusOut) {
this.hideInputText = !0;
const t = this.getInput();
if (!t || !t.style.paddingTop)
return;
this.revertInputPadding(t);
}
}
}
};
var x = function() {
var e = this, i = e._self._c;
return i("dt-recipe-combobox-with-popover", { ref: "comboboxWithPopover", attrs: { label: e.label, "show-list": e.showList, "max-height": e.listMaxHeight, "max-width": e.listMaxWidth, "popover-offset": e.popoverOffset, "has-suggestion-list": e.hasSuggestionList, "content-width": "anchor", "append-to": e.appendTo, transition: e.transition }, on: { select: e.onComboboxSelect, highlight: e.comboboxHighlight }, scopedSlots: e._u([{ key: "input", fn: function({ onInput: o }) {
return [i("span", { ref: "inputSlotWrapper", staticClass: "d-recipe-combobox-multi-select__input-wrapper", on: { focusin: e.handleInputFocusIn, focusout: e.handleInputFocusOut } }, [i("span", { ref: "chipsWrapper", class: ["d-recipe-combobox-multi-select__chip-wrapper", e.chipWrapperClass] }, e._l(e.selectedItemsWithKeys, function({ item: s, key: r }) {
return i("dt-chip", e._g({ key: r, ref: "chips", refInFor: !0, class: [
"d-recipe-combobox-multi-select__chip",
{ "d-recipe-combobox-multi-select__chip--truncate": !!e.chipMaxWidth }
], style: { maxWidth: e.chipMaxWidth }, attrs: { "label-class": ["d-chip__label"], size: e.CHIP_SIZES[e.size], disabled: e.disabled }, on: { keydown: function(n) {
return !n.type.indexOf("key") && e._k(n.keyCode, "backspace", void 0, n.key, void 0) ? null : e.onChipRemove(s);
}, close: function(n) {
return e.onChipRemove(s);
} } }, e.chipListeners), [e._v(" " + e._s(s) + " ")]);
}), 1), i("dt-input", e._g({ ref: "input", staticClass: "d-recipe-combobox-multi-select__input", attrs: { "input-class": [
e.inputClass,
{
"d-recipe-combobox-multi-select__input--hidden": e.hideInputText
}
], "input-wrapper-class": e.inputWrapperClass, disabled: e.disabled, "aria-label": e.label, label: e.labelVisible ? e.label : "", description: e.description, placeholder: e.inputPlaceHolder, "show-messages": e.showInputMessages, messages: e.inputMessages, size: e.size }, on: { input: o, select: function(s) {
s.stopPropagation();
} }, model: { value: e.value, callback: function(s) {
e.value = s;
}, expression: "value" } }, e.inputListeners)), i("dt-validation-messages", { attrs: { "validation-messages": e.maxSelectedMessage, "show-messages": e.showValidationMessages } })], 1)];
} }, e.$slots.header ? { key: "header", fn: function() {
return [i("div", { ref: "header" }, [e._t("header")], 2)];
}, proxy: !0 } : null, { key: "list", fn: function() {
return [i("div", { ref: "list", staticClass: "d-recipe-combobox-multi-select__list", on: { mousedown: function(o) {
o.preventDefault();
} } }, [e.loading ? i("div", { staticClass: "d-recipe-combobox-multi-select__list--loading" }, [e._v(" " + e._s(e.loadingMessage) + " ")]) : e._t("list")], 2)];
}, proxy: !0 }, e.$slots.footer ? { key: "footer", fn: function() {
return [i("div", { ref: "footer" }, [e._t("footer")], 2)];
}, proxy: !0 } : null], null, !0) });
}, I = [], w = /* @__PURE__ */ y(
C,
x,
I
);
const T = w.exports;
export {
T as default
};
//# sourceMappingURL=combobox-multi-select.js.map