vuetify
Version:
Vue Material Component Framework
283 lines (282 loc) • 9.68 kB
JavaScript
import { createTextVNode as _createTextVNode, mergeProps as _mergeProps, createVNode as _createVNode, Fragment as _Fragment } from "vue";
// Styles
import "./VSelect.css";
// Components
import { filterVTextFieldProps, makeVTextFieldProps } from "../VTextField/VTextField.mjs";
import { VCheckboxBtn } from "../VCheckbox/index.mjs";
import { VChip } from "../VChip/index.mjs";
import { VDefaultsProvider } from "../VDefaultsProvider/index.mjs";
import { VDialogTransition } from "../transitions/index.mjs";
import { VList, VListItem } from "../VList/index.mjs";
import { VMenu } from "../VMenu/index.mjs";
import { VTextField } from "../VTextField/index.mjs"; // Composables
import { forwardRefs } from "../../composables/forwardRefs.mjs";
import { IconValue } from "../../composables/icons.mjs";
import { makeItemsProps, useItems } from "../../composables/items.mjs";
import { makeTransitionProps } from "../../composables/transition.mjs";
import { useForm } from "../../composables/form.mjs";
import { useLocale } from "../../composables/locale.mjs";
import { useProxiedModel } from "../../composables/proxiedModel.mjs"; // Utility
import { computed, mergeProps, ref } from 'vue';
import { deepEqual, genericComponent, omit, propsFactory, useRender, wrapInArray } from "../../util/index.mjs"; // Types
export const makeSelectProps = propsFactory({
chips: Boolean,
closableChips: Boolean,
eager: Boolean,
hideNoData: Boolean,
hideSelected: Boolean,
menu: Boolean,
menuIcon: {
type: IconValue,
default: '$dropdown'
},
menuProps: {
type: Object
},
multiple: Boolean,
noDataText: {
type: String,
default: '$vuetify.noDataText'
},
openOnClear: Boolean,
valueComparator: {
type: Function,
default: deepEqual
},
...makeItemsProps({
itemChildren: false
})
}, 'v-select');
export const VSelect = genericComponent()({
name: 'VSelect',
props: {
...makeSelectProps(),
...omit(makeVTextFieldProps({
modelValue: null
}), ['validationValue', 'dirty', 'appendInnerIcon']),
...makeTransitionProps({
transition: {
component: VDialogTransition
}
})
},
emits: {
'update:modelValue': val => true,
'update:menu': val => true
},
setup(props, _ref) {
let {
slots
} = _ref;
const {
t
} = useLocale();
const vTextFieldRef = ref();
const vMenuRef = ref();
const _menu = useProxiedModel(props, 'menu');
const menu = computed({
get: () => _menu.value,
set: v => {
if (_menu.value && !v && vMenuRef.value?.ΨopenChildren) return;
_menu.value = v;
}
});
const {
items,
transformIn,
transformOut
} = useItems(props);
const model = useProxiedModel(props, 'modelValue', [], v => transformIn(wrapInArray(v)), v => {
const transformed = transformOut(v);
return props.multiple ? transformed : transformed[0] ?? null;
});
const form = useForm();
const selections = computed(() => {
return model.value.map(v => {
return items.value.find(item => props.valueComparator(item.value, v.value)) || v;
});
});
const selected = computed(() => selections.value.map(selection => selection.props.value));
const displayItems = computed(() => {
if (props.hideSelected) {
return items.value.filter(item => !selections.value.some(s => s === item));
}
return items.value;
});
const listRef = ref();
function onClear(e) {
if (props.openOnClear) {
menu.value = true;
}
}
function onMousedownControl() {
if (props.hideNoData && !items.value.length || props.readonly || form?.isReadonly.value) return;
menu.value = !menu.value;
}
function onKeydown(e) {
if (props.readonly || form?.isReadonly.value) return;
if (['Enter', ' ', 'ArrowDown', 'ArrowUp', 'Home', 'End'].includes(e.key)) {
e.preventDefault();
}
if (['Enter', 'ArrowDown', ' '].includes(e.key)) {
menu.value = true;
}
if (['Escape', 'Tab'].includes(e.key)) {
menu.value = false;
}
if (e.key === 'ArrowDown') {
listRef.value?.focus('next');
} else if (e.key === 'ArrowUp') {
listRef.value?.focus('prev');
} else if (e.key === 'Home') {
listRef.value?.focus('first');
} else if (e.key === 'End') {
listRef.value?.focus('last');
}
}
function select(item) {
if (props.multiple) {
const index = selected.value.findIndex(selection => props.valueComparator(selection, item.value));
if (index === -1) {
model.value = [...model.value, item];
} else {
const value = [...model.value];
value.splice(index, 1);
model.value = value;
}
} else {
model.value = [item];
menu.value = false;
}
}
function onBlur(e) {
if (!listRef.value?.$el.contains(e.relatedTarget)) {
menu.value = false;
}
}
function onFocusout(e) {
if (e.relatedTarget == null) {
vTextFieldRef.value?.focus();
}
}
useRender(() => {
const hasChips = !!(props.chips || slots.chip);
const hasList = !!(!props.hideNoData || displayItems.value.length || slots.prepend || slots.append || slots['no-data']);
const [textFieldProps] = filterVTextFieldProps(props);
return _createVNode(VTextField, _mergeProps({
"ref": vTextFieldRef
}, textFieldProps, {
"modelValue": model.value.map(v => v.props.value).join(', '),
"onUpdate:modelValue": v => {
if (v == null) model.value = [];
},
"validationValue": model.externalValue,
"dirty": model.value.length > 0,
"class": ['v-select', {
'v-select--active-menu': menu.value,
'v-select--chips': !!props.chips,
[`v-select--${props.multiple ? 'multiple' : 'single'}`]: true,
'v-select--selected': model.value.length
}],
"appendInnerIcon": props.menuIcon,
"readonly": true,
"onClick:clear": onClear,
"onMousedown:control": onMousedownControl,
"onBlur": onBlur,
"onKeydown": onKeydown
}), {
...slots,
default: () => _createVNode(_Fragment, null, [_createVNode(VMenu, _mergeProps({
"ref": vMenuRef,
"modelValue": menu.value,
"onUpdate:modelValue": $event => menu.value = $event,
"activator": "parent",
"contentClass": "v-select__content",
"eager": props.eager,
"maxHeight": 310,
"openOnClick": false,
"closeOnContentClick": false,
"transition": props.transition
}, props.menuProps), {
default: () => [hasList && _createVNode(VList, {
"ref": listRef,
"selected": selected.value,
"selectStrategy": props.multiple ? 'independent' : 'single-independent',
"onMousedown": e => e.preventDefault(),
"onFocusout": onFocusout
}, {
default: () => [!displayItems.value.length && !props.hideNoData && (slots['no-data']?.() ?? _createVNode(VListItem, {
"title": t(props.noDataText)
}, null)), slots['prepend-item']?.(), displayItems.value.map((item, index) => {
if (slots.item) {
return slots.item?.({
item,
index,
props: mergeProps(item.props, {
onClick: () => select(item)
})
});
}
return _createVNode(VListItem, _mergeProps({
"key": index
}, item.props, {
"onClick": () => select(item)
}), {
prepend: _ref2 => {
let {
isSelected
} = _ref2;
return props.multiple && !props.hideSelected ? _createVNode(VCheckboxBtn, {
"modelValue": isSelected,
"ripple": false
}, null) : undefined;
}
});
}), slots['append-item']?.()]
})]
}), selections.value.map((item, index) => {
function onChipClose(e) {
e.stopPropagation();
e.preventDefault();
select(item);
}
const slotProps = {
'onClick:close': onChipClose,
modelValue: true,
'onUpdate:modelValue': undefined
};
return _createVNode("div", {
"key": item.value,
"class": "v-select__selection"
}, [hasChips ? _createVNode(VDefaultsProvider, {
"defaults": {
VChip: {
closable: props.closableChips,
size: 'small',
text: item.title
}
}
}, {
default: () => [slots.chip ? slots.chip({
item,
index,
props: slotProps
}) : _createVNode(VChip, slotProps, null)]
}) : slots.selection ? slots.selection({
item,
index
}) : _createVNode("span", {
"class": "v-select__selection-text"
}, [item.title, props.multiple && index < selections.value.length - 1 && _createVNode("span", {
"class": "v-select__selection-comma"
}, [_createTextVNode(",")])])]);
})])
});
});
return forwardRefs({
menu,
select
}, vTextFieldRef);
}
});
//# sourceMappingURL=VSelect.mjs.map