Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Menu position fixed when scroll #253

Closed
StevenHeven opened this issue Jan 25, 2023 · 18 comments
Closed

Menu position fixed when scroll #253

StevenHeven opened this issue Jan 25, 2023 · 18 comments
Labels
bug Something isn't working documentation Improvements or additions to documentation

Comments

@StevenHeven
Copy link

Describe the bug
Don't understand why, and after multiple searches, when I'm scrolling, the calendar overlay (.dp__menu) stay at fixed position and no attached at the field (GIF illustration below). I check if we have overwrite style can do this, but nothing found.

To Reproduce
My DatePicker component in TS :

<script setup lang="ts">
import { InputField, Icon } from '../@atoms';
import Datepicker from '@vuepic/vue-datepicker';
import '@vuepic/vue-datepicker/dist/main.css';
import { InputSlotsProps } from '../@atoms/aInputField.vue';
import { endOfMonth, endOfYear, startOfMonth, startOfYear, subMonths, startOfISOWeek, endOfISOWeek, subWeeks, startOfDay } from 'date-fns';

export interface DatePickerProps {
  label: string;
  onlyTimePicker?: boolean;
  needTimePicker?: boolean;
  range?: boolean;
  locale?: string;
  required?: boolean;
  valueDate?: Date | number;
  minDate?: Date | string;
  maxDate?: Date | string;
  minRange?: number;
  maxRange?: number;
}

const props = withDefaults(defineProps<DatePickerProps>(), {
  needTimePicker: true,
  locale: 'fr-FR'
});

const emit = defineEmits<{
  (e: 'update:modelValue', value: Date | number | undefined): Date;
}>();

const date = ref<Date | number>();
const state = ref<boolean>();
const now = new Date();
const presetRanges = ref([
  { label: 'This week', range: [startOfISOWeek(now), endOfISOWeek(now)] },
  { label: 'Last week', range: [startOfISOWeek(subWeeks(now, 1)), endOfISOWeek(subWeeks(now, 1))] },
  { label: 'This month', range: [startOfMonth(now), endOfMonth(now)] },
  {
    label: 'Last month',
    range: [startOfMonth(subMonths(now, 1)), endOfMonth(subMonths(now, 1))]
  },
  {
    label: 'Last sliding month',
    range: [startOfDay(subMonths(now, 1)), now]
  },
  { label: 'This year', range: [startOfYear(now), endOfYear(now)] }
]);

onMounted(() => {
  date.value = props.valueDate;
});

function checkValidation(inputProps: InputSlotsProps) {
  if (props.required) {
    state.value = date.value !== undefined && date.value !== null;
    inputProps.isValid(state.value);
  }
  emit('update:modelValue', date.value);
}

function determineFormat(): string {
  let formatDate = '';
  let formatHour = '';
  let formatGlobal = '';
  if (props.locale === 'fr-FR') {
    formatDate = 'dd/MM/yyyy';
  } else {
    formatDate = 'MM-dd-yyyy';
  }

  formatHour = 'HH:mm';
  if (props.needTimePicker && !props.onlyTimePicker) {
    formatGlobal = formatDate + ' ' + formatHour;
  }
  if (props.onlyTimePicker) {
    formatGlobal = formatHour;
  }

  return formatGlobal;
}
</script>

<template>
  <div class="mol-date-picker">
    <InputField :label="props.label" :required="props.required">
      <template #input="inputProps">
        <Datepicker
          class="date-picker"
          v-model="date"
          textInput
          :class="props.range && 'range'"
          :enable-time-picker="props.needTimePicker"
          :time-picker="props.onlyTimePicker"
          :flow="['calendar', 'time']"
          :format="determineFormat()"
          :locale="props.locale"
          :min-date="props.minDate"
          :max-date="props.maxDate"
          :min-range="props.minRange"
          :max-range="props.maxRange"
          :partial-range="props.range && false"
          :range="props.range"
          :preset-ranges="props.range ? presetRanges : undefined"
          :state="state"
          :auto-position="false"
          @cleared="checkValidation(inputProps)"
          @update:modelValue="checkValidation(inputProps)"
          @blur="checkValidation(inputProps)"
          @open="inputProps.toggleFocus"
          @closed="inputProps.toggleFocus"
        >
          <template #input-icon>
            <Icon
              class="date-time-icon"
              :use="props.onlyTimePicker ? 'iterop-clock-regular' : props.range ? 'iterop-date-range-regular' : 'iterop-calendar-regular'"
            />
          </template>

          <template #yearly="{ label, range, presetDateRange }">
            <span @click="presetDateRange(range)">{{ label }}</span>
          </template>
        </Datepicker>
      </template>
    </InputField>
  </div>
