vuetify
Version:
Vue Material Component Framework
269 lines (268 loc) • 9.48 kB
JavaScript
import { Fragment as _Fragment, mergeProps as _mergeProps, createVNode as _createVNode, createElementVNode as _createElementVNode } from "vue";
// Components
import { makeVConfirmEditProps, VConfirmEdit } from "../../components/VConfirmEdit/VConfirmEdit.js";
import { makeVDatePickerProps, VDatePicker } from "../../components/VDatePicker/VDatePicker.js";
import { VMenu } from "../../components/VMenu/VMenu.js";
import { makeVTextFieldProps, VTextField } from "../../components/VTextField/VTextField.js"; // Composables
import { useDate } from "../../composables/date/index.js";
import { makeDateFormatProps, useDateFormat } from "../../composables/dateFormat.js";
import { makeDisplayProps, useDisplay } from "../../composables/display.js";
import { makeFocusProps, useFocus } from "../../composables/focus.js";
import { forwardRefs } from "../../composables/forwardRefs.js";
import { useLocale } from "../../composables/locale.js";
import { useProxiedModel } from "../../composables/proxiedModel.js"; // Utilities
import { computed, ref, shallowRef, watch } from 'vue';
import { createRange, genericComponent, omit, propsFactory, useRender, wrapInArray } from "../../util/index.js"; // Types
// Types
export const makeVDateInputProps = propsFactory({
displayFormat: [Function, String],
location: {
type: String,
default: 'bottom start'
},
menu: Boolean,
updateOn: {
type: Array,
default: () => ['blur', 'enter']
},
...makeDateFormatProps(),
...makeDisplayProps({
mobile: null
}),
...makeFocusProps(),
...makeVConfirmEditProps({
hideActions: true
}),
...makeVTextFieldProps({
prependIcon: '$calendar'
}),
...omit(makeVDatePickerProps({
hideHeader: true,
showAdjacentMonths: true
}), ['active', 'location', 'rounded'])
}, 'VDateInput');
export const VDateInput = genericComponent()({
name: 'VDateInput',
props: makeVDateInputProps(),
emits: {
save: value => true,
cancel: () => true,
'update:modelValue': val => true,
'update:menu': val => true
},
setup(props, _ref) {
let {
emit,
slots
} = _ref;
const {
t,
current: currentLocale
} = useLocale();
const adapter = useDate();
const {
isValid,
parseDate,
formatDate,
parserFormat
} = useDateFormat(props, currentLocale);
const {
mobile
} = useDisplay(props);
const {
isFocused,
focus,
blur
} = useFocus(props);
const emptyModelValue = () => props.multiple ? [] : null;
const model = useProxiedModel(props, 'modelValue', emptyModelValue(), val => Array.isArray(val) ? val.map(item => adapter.toJsDate(item)) : val ? adapter.toJsDate(val) : val, val => Array.isArray(val) ? val.map(item => adapter.date(item)) : val ? adapter.date(val) : val);
const menu = useProxiedModel(props, 'menu');
const isEditingInput = shallowRef(false);
const vTextFieldRef = ref();
const disabledActions = ref(['save']);
function format(date) {
if (typeof props.displayFormat === 'function') {
return props.displayFormat(date);
}
if (props.displayFormat) {
return adapter.format(date, props.displayFormat ?? 'keyboardDate');
}
return formatDate(date);
}
const display = computed(() => {
const value = wrapInArray(model.value);
if (!value.length) return null;
if (props.multiple === true) {
return t('$vuetify.datePicker.itemsSelected', value.length);
}
if (props.multiple === 'range') {
const start = value[0];
const end = value[value.length - 1];
if (!adapter.isValid(start) || !adapter.isValid(end)) return '';
return `${format(adapter.date(start))} - ${format(adapter.date(end))}`;
}
return adapter.isValid(model.value) ? format(adapter.date(model.value)) : '';
});
const inputmode = computed(() => {
if (!mobile.value) return undefined;
if (isEditingInput.value) return 'text';
return 'none';
});
const isInteractive = computed(() => !props.disabled && !props.readonly);
const isReadonly = computed(() => {
if (!props.updateOn.length) return true;
return !(mobile.value && isEditingInput.value) && props.readonly;
});
watch(menu, val => {
if (val) return;
isEditingInput.value = false;
disabledActions.value = ['save'];
});
function onKeydown(e) {
if (e.key !== 'Enter') return;
if (!menu.value || !isFocused.value) {
menu.value = true;
}
if (props.updateOn.includes('enter')) {
onUserInput(e.target);
}
}
function onClick(e) {
e.preventDefault();
e.stopPropagation();
if (menu.value && mobile.value) {
isEditingInput.value = true;
} else {
menu.value = true;
}
}
function onCancel() {
emit('cancel');
menu.value = false;
isEditingInput.value = false;
}
function onSave(value) {
emit('save', value);
menu.value = false;
}
function onUpdateDisplayModel(value) {
if (value != null) return;
model.value = emptyModelValue();
}
function onBlur(e) {
if (props.updateOn.includes('blur')) {
onUserInput(e.target);
}
blur();
// When in mobile mode and editing is done (due to keyboard dismissal), close the menu
if (mobile.value && isEditingInput.value && !isFocused.value) {
menu.value = false;
isEditingInput.value = false;
}
}
function onUserInput(_ref2) {
let {
value
} = _ref2;
if (!value.trim()) {
model.value = emptyModelValue();
} else if (!props.multiple) {
if (isValid(value)) {
model.value = parseDate(value);
}
} else {
const parts = value.trim().split(/\D+-\D+|[^\d\-/.]+/);
if (parts.every(isValid)) {
if (props.multiple === 'range') {
model.value = getRange(parts);
} else {
model.value = parts.map(parseDate);
}
}
}
}
function getRange(inputDates) {
const [start, stop] = inputDates.map(parseDate).toSorted((a, b) => adapter.isAfter(a, b) ? 1 : -1);
const diff = adapter.getDiff(stop ?? start, start, 'days');
return [start, ...createRange(diff, 1).map(i => adapter.addDays(start, i))];
}
useRender(() => {
const confirmEditProps = VConfirmEdit.filterProps(props);
const datePickerProps = VDatePicker.filterProps(omit(props, ['active', 'location', 'rounded']));
const textFieldProps = VTextField.filterProps(omit(props, ['placeholder']));
return _createVNode(VTextField, _mergeProps({
"ref": vTextFieldRef
}, textFieldProps, {
"class": props.class,
"style": props.style,
"modelValue": display.value,
"inputmode": inputmode.value,
"placeholder": props.placeholder ?? parserFormat.value,
"readonly": isReadonly.value,
"onKeydown": isInteractive.value ? onKeydown : undefined,
"focused": menu.value || isFocused.value,
"onFocus": focus,
"onBlur": onBlur,
"validationValue": model.value,
"onClick:control": isInteractive.value ? onClick : undefined,
"onClick:prepend": isInteractive.value ? onClick : undefined,
"onUpdate:modelValue": onUpdateDisplayModel
}), {
...slots,
default: () => _createElementVNode(_Fragment, null, [_createVNode(VMenu, {
"modelValue": menu.value,
"onUpdate:modelValue": $event => menu.value = $event,
"activator": "parent",
"min-width": "0",
"eager": isFocused.value,
"location": props.location,
"closeOnContentClick": false,
"openOnClick": false
}, {
default: () => [_createVNode(VConfirmEdit, _mergeProps(confirmEditProps, {
"modelValue": model.value,
"onUpdate:modelValue": $event => model.value = $event,
"disabled": disabledActions.value,
"onSave": onSave,
"onCancel": onCancel
}), {
default: _ref3 => {
let {
actions,
model: proxyModel,
save,
cancel,
isPristine
} = _ref3;
function onUpdateModel(value) {
if (!props.hideActions) {
proxyModel.value = value;
} else {
model.value = value;
if (!props.multiple) {
menu.value = false;
}
}
emit('save', value);
disabledActions.value = [];
}
return _createVNode(VDatePicker, _mergeProps(datePickerProps, {
"modelValue": props.hideActions ? model.value : proxyModel.value,
"onUpdate:modelValue": value => onUpdateModel(value),
"onMousedown": e => e.preventDefault()
}), {
actions: !props.hideActions ? () => slots.actions?.({
save,
cancel,
isPristine
}) ?? actions() : undefined
});
}
})]
}), slots.default?.()])
});
});
return forwardRefs({}, vTextFieldRef);
}
});
//# sourceMappingURL=VDateInput.js.map