Skip to content

Commit

Permalink
fix(VDatePicker): recursive update when using hide-actions (#17914)
Browse files Browse the repository at this point in the history
fixes #17867 
fixes #17872

Co-authored-by: John Leider <john@vuetifyjs.com>
  • Loading branch information
nekosaur and johnleider committed Aug 8, 2023
1 parent bf76c7a commit 6f30e56
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 12 deletions.
13 changes: 13 additions & 0 deletions packages/vuetify/src/labs/VDateInput/composables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,25 @@ export function createDateInput (props: DateInputProps, isRange: boolean) {
return adapter.isValid(date) ? date : fallback
}

function isEqual (model: readonly any[], comparing: readonly any[]) {
if (model.length !== comparing.length) return false

for (let i = 0; i < model.length; i++) {
if (comparing[i] && !adapter.isEqual(model[i], comparing[i])) {
return false
}
}

return true
}

return {
model,
adapter,
inputMode,
viewMode,
displayDate,
parseKeyboardDate,
isEqual,
}
}
48 changes: 37 additions & 11 deletions packages/vuetify/src/labs/VDatePicker/VDatePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ export const makeVDatePickerProps = propsFactory({
type: String,
default: '$vuetify.datePicker.input.placeholder',
},
inputPlaceholder: {
type: String,
default: 'dd/mm/yyyy',
},
header: {
type: String,
default: '$vuetify.datePicker.header',
Expand Down Expand Up @@ -79,25 +83,43 @@ export const VDatePicker = genericComponent<VDatePickerSlots>()({
const adapter = useDate()
const { t } = useLocale()

const { model, displayDate, viewMode, inputMode } = createDatePicker(props)
const { model, displayDate, viewMode, inputMode, isEqual } = createDatePicker(props)

const isReversing = shallowRef(false)

const inputModel = computed(() => model.value.length ? adapter.format(model.value[0], 'keyboardDate') : '')
const inputModel = ref(model.value.map(date => adapter.format(date, 'keyboardDate')))
const temporaryModel = ref(model.value)
const title = computed(() => t(props.title))
const header = computed(() => model.value.length ? adapter.format(model.value[0], 'normalDateWithWeekday') : t(props.header))
const headerIcon = computed(() => inputMode.value === 'calendar' ? props.keyboardIcon : props.calendarIcon)
const headerTransition = computed(() => `date-picker-header${isReversing.value ? '-reverse' : ''}-transition`)

watch(inputModel, () => {
function updateFromInput (input: string, index: number) {
const { isValid, date } = adapter

model.value = isValid(inputModel.value) ? [date(inputModel.value)] : []
if (isValid(input)) {
const newModel = model.value.slice()
newModel[index] = date(input)

if (props.hideActions) {
model.value = newModel
} else {
temporaryModel.value = newModel
}
}
}

watch(model, val => {
if (!isEqual(val, temporaryModel.value)) {
temporaryModel.value = val
}

inputModel.value = val.map(date => adapter.format(date, 'keyboardDate'))
})

watch(model, (val, oldVal) => {
if (props.hideActions) {
emit('update:modelValue', val)
watch(temporaryModel, (val, oldVal) => {
if (props.hideActions && !isEqual(val, model.value)) {
model.value = val
}

if (val[0] && oldVal[0]) {
Expand All @@ -108,10 +130,13 @@ export const VDatePicker = genericComponent<VDatePickerSlots>()({
function onClickCancel () {
emit('click:cancel')
}

function onClickSave () {
emit('click:save')
emit('update:modelValue', model.value)

model.value = temporaryModel.value
}

function onClickAppend () {
inputMode.value = inputMode.value === 'calendar' ? 'keyboard' : 'calendar'
}
Expand Down Expand Up @@ -159,7 +184,7 @@ export const VDatePicker = genericComponent<VDatePickerSlots>()({
<VDatePickerMonth
key="date-picker-month"
{ ...datePickerMonthProps }
v-model={ model.value }
v-model={ temporaryModel.value }
v-model:displayDate={ displayDate.value }
/>
) : (
Expand All @@ -175,9 +200,10 @@ export const VDatePicker = genericComponent<VDatePickerSlots>()({
) : (
<div class="v-date-picker__input">
<VTextField
v-model={ inputModel.value }
modelValue={ inputModel.value[0] }
onUpdate:modelValue={ v => updateFromInput(v, 0) }
label={ t(props.inputText) }
placeholder="dd/mm/yyyy"
placeholder={ props.inputPlaceholder }
/>
</div>
),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/// <reference types="../../../../types/cypress" />

import { ref } from 'vue'
import { VDatePicker } from '../VDatePicker'
import { Application } from '../../../../cypress/templates'

describe('VDatePicker', () => {
it('should update model directly when using hide-actions prop', () => {
const model = ref(new Date(2023, 0, 1))
cy.mount(() => (
<Application>
<VDatePicker v-model={model.value} hideActions />
</Application>
))
.get('div[data-v-date="2023-1-2"]')
.click()
.vue()
.then(wrapper => {
const datePicker = wrapper.getComponent(VDatePicker)
const emitted = datePicker.emitted('update:modelValue')
expect(emitted).to.have.length(1)
})
})

it('should not update model until clicking ok', () => {
const model = ref(new Date(2023, 0, 1))
cy.mount(() => (
<Application>
<VDatePicker v-model={model.value} />
</Application>
))
.get('div[data-v-date="2023-1-2"]')
.click()
.vue()
.then(wrapper => {
const datePicker = wrapper.getComponent(VDatePicker)
const emitted = datePicker.emitted('update:modelValue')
expect(emitted).to.be.undefined
})
.get('.v-picker__actions')
.contains('OK')
.click()
.vue()
.then(wrapper => {
const datePicker = wrapper.getComponent(VDatePicker)
const emitted = datePicker.emitted('update:modelValue')
expect(emitted).to.have.length(1)
})
})
})
3 changes: 2 additions & 1 deletion packages/vuetify/src/labs/VDatePicker/composables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function createDatePicker (props: DateProps) {
})

// TODO: This composable should probably not live in DateInput
const { model, displayDate, viewMode, inputMode } = createDateInput(props, !!props.multiple)
const { model, displayDate, viewMode, inputMode, isEqual } = createDateInput(props, !!props.multiple)

return {
hoverDate,
Expand All @@ -48,6 +48,7 @@ export function createDatePicker (props: DateProps) {
displayDate,
viewMode,
inputMode,
isEqual,
}
}

Expand Down

0 comments on commit 6f30e56

Please sign in to comment.