</template>

<style scoped lang="scss">
.mol-date-picker {
  position: relative;

  .date-picker {
    :deep(.dp__input_wrap input) {
      border: none;
    }

    :deep(.dp__input_valid),
    :deep(.dp__input_invalid) {
      box-shadow: none;
    }

    :deep(.dp__input_icon) {
      top: 75%;
      transition: all 0.4s;
    }

    &.range {
      min-width: rem(325);
    }
  }

  .focus .date-time-icon {
    fill: $sea-blue;
  }

  .valid .date-time-icon {
    fill: $grass-green;
  }

  .invalid .date-time-icon {
    fill: $blaze-red;
  }
}
</style>

Expected behavior
Like your demo, menu stay attached to field when scrolling.

Screenshots
Date Picker positioning gif

Desktop & mobile (please complete the following information):

  • On all Browser
@StevenHeven StevenHeven added the bug Something isn't working label Jan 25, 2023
@Jasenkoo
Copy link
Contributor

Very strange, maybe InputField slot has something to do with it, can you check if it is still stuck if you place it outside of slot? Also, can you try setting :alt-position="true"

@StevenHeven
Copy link
Author

I try to put DatePicker outside of slot : nothing change. And with your alt-position props is the same :/

@Jasenkoo
Copy link
Contributor

I tried with coping all props you used and can't replicate the issue. Closing this as this is caused by something project related. I would suggest you check if there are any errors in the console.

Also, make sure that the scroll listener is not blocked by something.

If not there is an exposed method onScroll (just realized this is not documented) you can add a global window scroll listener and call this method.

@Jasenkoo Jasenkoo added documentation Improvements or additions to documentation wontfix This will not be worked on and removed bug Something isn't working labels Jan 30, 2023
@tumur1
Copy link

tumur1 commented Feb 13, 2023

Same issue here. When scrollable parent is not 'body', menu position is fixed. And "close-on-scroll" is not working.
I forked the playground.
ss1
ss2

playground URL:
https://stackblitz.com/edit/vuepic-vue-datepicker-tfy4dy?file=src/components/Playground.vue

@StevenHeven
Copy link
Author

I'm feeling less alone ^^ and I found no solution yet

@Jasenkoo
Copy link
Contributor

@tumur1 Thank you for the demo, will check it out.

@Jasenkoo Jasenkoo reopened this Feb 13, 2023
@Jasenkoo Jasenkoo added bug Something isn't working and removed wontfix This will not be worked on labels Feb 13, 2023
@StevenHeven
Copy link
Author

StevenHeven commented Feb 17, 2023

With your PR, I've no overlay anymore ... instead with clean module, or deleted node_modules folder ....
bug-date-picker-4.0

In HTML , we can see the overlay not appear.
With 3.6.8 version :
image
With 4.0.0 version :
image

Do you any suggestions ? Or maybe it is necessary to do migration more efficient ?

@Jasenkoo
Copy link
Contributor

@StevenHeven Can you inspect and see where it is placed, it should be under the input, just to confirm if it is the wrong placement or z-index property.

@StevenHeven
Copy link
Author

Or i didn't see but the menu appear at the top of my page xD
bug-date-picker

I use :altPosition to set position .... only with undefined value it's works to get the position attached with overwrite SCSS :

const customPosition = () => ({ top: datePicker.value.top, left: datePicker.value.left });
</script>

