Skip to content
This repository was archived by the owner on Apr 17, 2022. It is now read-only.

Commit bd828d2

Browse files
committed
feat: Add autoPosition prop
- Some adjustments in dp menu positioning
1 parent d689faa commit bd828d2

File tree

3 files changed

+54
-47
lines changed

3 files changed

+54
-47
lines changed

src/Vue3DatePicker/Vue3DatePicker.vue

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
:class="theme"
3030
:uid="uid"
3131
v-click-outside-directive.dp__menu="closeMenu"
32+
:open-on-top="openOnTop"
3233
:enable-time-picker="enableTimePicker"
3334
:week-numbers="weekNumbers"
3435
:week-start="weekStart"
@@ -60,7 +61,7 @@
6061
v-model:rangeModelValue="rangeModelValue"
6162
@closePicker="closeMenu"
6263
@selectDate="selectDate"
63-
@openToTop="recalculatePosition"
64+
@dpOpen="recalculatePosition"
6465
v-if="isOpen"
6566
/>
6667
</teleport>
@@ -121,7 +122,7 @@
121122
hideInputIcon: { type: Boolean as PropType<boolean>, default: false },
122123
state: { type: Boolean as PropType<boolean>, default: null },
123124
clearable: { type: Boolean as PropType<boolean>, default: true },
124-
closeOnScroll: { type: Boolean as PropType<boolean>, default: true },
125+
closeOnScroll: { type: Boolean as PropType<boolean>, default: false },
125126
autoApply: { type: Boolean as PropType<boolean>, default: false },
126127
filters: { type: Object as PropType<IDateFilter>, default: () => ({}) },
127128
disableMonthYearSelect: { type: Boolean as PropType<boolean>, default: false },
@@ -130,13 +131,15 @@
130131
inline: { type: Boolean as PropType<boolean>, default: false },
131132
selectText: { type: String as PropType<string>, default: 'Select' },
132133
cancelText: { type: String as PropType<string>, default: 'Cancel' },
134+
autoPosition: { type: Boolean as PropType<boolean>, default: true },
133135
},
134136
setup(props: RDatepickerProps, { emit }) {
135137
const isOpen = ref(false);
136138
const menuPosition = ref({ top: '0', left: '0', transform: 'none' });
137139
const internalValue = ref<string | string[]>('');
138140
const singleModelValue = ref();
139141
const rangeModelValue = ref();
142+
const openOnTop = ref(false);
140143
const valueCleared = ref(false);
141144
const modelValue = toRef(props, 'modelValue');
142145
@@ -156,20 +159,16 @@
156159
157160
onMounted(() => {
158161
mapExternalToInternalValue();
159-
if (props.closeOnScroll) {
160-
window.addEventListener('scroll', closeMenu);
161-
}
162-
window.addEventListener('resize', setMenuPosition);
162+
window.addEventListener('scroll', onScroll);
163+
window.addEventListener('resize', onResize);
163164
if (props.inline) {
164165
isOpen.value = true;
165166
}
166167
});
167168
168169
onUnmounted(() => {
169-
if (props.closeOnScroll) {
170-
window.removeEventListener('scroll', closeMenu);
171-
}
172-
window.removeEventListener('resize', setMenuPosition);
170+
window.removeEventListener('scroll', onScroll);
171+
window.removeEventListener('resize', onResize);
173172
});
174173
175174
const wrapperClass = computed(
@@ -279,7 +278,21 @@
279278
closeMenu();
280279
};
281280
282-
const setMenuPosition = (): void => {
281+
const onScroll = (): void => {
282+
if (props.closeOnScroll) {
283+
closeMenu();
284+
} else if (props.autoPosition) {
285+
setMenuPosition();
286+
} else {
287+
window.removeEventListener('scroll', onScroll);
288+
}
289+
};
290+
291+
const onResize = (): void => {
292+
setMenuPosition();
293+
};
294+
295+
const setMenuPosition = (recalculate = true): void => {
283296
const el = document.getElementById(`dp__input_${props.uid}`);
284297
if (el) {
285298
const { left, width, height } = el.getBoundingClientRect();
@@ -298,13 +311,31 @@
298311
position.transform = `translateX(-50%)`;
299312
}
300313
menuPosition.value = position;
314+
if (recalculate) {
315+
recalculatePosition();
316+
}
301317
}
302318
};
303319
304-
const recalculatePosition = (height: number) => {
320+
const recalculatePosition = (): void => {
305321
const el = document.getElementById(`dp__input_${props.uid}`);
306322
if (el) {
307-
menuPosition.value.top = `${el.offsetTop - height - 12}px`;
323+
const { height: inputHeight, top } = el.getBoundingClientRect();
324+
const fullHeight = window.innerHeight;
325+
const freeSpace = fullHeight - top - inputHeight;
326+
const menuEl = document.getElementById(`dp__menu_${props.uid}`);
327+
328+
if (menuEl) {
329+
const { height } = menuEl.getBoundingClientRect();
330+
const menuHeight = height + inputHeight;
331+
if (menuHeight > freeSpace) {
332+
menuPosition.value.top = `${el.offsetTop - height - 12}px`;
333+
openOnTop.value = true;
334+
} else {
335+
setMenuPosition(false);
336+
openOnTop.value = false;
337+
}
338+
}
308339
}
309340
};
310341
@@ -344,12 +375,13 @@
344375
internalValue,
345376
isOpen,
346377
isSingle,
378+
theme,
379+
wrapperClass,
380+
openOnTop,
347381
clearValue,
348382
openMenu,
349383
closeMenu,
350384
selectDate,
351-
theme,
352-
wrapperClass,
353385
recalculatePosition,
354386
};
355387
},

