@dialpad/dialtone-vue
Version:
Vue component library for Dialpad's design system Dialtone
590 lines (589 loc) • 19.4 kB
JavaScript
"use strict";
Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
const combobox_with_popover = require("../combobox_with_popover/combobox_with_popover.vue.cjs");
const input = require("../../../components/input/input.vue.cjs");
const chip = require("../../../components/chip/chip.vue.cjs");
const validation_messages = require("../../../components/validation_messages/validation_messages.vue.cjs");
const common_validators = require("../../../common/validators.cjs");
const popover_constants = require("../../../components/popover/popover_constants.cjs");
const combobox_multi_select_constants = require("./combobox_multi_select_constants.cjs");
const sr_only_close_button = require("../../../common/mixins/sr_only_close_button.cjs");
const common_utils = require("../../../common/utils.cjs");
const _pluginVue2_normalizer = require("../../../_virtual/_plugin-vue2_normalizer.cjs");
const _sfc_main = {
name: "DtRecipeComboboxMultiSelect",
components: {
DtRecipeComboboxWithPopover: combobox_with_popover.default,
DtInput: input.default,
DtChip: chip.default,
DtValidationMessages: validation_messages.default
},
mixins: [sr_only_close_button.default],
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
},
/**
* 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: (inputMessages) => {
return common_validators.validationMessageValidator(inputMessages);
}
},
/**
* Show input validation message
*/
showInputMessages: {
type: Boolean,
default: true
},
// @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: false
},
/**
* 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: true
},
/**
* Size of the chip, one of `xs`, `sm`, `md`
*/
size: {
type: String,
default: "md",
validator: (t) => Object.values(combobox_multi_select_constants.MULTI_SELECT_SIZES).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: (appendTo) => {
return popover_constants.POPOVER_APPEND_TO_VALUES.includes(appendTo) || appendTo 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: false
},
/**
* 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: false,
resizeWindowObserver: null,
initialInputHeight: null,
CHIP_SIZES: combobox_multi_select_constants.CHIP_SIZES,
inputFocused: false,
hideInputText: false
};
},
computed: {
inputPlaceHolder() {
var _a;
return ((_a = this.selectedItems) == null ? void 0 : _a.length) > 0 ? "" : this.placeholder;
},
chipListeners() {
return {
...this.$listeners,
keyup: (event) => {
this.onChipKeyup(event);
this.$emit("keyup", event);
}
};
},
inputListeners() {
return {
...this.$listeners,
input: (event) => {
this.$emit("input", event);
if (this.hasSuggestionList) {
this.showComboboxList();
}
},
keyup: (event) => {
this.onInputKeyup(event);
this.$emit("keyup", event);
},
click: (event) => {
if (this.hasSuggestionList) {
this.showComboboxList();
}
}
};
},
selectedItemsWithKeys() {
return this.selectedItems.map((selectedItem) => ({
item: selectedItem,
key: common_utils.getUniqueString(selectedItem)
}));
},
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 input2 = this.getInput();
this.revertInputPadding(input2);
this.initialInputHeight = input2.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 _a;
(_a = this.resizeWindowObserver) == null ? void 0 : _a.unobserve(document.body);
},
methods: {
comboboxHighlight(highlightIndex) {
this.$emit("combobox-highlight", highlightIndex);
},
async initSelectedItems() {
await this.$nextTick();
this.setInputPadding();
this.setChipsTopPosition();
this.setInputMinWidth();
this.checkMaxSelected();
},
onChipRemove(item) {
var _a;
this.$emit("remove", item);
(_a = this.$refs.input) == null ? void 0 : _a.focus();
},
onComboboxSelect(i) {
if (this.loading) return;
this.value = "";
this.$emit("select", i);
},
showComboboxList() {
var _a;
if (this.showList != null) {
return;
}
(_a = this.$refs.comboboxWithPopover) == null ? void 0 : _a.showComboboxList();
},
closeComboboxList() {
var _a;
if (this.showList != null) {
return;
}
(_a = this.$refs.comboboxWithPopover) == null ? void 0 : _a.closeComboboxList();
},
getChipButtons() {
return this.$refs.chips && this.$refs.chips.map((chip2) => chip2.$el.querySelector("button"));
},
getChips() {
return this.$refs.chips && this.$refs.chips.map((chip2) => chip2.$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 _a;
return (_a = this.$refs.input) == null ? void 0 : _a.$refs.input;
},
onChipKeyup(event) {
var _a;
const key = (_a = event.code) == null ? void 0 : _a.toLowerCase();
if (key === "arrowleft") {
this.navigateBetweenChips(event.target, true);
} else if (key === "arrowright") {
if (event.target.id === this.getLastChipButton().id) {
this.moveFromChipToInput();
} else {
this.navigateBetweenChips(event.target, false);
}
}
},
onInputKeyup(event) {
var _a;
const key = (_a = event.code) == null ? void 0 : _a.toLowerCase();
if (this.selectedItems.length > 0 && event.target.selectionStart === 0) {
if (key === "backspace" || key === "arrowleft") {
this.moveFromInputToChip();
}
}
},
moveFromInputToChip() {
var _a;
this.getLastChipButton().focus();
(_a = this.$refs.input) == null ? void 0 : _a.blur();
this.closeComboboxList();
},
moveFromChipToInput() {
var _a;
this.getLastChipButton().blur();
(_a = this.$refs.input) == null ? void 0 : _a.focus();
this.showComboboxList();
},
navigateBetweenChips(target, toLeft) {
var _a;
const from = this.getChipButtons().indexOf(target);
const to = toLeft ? from - 1 : from + 1;
if (to < 0 || to >= ((_a = this.$refs.chips) == null ? void 0 : _a.length)) {
return;
}
this.getChipButtons()[from].blur();
this.getChipButtons()[to].focus();
this.closeComboboxList();
},
setChipsTopPosition() {
const input2 = this.getInput();
if (!input2) return;
const inputSlotWrapper = this.$refs.inputSlotWrapper;
const top = input2.getBoundingClientRect().top - inputSlotWrapper.getBoundingClientRect().top;
const chipsWrapper = this.$refs.chipsWrapper;
chipsWrapper.style.top = top - combobox_multi_select_constants.CHIP_TOP_POSITION[this.size] + "px";
},
setInputPadding() {
const lastChip = this.getLastChip();
const input2 = this.getInput();
const chipsWrapper = this.$refs.chipsWrapper;
if (!input2) return;
this.revertInputPadding(input2);
this.popoverOffset = [0, 4];
if (!lastChip) return;
if (this.collapseOnFocusOut && !this.inputFocused) return;
const left = lastChip.offsetLeft + this.getFullWidth(lastChip);
const spaceLeft = input2.getBoundingClientRect().width - left;
if (spaceLeft > this.reservedRightSpace) {
input2.style.paddingLeft = left + "px";
} else {
input2.style.paddingLeft = "4px";
}
const chipsWrapperHeight = chipsWrapper.getBoundingClientRect().height - 4;
const lastChipHeight = lastChip.getBoundingClientRect().height - 4;
const top = spaceLeft > this.reservedRightSpace ? lastChip.offsetTop + 2 : chipsWrapperHeight + lastChipHeight - 9;
input2.style.paddingTop = `${top}px`;
},
revertInputPadding(input2) {
input2.style.paddingLeft = "";
input2.style.paddingTop = "";
input2.style.paddingBottom = "";
},
getFullWidth(el) {
const styles = window.getComputedStyle(el);
return el.offsetWidth + parseInt(styles.marginLeft) + parseInt(styles.marginRight);
},
setInputMinWidth() {
const firstChip = this.getFirstChip();
const input2 = this.getInput();
if (!input2) return;
if (firstChip) {
input2.style.minWidth = this.getFullWidth(firstChip) + 4 + "px";
} else {
input2.style.minWidth = "";
}
},
checkMaxSelected() {
if (this.maxSelected === 0) return;
if (this.selectedItems.length > this.maxSelected) {
this.showValidationMessages = true;
this.$emit("max-selected");
} else {
this.showValidationMessages = false;
}
},
setInitialInputHeight() {
const input2 = this.getInput();
if (!input2) return;
this.initialInputHeight = input2.getBoundingClientRect().height;
},
async handleInputFocusIn() {
this.inputFocused = true;
if (this.collapseOnFocusOut) {
await this.$nextTick();
this.setInputPadding();
this.hideInputText = false;
}
},
async handleInputFocusOut() {
this.inputFocused = false;
if (this.collapseOnFocusOut) {
const input2 = this.getInput();
if (!input2) return;
if (!input2.style.paddingTop) {
return;
}
this.hideInputText = true;
this.revertInputPadding(input2);
}
}
}
};
var _sfc_render = function render() {
var _vm = this, _c = _vm._self._c;
return _c("dt-recipe-combobox-with-popover", { ref: "comboboxWithPopover", attrs: { "label": _vm.label, "show-list": _vm.showList, "max-height": _vm.listMaxHeight, "max-width": _vm.listMaxWidth, "popover-offset": _vm.popoverOffset, "has-suggestion-list": _vm.hasSuggestionList, "visually-hidden-close-label": _vm.visuallyHiddenCloseLabel, "visually-hidden-close": _vm.visuallyHiddenClose, "content-width": "anchor", "append-to": _vm.appendTo, "transition": _vm.transition }, on: { "select": _vm.onComboboxSelect, "highlight": _vm.comboboxHighlight }, scopedSlots: _vm._u([{ key: "input", fn: function({ onInput }) {
return [_c("span", { ref: "inputSlotWrapper", staticClass: "d-recipe-combobox-multi-select__input-wrapper", on: { "focusin": _vm.handleInputFocusIn, "focusout": _vm.handleInputFocusOut } }, [_c("span", { ref: "chipsWrapper", class: ["d-recipe-combobox-multi-select__chip-wrapper", _vm.chipWrapperClass] }, _vm._l(_vm.selectedItemsWithKeys, function({ item, key }) {
return _c("dt-chip", _vm._g({ key, ref: "chips", refInFor: true, class: [
"d-recipe-combobox-multi-select__chip",
{ "d-recipe-combobox-multi-select__chip--truncate": !!_vm.chipMaxWidth }
], style: { maxWidth: _vm.chipMaxWidth }, attrs: { "label-class": ["d-chip__label"], "close-button-props": { ariaLabel: "close" }, "size": _vm.CHIP_SIZES[_vm.size] }, on: { "keyup": function($event) {
if (!$event.type.indexOf("key") && _vm._k($event.keyCode, "backspace", void 0, $event.key, void 0)) return null;
return _vm.onChipRemove(item);
}, "close": function($event) {
return _vm.onChipRemove(item);
} } }, _vm.chipListeners), [_vm._v(" " + _vm._s(item) + " ")]);
}), 1), _c("dt-input", _vm._g({ ref: "input", staticClass: "d-recipe-combobox-multi-select__input", attrs: { "input-class": [
_vm.inputClass,
{
"d-recipe-combobox-multi-select__input--hidden": _vm.hideInputText
}
], "input-wrapper-class": _vm.inputWrapperClass, "aria-label": _vm.label, "label": _vm.labelVisible ? _vm.label : "", "description": _vm.description, "placeholder": _vm.inputPlaceHolder, "show-messages": _vm.showInputMessages, "messages": _vm.inputMessages, "size": _vm.size }, on: { "input": onInput }, model: { value: _vm.value, callback: function($$v) {
_vm.value = $$v;
}, expression: "value" } }, _vm.inputListeners)), _c("dt-validation-messages", { attrs: { "validation-messages": _vm.maxSelectedMessage, "show-messages": _vm.showValidationMessages } })], 1)];
} }, _vm.$slots.header ? { key: "header", fn: function() {
return [_c("div", { ref: "header" }, [_vm._t("header")], 2)];
}, proxy: true } : null, { key: "list", fn: function() {
return [_c("div", { ref: "list", staticClass: "d-recipe-combobox-multi-select__list", on: { "mousedown": function($event) {
$event.preventDefault();
} } }, [!_vm.loading ? _vm._t("list") : _c("div", { staticClass: "d-recipe-combobox-multi-select__list--loading" }, [_vm._v(" " + _vm._s(_vm.loadingMessage) + " ")])], 2)];
}, proxy: true }, _vm.$slots.footer ? { key: "footer", fn: function() {
return [_c("div", { ref: "footer" }, [_vm._t("footer")], 2)];
}, proxy: true } : null], null, true) });
};
var _sfc_staticRenderFns = [];
var __component__ = /* @__PURE__ */ _pluginVue2_normalizer.default(
_sfc_main,
_sfc_render,
_sfc_staticRenderFns
);
const combobox_multi_select = __component__.exports;
exports.default = combobox_multi_select;
//# sourceMappingURL=combobox_multi_select.vue.cjs.map