From e092dad739e77b9a02f7e9e4848b129cd18a7655 Mon Sep 17 00:00:00 2001 From: Jasenko Karovic Date: Fri, 8 Sep 2023 19:39:34 +0200 Subject: [PATCH] refactor: Group common props into `config`, add option to disable `stopPropagation` event (resolves #556) --- src/VueDatePicker/VueDatePicker.vue | 4 ++-- .../components/Common/SelectionOverlay.vue | 11 ++++++---- .../components/DatePicker/DpCalendar.vue | 21 +++++++++++++++---- .../components/DatePicker/DpHeader.vue | 1 + .../components/DatepickerInput.vue | 19 +++++++++++------ .../components/DatepickerMenu.vue | 16 +++++++------- .../components/MonthPicker/MonthPicker.vue | 5 ++++- .../components/MonthPicker/month-picker.ts | 3 ++- .../components/TimePicker/TimeInput.vue | 1 + .../components/TimePicker/TimePicker.vue | 8 +++++-- .../components/YearPicker/YearPicker.vue | 5 ++++- src/VueDatePicker/composables/defaults.ts | 4 ++++ src/VueDatePicker/interfaces.ts | 7 +++++++ src/VueDatePicker/props.ts | 2 ++ src/VueDatePicker/utils/defaults.ts | 11 ++++++++++ src/VueDatePicker/utils/util.ts | 11 +++++++++- tests/unit/behaviour.spec.ts | 6 +++--- 17 files changed, 101 insertions(+), 34 deletions(-) diff --git a/src/VueDatePicker/VueDatePicker.vue b/src/VueDatePicker/VueDatePicker.vue index 6dc04561f..d04b33fc6 100644 --- a/src/VueDatePicker/VueDatePicker.vue +++ b/src/VueDatePicker/VueDatePicker.vue @@ -148,7 +148,7 @@ const { setMenuFocused, setShiftKey } = useState(); const { clearArrowNav } = useArrowNavigation(); const { mapDatesArrToMap, validateDate, isValidTime } = useValidation(props); - const { defaultedTransitions, defaultedTextInput, defaultedInline } = useDefaults(props); + const { defaultedTransitions, defaultedTextInput, defaultedInline, defaultedConfig } = useDefaults(props); const { menuTransition, showTransition } = useTransitions(defaultedTransitions); onMounted(() => { @@ -237,7 +237,7 @@ */ const onScroll = (): void => { if (isOpen.value) { - if (props.closeOnScroll) { + if (props.closeOnScroll || defaultedConfig.value.closeOnScroll) { closeMenu(); } else { setMenuPosition(); diff --git a/src/VueDatePicker/components/Common/SelectionOverlay.vue b/src/VueDatePicker/components/Common/SelectionOverlay.vue index 986d51898..23ff4a7d9 100644 --- a/src/VueDatePicker/components/Common/SelectionOverlay.vue +++ b/src/VueDatePicker/components/Common/SelectionOverlay.vue @@ -67,10 +67,10 @@ diff --git a/src/VueDatePicker/components/DatePicker/DpHeader.vue b/src/VueDatePicker/components/DatePicker/DpHeader.vue index 31282bb2a..eb6044c62 100644 --- a/src/VueDatePicker/components/DatePicker/DpHeader.vue +++ b/src/VueDatePicker/components/DatePicker/DpHeader.vue @@ -46,6 +46,7 @@ :hide-navigation="hideNavigation" :is-last="autoApply && !keepActionRow" :skip-button-ref="false" + :config="config" :type="type.type" :header-refs="[]" :esc-close="escClose" diff --git a/src/VueDatePicker/components/DatepickerInput.vue b/src/VueDatePicker/components/DatepickerInput.vue index 3266e2a43..b8647d7d8 100644 --- a/src/VueDatePicker/components/DatepickerInput.vue +++ b/src/VueDatePicker/components/DatepickerInput.vue @@ -59,7 +59,7 @@ v-if="clearable && !$slots['clear-icon'] && inputValue && !disabled && !readonly" class="dp__clear_icon dp__input_icons" data-test="clear-icon" - @click.stop.prevent="onClear" + @click.prevent="onClear($event)" /> @@ -76,6 +76,7 @@ import type { PropType } from 'vue'; import type { DynamicClass } from '@/interfaces'; + import { checkStopPropagation } from '@/utils/util'; defineOptions({ compatConfig: { @@ -104,8 +105,14 @@ ...AllProps, }); - const { defaultedTextInput, defaultedAriaLabels, defaultedInline, getDefaultPattern, getDefaultStartTime } = - useDefaults(props); + const { + defaultedTextInput, + defaultedAriaLabels, + defaultedInline, + defaultedConfig, + getDefaultPattern, + getDefaultStartTime, + } = useDefaults(props); const parsedDate = ref(); const inputRef = ref(null); @@ -226,8 +233,7 @@ const handleOpen = (ev: KeyboardEvent | MouseEvent) => { ev.preventDefault(); - ev.stopImmediatePropagation(); - ev.stopPropagation(); + checkStopPropagation(ev, defaultedConfig.value, true); if ( defaultedTextInput.value.enabled && defaultedTextInput.value.openMenu && @@ -253,7 +259,8 @@ } }; - const onClear = () => { + const onClear = (ev?: Event) => { + checkStopPropagation(ev, defaultedConfig.value, true); emit('clear'); }; diff --git a/src/VueDatePicker/components/DatepickerMenu.vue b/src/VueDatePicker/components/DatepickerMenu.vue index ea966505b..d3d09965a 100644 --- a/src/VueDatePicker/components/DatepickerMenu.vue +++ b/src/VueDatePicker/components/DatepickerMenu.vue @@ -109,7 +109,7 @@ import ActionRow from '@/components/ActionRow.vue'; import { mapSlots, useArrowNavigation, useState, useFlow, useDefaults } from '@/composables'; - import { unrefElement } from '@/utils/util'; + import { checkStopPropagation, unrefElement } from '@/utils/util'; import { AllProps } from '@/props'; import MonthPicker from '@/components/MonthPicker/MonthPicker.vue'; @@ -172,7 +172,7 @@ const { setMenuFocused, setShiftKey, control } = useState(); const slots = useSlots(); - const { defaultedTextInput, defaultedInline } = useDefaults(props); + const { defaultedTextInput, defaultedInline, defaultedConfig } = useDefaults(props); const calendarWrapperRef = ref(null); const calendarWidth = ref(0); @@ -194,11 +194,10 @@ } if (menu) { const stopDefault = (event: Event) => { - if (props.allowPreventDefault) { + if (props.allowPreventDefault || defaultedConfig.value.allowPreventDefault) { event.preventDefault(); } - event.stopImmediatePropagation(); - event.stopPropagation(); + checkStopPropagation(event, defaultedConfig.value, true); }; menu.addEventListener('pointerdown', stopDefault); menu.addEventListener('mousedown', stopDefault); @@ -266,9 +265,8 @@ }), ); - const handleDpMenuClick = (e: Event) => { - e.stopPropagation(); - e.stopImmediatePropagation(); + const handleDpMenuClick = (ev: Event) => { + checkStopPropagation(ev, defaultedConfig.value, true); }; const handleEsc = (): void => { @@ -297,7 +295,7 @@ if (!props.disableMonthYearSelect && ev.code === 'Tab') { if ((ev.target as HTMLElement).classList.contains('dp__menu') && control.value.shiftKeyInMenu) { ev.preventDefault(); - ev.stopImmediatePropagation(); + checkStopPropagation(ev, defaultedConfig.value, true); emit('close-picker'); } } diff --git a/src/VueDatePicker/components/MonthPicker/MonthPicker.vue b/src/VueDatePicker/components/MonthPicker/MonthPicker.vue index 7759666e1..7de78f6dc 100644 --- a/src/VueDatePicker/components/MonthPicker/MonthPicker.vue +++ b/src/VueDatePicker/components/MonthPicker/MonthPicker.vue @@ -19,7 +19,8 @@ :arrow-navigation="arrowNavigation" :is-last="autoApply && !keepActionRow" :esc-close="escClose" - :height="modeHeight" + :height="modeHeight !== 255 ? modeHeight : defaultedConfig.modeHeight" + :config="config" @selected="selectMonth($event, instance)" @hover-value="setHoverDate($event, instance)" use-relative @@ -65,6 +66,7 @@ :items="groupedYears(instance)" :text-input="textInput" :esc-close="escClose" + :config="config" @toggle="toggleYearPicker(instance)" @selected="handleYearSelect($event, instance)" :is-last="autoApply && !keepActionRow" @@ -125,6 +127,7 @@ defaultedMultiCalendars, defaultedAriaLabels, defaultedTransitions, + defaultedConfig, setHoverDate, selectMonth, selectYear, diff --git a/src/VueDatePicker/components/MonthPicker/month-picker.ts b/src/VueDatePicker/components/MonthPicker/month-picker.ts index 530d2532c..b858e0932 100644 --- a/src/VueDatePicker/components/MonthPicker/month-picker.ts +++ b/src/VueDatePicker/components/MonthPicker/month-picker.ts @@ -20,7 +20,7 @@ import type { IDefaultSelect, OverlayGridItem, VueEmit } from '@/interfaces'; import type { PickerBasePropsType } from '@/props'; export const useMonthPicker = (props: PickerBasePropsType, emit: VueEmit) => { - const { defaultedMultiCalendars, defaultedAriaLabels, defaultedTransitions } = useDefaults(props); + const { defaultedMultiCalendars, defaultedAriaLabels, defaultedTransitions, defaultedConfig } = useDefaults(props); const { modelValue, year, month: instanceMonth, calendars } = useModel(props, emit); const months = computed(() => getMonths(props.formatLocale, props.locale, props.monthNameFormat)); @@ -190,6 +190,7 @@ export const useMonthPicker = (props: PickerBasePropsType, emit: VueEmit) => { defaultedMultiCalendars, defaultedAriaLabels, defaultedTransitions, + defaultedConfig, setHoverDate, selectMonth, selectYear, diff --git a/src/VueDatePicker/components/TimePicker/TimeInput.vue b/src/VueDatePicker/components/TimePicker/TimeInput.vue index bfb765611..4bf9807e8 100644 --- a/src/VueDatePicker/components/TimePicker/TimeInput.vue +++ b/src/VueDatePicker/components/TimePicker/TimeInput.vue @@ -109,6 +109,7 @@ :esc-close="escClose" :type="timeInput.type" :text-input="textInput" + :config="config" :arrow-navigation="arrowNavigation" @selected="handleTimeFromOverlay(timeInput.type, $event)" @toggle="toggleOverlay(timeInput.type)" diff --git a/src/VueDatePicker/components/TimePicker/TimePicker.vue b/src/VueDatePicker/components/TimePicker/TimePicker.vue index 34da0cdf2..aa7a31ab8 100644 --- a/src/VueDatePicker/components/TimePicker/TimePicker.vue +++ b/src/VueDatePicker/components/TimePicker/TimePicker.vue @@ -24,7 +24,11 @@ 'dp--overlay-absolute': !props.timePicker && !timePickerInline, 'dp--overlay-relative': props.timePicker, }" - :style="timePicker ? { height: `${modeHeight}px` } : undefined" + :style=" + timePicker + ? { height: `${modeHeight !== 255 ? modeHeight : defaultedConfig.modeHeight}px` } + : undefined + " ref="overlayRef" :tabindex="timePickerInline ? undefined : 0" > @@ -138,7 +142,7 @@ const { buildMatrix, setTimePicker } = useArrowNavigation(); const slots = useSlots(); - const { defaultedTransitions, defaultedAriaLabels, defaultedTextInput } = useDefaults(props); + const { defaultedTransitions, defaultedAriaLabels, defaultedTextInput, defaultedConfig } = useDefaults(props); const { transitionName, showTransition } = useTransitions(defaultedTransitions); const { hideNavigationButtons } = useCommon(); diff --git a/src/VueDatePicker/components/YearPicker/YearPicker.vue b/src/VueDatePicker/components/YearPicker/YearPicker.vue index e2e503369..a360c5a04 100644 --- a/src/VueDatePicker/components/YearPicker/YearPicker.vue +++ b/src/VueDatePicker/components/YearPicker/YearPicker.vue @@ -13,7 +13,8 @@ diff --git a/src/VueDatePicker/composables/defaults.ts b/src/VueDatePicker/composables/defaults.ts index 792efb16d..f4cc5d053 100644 --- a/src/VueDatePicker/composables/defaults.ts +++ b/src/VueDatePicker/composables/defaults.ts @@ -6,6 +6,7 @@ import { defaultPreviewFormat, defaultTransitions, getDefaultActionRowData, + getDefaultConfig, getDefaultFilters, getDefaultInlineOptions, getDefaultTextInputOptions, @@ -62,6 +63,8 @@ export const useDefaults = (props: AllPropsType | PickerBasePropsType) => { const defaultedInline = computed(() => getDefaultInlineOptions(props.inline)); + const defaultedConfig = computed(() => getDefaultConfig(props.config)); + return { defaultedTransitions, defaultedMultiCalendars, @@ -72,6 +75,7 @@ export const useDefaults = (props: AllPropsType | PickerBasePropsType) => { defaultedPreviewFormat, defaultedTextInput, defaultedInline, + defaultedConfig, getDefaultPattern, getDefaultStartTime, }; diff --git a/src/VueDatePicker/interfaces.ts b/src/VueDatePicker/interfaces.ts index 12e2cda14..1c2270aaa 100644 --- a/src/VueDatePicker/interfaces.ts +++ b/src/VueDatePicker/interfaces.ts @@ -281,3 +281,10 @@ export interface InlineOptions { export type InlineProp = boolean | { input?: boolean }; export type DisabledTimesArrProp = (ind: number, hours?: number) => TimeValuesInv; + +export interface Config { + allowStopPropagation: boolean; + closeOnScroll: boolean; + modeHeight: number; + allowPreventDefault: boolean; +} diff --git a/src/VueDatePicker/props.ts b/src/VueDatePicker/props.ts index 2a65bde7e..ee72bb624 100644 --- a/src/VueDatePicker/props.ts +++ b/src/VueDatePicker/props.ts @@ -26,6 +26,7 @@ import type { PresetDate, InlineProp, DisabledTimeArrProp, + Config, } from '@/interfaces'; export const AllProps = { @@ -168,6 +169,7 @@ export const AllProps = { showLastInRange: { type: Boolean as PropType, default: true }, timePickerInline: { type: Boolean as PropType, default: false }, calendar: { type: Function as PropType<(month: ICalendarDate[]) => ICalendarDate[]>, default: null }, + config: { type: Object as PropType>, default: undefined }, }; export const PickerBaseProps = { diff --git a/src/VueDatePicker/utils/defaults.ts b/src/VueDatePicker/utils/defaults.ts index c8c66bc0c..bdce89b15 100644 --- a/src/VueDatePicker/utils/defaults.ts +++ b/src/VueDatePicker/utils/defaults.ts @@ -11,6 +11,7 @@ import type { TextInputProp, InlineProp, InlineOptions, + Config, } from '@/interfaces'; export const mergeDefaultTransitions = (conf: Partial): Transition => ({ @@ -132,3 +133,13 @@ export const getDefaultInlineOptions = (inline: InlineProp): InlineOptions => { ...defaultOptions, }; }; + +export const getDefaultConfig = (config?: Partial): Config => { + const defaultConfig = { + allowStopPropagation: true, + closeOnScroll: false, + modeHeight: 255, + allowPreventDefault: false, + }; + return { ...defaultConfig, ...(config ?? {}) }; +}; diff --git a/src/VueDatePicker/utils/util.ts b/src/VueDatePicker/utils/util.ts index 88a1c1cab..ebb533357 100644 --- a/src/VueDatePicker/utils/util.ts +++ b/src/VueDatePicker/utils/util.ts @@ -1,7 +1,7 @@ import { unref } from 'vue'; import { format } from 'date-fns'; -import type { IDefaultSelect, IMarker, MaybeElementRef, ModelValue, OverlayGridItem } from '@/interfaces'; +import type { Config, IDefaultSelect, IMarker, MaybeElementRef, ModelValue, OverlayGridItem } from '@/interfaces'; import type { ComponentPublicInstance } from 'vue'; export const getArrayInArray = (list: T[], increment = 3): T[][] => { @@ -223,3 +223,12 @@ export const groupListAndMap = ( }); }); }; + +export const checkStopPropagation = (ev: Event | undefined, config: Config, immediate = false) => { + if (ev && config.allowStopPropagation) { + if (immediate) { + ev.stopImmediatePropagation(); + } + ev.stopPropagation(); + } +}; diff --git a/tests/unit/behaviour.spec.ts b/tests/unit/behaviour.spec.ts index e96852d3e..73555e16f 100644 --- a/tests/unit/behaviour.spec.ts +++ b/tests/unit/behaviour.spec.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from 'vitest'; +import { describe, expect, it, vi } from 'vitest'; import { add, addDays, addHours, addMinutes, addMonths, getHours, getMinutes, getMonth, getYear, set } from 'date-fns'; import { utcToZonedTime } from 'date-fns-tz/esm'; @@ -215,7 +215,7 @@ describe('It should validate various picker scenarios', () => { dp.unmount(); }); - it('Should correctly display months in multi-calendars based on the given range', async () => { + it('Should correctly display months in multi-calendars based on the given range (#540)', async () => { const today = new Date(); const sameViewRange = [today, addMonths(today, 1)]; const diffViewRange = [today, addMonths(today, 3)]; @@ -243,7 +243,7 @@ describe('It should validate various picker scenarios', () => { dp.unmount(); }); - it('Should not break flow on changing months and years when calendar is first step', async () => { + it('Should not break flow on changing months and years when calendar is first step (#553)', async () => { const flow = [FlowStep.calendar, FlowStep.time]; const dp = await openMenu({ flow }); const today = resetDateTime(new Date());