src/Vue3DatePicker/components/DatepickerMenu.vue

Lines changed: 5 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
</template>
4141

4242
<script lang="ts">
43-
import { computed, defineComponent, onMounted, PropType, ref } from 'vue';
43+
import { computed, defineComponent, onMounted, PropType, ref, nextTick } from 'vue';
4444
import Calendar from './Calendar.vue';
4545
4646
import { DatepickerMenuProps, IMonth, DynamicClass, FormatOptions, IDateFilter, ITimeRange } from '../interfaces';
@@ -50,7 +50,7 @@
5050
export default defineComponent({
5151
name: 'DatepickerMenu',
5252
components: { Calendar },
53-
emits: ['update:singleModelValue', 'update:rangeModelValue', 'closePicker', 'selectDate', 'openToTop'],
53+
emits: ['update:singleModelValue', 'update:rangeModelValue', 'closePicker', 'selectDate', 'dpOpen'],
5454
props: {
5555
uid: { type: String as PropType<string>, default: 'dp' },
5656
weekNumbers: { type: Boolean as PropType<boolean>, default: false },
@@ -85,17 +85,17 @@
8585
minTime: { type: Object as PropType<ITimeRange>, default: () => ({}) },
8686
maxTime: { type: Object as PropType<ITimeRange>, default: () => ({}) },
8787
inline: { type: Boolean as PropType<boolean>, default: false },
88+
openOnTop: { type: Boolean as PropType<boolean>, default: false },
8889
},
8990
setup(props: DatepickerMenuProps, { emit }) {
9091
const assignedFilter = ref({});
91-
const openOnTop = ref(false);
9292
9393
onMounted(() => {
94-
recalculatePosition();
9594
assignedFilter.value = Object.assign(
9695
{ months: [], years: [], times: { hours: [], minutes: [] } },
9796
props.filters,
9897
);
98+
nextTick(() => emit('dpOpen'));
9999
});
100100
101101
const mappedMonths = computed((): IMonth[] =>
@@ -109,38 +109,11 @@
109109
}),
110110
);
111111
112-
const arrowClass = computed(() => (!openOnTop.value ? 'dp__arrow_top' : 'dp__arrow_bottom'));
112+
const arrowClass = computed(() => (!props.openOnTop ? 'dp__arrow_top' : 'dp__arrow_bottom'));
113113
114114
const rangeDate = useBindValue(props, emit, 'rangeModelValue');
115115
const singleDate = useBindValue(props, emit, 'singleModelValue');
116116
117-
/**
118-
* If input is near the bottom position where menu can't fit, it will recalculate and
119-
* place it on top
120-
*/
121-
const recalculatePosition = (): void => {
122-
const el = document.getElementById(`dp__input_${props.uid}`);
123-
if (el) {
124-
const { height: inputHeight } = el.getBoundingClientRect();
125-
const getHeight = (): number => {
126-
const { documentElement, body } = document;
127-
const calcEl = documentElement.offsetHeight ? documentElement : body;
128-
return Math.max(calcEl.scrollHeight, calcEl.offsetHeight);
129-
};
130-
const fullHeight = getHeight();
131-
const freeSpace = fullHeight - el.offsetTop - inputHeight;
132-
const menuEl = document.getElementById(`dp__menu_${props.uid}`);
133-
if (menuEl) {
134-
const { height } = menuEl.getBoundingClientRect();
135-
const menuHeight = height + inputHeight;
136-
if (menuHeight > freeSpace) {
137-
emit('openToTop', menuHeight);
138-
openOnTop.value = true;
139-
}
140-
}
141-
}
142-
};
143-
144117
const firstDate = computed((): Date => {
145118
if (props.range) {
146119
return props.rangeModelValue && props.rangeModelValue[0]

src/Vue3DatePicker/interfaces.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ export interface RDatepickerProps {
6666
selectText: string;
6767
cancelText: string;
6868
weekNumName: string;
69+
autoPosition: boolean;
6970
}
7071

7172
export interface DatepickerInputProps {
@@ -121,6 +122,7 @@ export interface DatepickerMenuProps {
121122
minTime: ITimeRange;
122123
maxTime: ITimeRange;
123124
inline: boolean;
125+
openOnTop: boolean;
124126
}
125127

126128
export interface CalendarProps {

0 commit comments

Comments
 (0)