<template>
  <div class="mol-date-picker">
    <InputField :label="props.label" :required="props.required" :value="props.valueDate?.toString()">
      <template #input="inputProps">
        <Datepicker
          class="date-picker"
          v-model="date"
          textInput
          ref="datePicker"   <---- datePicker reference
          auto-apply
          keepActionRow
          :altPosition="customPosition"   <---- here
          :dark="themeDatePicker()"
          :class="props.range && 'range'"
          :enable-time-picker="props.needTimePicker"
          :time-picker="props.onlyTimePicker"
          :flow="['calendar', 'time']"
          :format="determineFormat()"
          :locale="props.locale"
          :minDate="props.minDate"
          :maxDate="props.maxDate"
          :minRange="props.minRange"
          :maxRange="props.maxRange"
          :partialRange="props.range && false"
          :range="props.range"
          :preset-ranges="props.range ? presetRanges : undefined"
          :state="state"
          @cleared="checkValidation(inputProps)"
          @update:modelValue="checkValidation(inputProps)"
          @blur="checkValidation(inputProps)"
          @open="inputProps.toggleFocus"
          @closed="inputProps.toggleFocus"
          @keydown.esc="cancelSelection"
        >
          <template #input-icon>
            <Icon class="date-time-icon" :icon="props.onlyTimePicker ? 'clock' : 'calendar'" />
          </template>

          <template #action-row="{ internalModelValue, selectDate }">
            <div class="action-row" style="text-align: center">
              <p class="current-selection">{{ formatPreviewDate(internalModelValue) }}</p>
              <Button :aspect="ButtonAspect.FILLED" @button-click="selectDate" label="Select" />
            </div>
          </template>

          <template #yearly="{ label, range, presetDateRange }">
            <span @click="presetDateRange(range)">{{ label }}</span>
          </template>
        </Datepicker>
      </template>
    </InputField>
  </div>
</template>

<style scoped lang="scss">
   [...]

  .date-picker {
    [...]

    :deep(.dp__menu) {
      margin-top: rem(12);
      margin-left: rem(-40);
    }

datePicker.value.top not exist.
If I used HTMLElement passed in altPosition, el.offsetTop or el.offsetLeft is always 0 ....

And I have this error on altPosition :

Type '() => { top: any; left: any; }' is not assignable to type 'boolean | ((el: HTMLElement | undefined) => { top: string; left: string; transform: string; }) | undefined'.
  Type '() => { top: any; left: any; }' is not assignable to type '(el: HTMLElement | undefined) => { top: string; left: string; transform: string; }'.ts(2322)

It works, menu stay at the position, but with strange logic ...

@Jasenkoo
Copy link
Contributor

@StevenHeven Tnx, will check what is going on.

@StevenHeven
Copy link
Author

StevenHeven commented Feb 17, 2023

I tried this when I found this method element.getBoundingClientRect() :
const customPosition = (el: HTMLElement) => ({ top: el.getBoundingClientRect().top, left: el.getBoundingClientRect().left });

And I've same position ... Maybe the element past get wrong coordinates position...

@Jasenkoo
Copy link
Contributor

I tried this when I found this method element.getBoundingClientRect() : const customPosition = (el: HTMLElement) => ({ top: el.getBoundingClientRect().top, left: el.getBoundingClientRect().left });

When you pass this function it doesn't work properly?

@StevenHeven
Copy link
Author

I tried this when I found this method element.getBoundingClientRect() : const customPosition = (el: HTMLElement) => ({ top: el.getBoundingClientRect().top, left: el.getBoundingClientRect().left });

When you pass this function it doesn't work properly?

Nope, I've the same position like as my GIF demonstrate upper. The value was, by memory, 425 for top and 215 for left, for all DatePiker. It's good left value, not for top :/

@Jasenkoo
Copy link
Contributor

Ok, I'll change the positioning, looks like position: fixed is having some issues.

Jasenkoo added a commit that referenced this issue Feb 17, 2023
@StevenHeven
Copy link
Author

IT WORKS !! Thx a lot ^^ With no alt-position props ^^

@StevenHeven
Copy link
Author

BUT ... Bug appears for range mode with presets :
image

@Jasenkoo
Copy link
Contributor

IT WORKS !! Thx a lot ^^ With no alt-position props ^^

Great

For the layout, please open a new issue.

@axelkennedal
Copy link

I encountered this same issue, upgrading to the latest version of vue-datepicker fixed it :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working documentation Improvements or additions to documentation
Projects
None yet
Development

No branches or pull requests

4 participants