@dialpad/dialtone
Version:
Dialpad's Dialtone design system monorepo
563 lines (562 loc) • 17.8 kB
JavaScript
import _ from "../combobox-with-popover/combobox-with-popover.js";
import L from "../input/input.js";
import W from "../chip/chip.js";
import M from "../validation-messages/validation-messages.js";
import { validationMessageValidator as O } from "../../common/validators/index.js";
import { returnFirstEl as g, hasSlotContent as T } from "../../common/utils/index.js";
import { POPOVER_APPEND_TO_VALUES as P } from "../popover/popover-constants.js";
import { CHIP_TOP_POSITION as B, CHIP_SIZES as F, MULTI_SELECT_SIZES as k } from "./combobox-multi-select-constants.js";
import { resolveComponent as h, createBlock as m, openBlock as p, createSlots as H, withCtx as r, createElementVNode as a, withModifiers as R, renderSlot as d, createElementBlock as f, toDisplayString as b, createVNode as C, normalizeClass as z, Fragment as V, renderList as E, mergeProps as y, toHandlers as x, withKeys as D, createTextVNode as A } from "vue";
import { _ as K } from "../../_plugin-vue_export-helper-CHgC5LLL.js";
const N = {
compatConfig: { MODE: 3 },
name: "DtRecipeComboboxMultiSelect",
components: {
DtRecipeComboboxWithPopover: _,
DtInput: L,
DtChip: W,
DtValidationMessages: M
},
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) => O(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(k).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) => P.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: ""
}
},
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",
/**
* 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: F,
hasSlotContent: T,
inputFocused: !1,
hideInputText: !1
};
},
computed: {
inputPlaceHolder() {
var t;
return ((t = this.selectedItems) == null ? void 0 : t.length) > 0 ? "" : this.placeholder;
},
chipListeners() {
return {
keyup: (t) => {
this.onChipKeyup(t), this.$emit("keyup", t);
}
};
},
inputListeners() {
return {
input: (t) => {
this.$emit("input", t), this.hasSuggestionList && this.showComboboxList();
},
keyup: (t) => {
this.onInputKeyup(t), this.$emit("keyup", t);
},
click: () => {
this.hasSuggestionList && this.showComboboxList();
}
};
},
chipWrapperClass() {
return {
[`d-recipe-combobox-multi-select__chip-wrapper-${this.size}--collapsed`]: !this.inputFocused && this.collapseOnFocusOut
};
}
},
watch: {
selectedItems: {
deep: !0,
handler: async function() {
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();
},
beforeUnmount() {
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 i;
this.$emit("remove", t), (i = this.$refs.input) == null || i.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) => g(t.$el).querySelector("button"));
},
getChips() {
return this.$refs.chips && this.$refs.chips.map((t) => g(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;
},
onChipKeyup(t) {
var e;
const i = (e = t.code) == null ? void 0 : e.toLowerCase();
i === "arrowleft" ? this.navigateBetweenChips(t.target, !0) : i === "arrowright" && (t.target.id === this.getLastChipButton().id ? this.moveFromChipToInput() : this.navigateBetweenChips(t.target, !1));
},
onInputKeyup(t) {
var e;
const i = (e = t.code) == null ? void 0 : e.toLowerCase();
this.selectedItems.length > 0 && t.target.selectionStart === 0 && (i === "backspace" || i === "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, i) {
var s;
const e = this.getChipButtons().indexOf(t), l = i ? e - 1 : e + 1;
l < 0 || l >= ((s = this.$refs.chips) == null ? void 0 : s.length) || (this.getChipButtons()[e].blur(), this.getChipButtons()[l].focus(), this.closeComboboxList());
},
setChipsTopPosition() {
const t = this.getInput();
if (!t) return;
const i = this.$refs.inputSlotWrapper, e = t.getBoundingClientRect().top - i.getBoundingClientRect().top, l = this.$refs.chipsWrapper;
l.style.top = e - B[this.size] + "px";
},
setInputPadding() {
const t = this.getLastChip(), i = this.getInput(), e = this.$refs.chipsWrapper;
if (!i || (this.revertInputPadding(i), this.popoverOffset = [0, 4], !t) || this.collapseOnFocusOut && !this.inputFocused) return;
const l = t.offsetLeft + this.getFullWidth(t), s = i.getBoundingClientRect().width - l;
s > this.reservedRightSpace ? i.style.paddingLeft = l + "px" : i.style.paddingLeft = "4px";
const o = e.getBoundingClientRect().height - 4, u = t.getBoundingClientRect().height - 4, c = s > this.reservedRightSpace ? t.offsetTop + 2 : o + u - 9;
i.style.paddingTop = `${c}px`;
},
revertInputPadding(t) {
t.style.paddingLeft = "", t.style.paddingTop = "", t.style.paddingBottom = "";
},
getFullWidth(t) {
const i = window.getComputedStyle(t);
return t.offsetWidth + parseInt(i.marginLeft) + parseInt(i.marginRight);
},
setInputMinWidth() {
const t = this.getFirstChip(), i = this.getInput();
i && (t ? i.style.minWidth = this.getFullWidth(t) + 4 + "px" : i.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);
}
}
}
}, U = { ref: "header" }, j = {
key: 1,
class: "d-recipe-combobox-multi-select__list--loading"
}, Z = { ref: "footer" };
function q(t, i, e, l, s, o) {
const u = h("dt-chip"), c = h("dt-input"), I = h("dt-validation-messages"), w = h("dt-recipe-combobox-with-popover");
return p(), m(w, {
ref: "comboboxWithPopover",
label: e.label,
"show-list": e.showList,
"max-height": e.listMaxHeight,
"max-width": e.listMaxWidth,
"popover-offset": s.popoverOffset,
"has-suggestion-list": e.hasSuggestionList,
"content-width": "anchor",
"append-to": e.appendTo,
transition: e.transition,
onSelect: o.onComboboxSelect,
onHighlight: o.comboboxHighlight
}, H({
input: r(({ onInput: S }) => [
a("span", {
ref: "inputSlotWrapper",
class: "d-recipe-combobox-multi-select__input-wrapper",
onFocusin: i[1] || (i[1] = (...n) => o.handleInputFocusIn && o.handleInputFocusIn(...n)),
onFocusout: i[2] || (i[2] = (...n) => o.handleInputFocusOut && o.handleInputFocusOut(...n))
}, [
a("span", {
ref: "chipsWrapper",
class: z(["d-recipe-combobox-multi-select__chip-wrapper", o.chipWrapperClass])
}, [
(p(!0), f(V, null, E(e.selectedItems, (n) => (p(), m(u, y({
ref_for: !0,
ref: "chips",
key: n,
"label-class": ["d-chip__label"],
class: [
"d-recipe-combobox-multi-select__chip",
{ "d-recipe-combobox-multi-select__chip--truncate": !!e.chipMaxWidth }
],
style: { maxWidth: e.chipMaxWidth },
size: s.CHIP_SIZES[e.size]
}, x(o.chipListeners), {
onKeyup: D((v) => o.onChipRemove(n), ["backspace"]),
onClose: (v) => o.onChipRemove(n)
}), {
default: r(() => [
A(b(n), 1)
]),
_: 2
}, 1040, ["class", "style", "size", "onKeyup", "onClose"]))), 128))
], 2),
C(c, y({
ref: "input",
modelValue: s.value,
"onUpdate:modelValue": i[0] || (i[0] = (n) => s.value = n),
class: "d-recipe-combobox-multi-select__input",
"input-class": [
e.inputClass,
{
"d-recipe-combobox-multi-select__input--hidden": s.hideInputText
}
],
"input-wrapper-class": e.inputWrapperClass,
"aria-label": e.label,
label: e.labelVisible ? e.label : "",
description: e.description,
placeholder: o.inputPlaceHolder,
"show-messages": e.showInputMessages,
messages: e.inputMessages,
size: e.size
}, x(o.inputListeners), { onInput: S }), null, 16, ["modelValue", "input-class", "input-wrapper-class", "aria-label", "label", "description", "placeholder", "show-messages", "messages", "size", "onInput"]),
C(I, {
"validation-messages": e.maxSelectedMessage,
"show-messages": s.showValidationMessages
}, null, 8, ["validation-messages", "show-messages"])
], 544)
]),
list: r(() => [
a("div", {
ref: "list",
class: "d-recipe-combobox-multi-select__list",
onMousedown: i[3] || (i[3] = R(() => {
}, ["prevent"]))
}, [
e.loading ? (p(), f("div", j, b(e.loadingMessage), 1)) : d(t.$slots, "list", { key: 0 })
], 544)
]),
_: 2
}, [
s.hasSlotContent(t.$slots.header) ? {
name: "header",
fn: r(() => [
a("div", U, [
d(t.$slots, "header")
], 512)
]),
key: "0"
} : void 0,
s.hasSlotContent(t.$slots.footer) ? {
name: "footer",
fn: r(() => [
a("div", Z, [
d(t.$slots, "footer")
], 512)
]),
key: "1"
} : void 0
]), 1032, ["label", "show-list", "max-height", "max-width", "popover-offset", "has-suggestion-list", "append-to", "transition", "onSelect", "onHighlight"]);
}
const ot = /* @__PURE__ */ K(N, [["render", q]]);
export {
ot as default
};
//# sourceMappingURL=combobox-multi-select.js.map