mengxiong10 / vue-datepicker-next

A datepicker / datetimepicker component for Vue3
https://mengxiong10.github.io/vue-datepicker-next/
MIT License
147 stars 34 forks source link

[Feature request] Append popup to any element, not just body or datepicker #3

Closed ItMaga closed 2 years ago

ItMaga commented 2 years ago

What problem does this feature solve?

Sometimes there are cases when you need append a popup to any element, I think it would be nice to have such a prop

What does the proposed API look like?

props: {
  appendTo: string
}
<Teleport :to="appendTo">
  ...popup content
</Teleport>
mengxiong10 commented 2 years ago

If you just want to change the position not the parent element, you can use popupStyle

<date-picker :popup-style="{ left: 100px, top: 100px }" >
ItMaga commented 2 years ago

If you just want to change the position not the parent element, you can use popupStyle

<date-picker :popup-style="{ left: 100px, top: 100px }" >

In my case, the popup should be displayed on the bottom sheet, and absolute positioning in this case is not very reliable and convenient

In the vue2-datepicker I used the following hack:

     const datepickerPopup = this.$refs.datepicker.$refs.popup.$el;
     this.$refs.bottomSheetContent.appendChild(datepickerPopup);

For this library this hack is not relevant.

mengxiong10 commented 2 years ago

You also can do it like vue2-datepicker.

mounted() {
     const datepickerPopup = document.querySelector('.mx-datepicker-popup');
     this.$refs.bottomSheetContent.appendChild(datepickerPopup);
}

But I think change the style is better.

<date-picker @open="handleOpen" :popup-style="position"  />
data(){{
  return { postion: { left: '0px', top: '0px'}}
}
methods: {
  handleOpen(){
    const element = /* bottom sheet */ 
    const  { left , top} = element.getBoundingClientRect();
    this.position.left = left + window.pageXOffset + 'px'
    this.position.top = left + window.pageYOffset + 'px'
  }
}
ItMaga commented 2 years ago

You also can do it like vue2-datepicker.

mounted() {
     const datepickerPopup = document.querySelector('.mx-datepicker-popup');
     this.$refs.bottomSheetContent.appendChild(datepickerPopup);
}

I can only do this trick once after rendering the datepicker component.


But I think change the style is better.

<date-picker @open="handleOpen" :popup-style="position"  />
data(){{
  return { postion: { left: '0px', top: '0px'}}
}
methods: {
  handleOpen(){
    const element = /* bottom sheet */ 
    const  { left , top} = element.getBoundingClientRect();
    this.position.left = left + window.pageXOffset + 'px'
    this.position.top = left + window.pageYOffset + 'px'
  }
}

As for this solution, for example, in Safari there is a bottom bar that can collapse. In that case, do we need to recalculate the positions?

ItMaga commented 2 years ago

@mengxiong10 Hello, any updates? Will it be possible to append to an element, or can we maybe expose a Popup via template ref?

mengxiong10 commented 2 years ago

@mengxiong10 Hello, any updates? Will it be possible to append to an element, or can we maybe expose a Popup via template ref?

You can use document.querySelector to get the element.

<date-picker  @open="handleOpen" />
handleOpen() {
    this.$nextTick(() => {
          const parent = // an element
         const datepickerPopup = document.querySelector('.mx-datepicker-popup');
         if (datepickerPopup) parent.appendChild(datepickerPopup);

    })
}

mounted() {
   this.handleOpen()
}
ItMaga commented 2 years ago

@mengxiong10 Hello, any updates? Will it be possible to append to an element, or can we maybe expose a Popup via template ref?

You can use document.querySelector to get the element.

<date-picker  @open="handleOpen" />
handleOpen() {
    this.$nextTick(() => {
          const parent = // an element
         const datepickerPopup = document.querySelector('.mx-datepicker-popup');
         if (datepickerPopup) parent.appendChild(datepickerPopup);

    })
}

mounted() {
   this.handleOpen()
}

Did as you suggested, looks like this

  <div>
    <Datepicker @change="changeValue" :value="modelValue" :lang="locale" separator=" - " v-model:open="isShowPanel" />

    <Teleport to="#overlay">
      <BottomSheet :title="title" :isShow="isShowPanel">
        <div ref="bottomSheetContent"></div>
      </BottomSheet>
    </Teleport>
  </div>

<script>
export default {
...

  watch: {
    isShowPanel() {
      this.isShowPanel && this.appendDatepickerPopupToBottomSheet();
    },
  },

  methods: {
    appendDatepickerPopupToBottomSheet() {
      this.$nextTick(() => {
        const datepickerPopup = document.querySelector('.mx-datepicker-popup');
        if (datepickerPopup) this.$refs.bottomSheetContent.appendChild(datepickerPopup);
      });
    },
  },

  mounted() {
    this.appendDatepickerPopupToBottomSheet();
  },
}
</script>

This way, I can only open the datepicker once, other times const datepickerPopup = document.querySelector('.mx-datepicker-popup'); equals undefined

ItMaga commented 2 years ago

@mengxiong10 Any advice for my case?)

mengxiong10 commented 2 years ago

Any update? https://codesandbox.io/s/vue-datepicker-next-appendto-iq5eq?file=/src/App.vue

ItMaga commented 2 years ago

Any update? https://codesandbox.io/s/vue-datepicker-next-appendto-iq5eq?file=/src/App.vue

This behavior is observed when adding v-if Example - https://codesandbox.io/s/vue-datepicker-next-appendto-forked-lq5ox2?file=/src/App.vue

mengxiong10 commented 2 years ago

@ItMaga You should change the popup to body when the parentElemnt is destroyed. https://codesandbox.io/s/vue-datepicker-next-appendto-forked-zphlw4?file=/src/App.vue

ItMaga commented 2 years ago

Thank you @mengxiong10:) It works