htmlstreamofficial / preline

Preline UI is an open-source set of prebuilt UI components based on the utility-first Tailwind CSS framework.
https://preline.co
Other
4.9k stars 309 forks source link

Inconsistent behaviour of modal with Vue - some methods fail to open/ close modal and/or backdrop #492

Open RSchmitzHH opened 3 weeks ago

RSchmitzHH commented 3 weeks ago

Summary

  1. Some methods fail to open/ close modal and/or backdrop. 2. Triggering a DOM updates thru changing a reactive variable leads to a permanently opened backdrop even for the working methods

Steps to Reproduce

I have a very basic modal for prompting user confirmation which pretty much follows the one from the documentation. I have two problems with it

  1. Several of the documented methods to open or close the modal fail entirely, which a pretty uncommon, to my understanding, combination of two different method left that works
  2. Even for that working combination of methods, reactive updates to the DOM directly before opening or with the modal open lead to a failure of modal closing, in the sense that the modal window itself is gone but the backdrop reimains open forever

I will be showing the whole code to present the different combination of working methods to open and close it. I have marked the different methods to open and close it and commented what happens with them.

I open the modal from another component through

userConformationModalRef.value.promptUserConformation()

The modal code is the following:

<script setup>
import { nextTick, onBeforeUnmount, onMounted, ref, computed } from 'vue';
import { HSOverlay } from 'preline';

// Props
const props = defineProps({
  overlayId: {
    type: String,
    required: true,
  },
  heading: {
    type: String,
    default: 'Bestätigung erforderlich',
  },
  prompt_message: {
    type: String,
    required: true,
  },
  approve_message: {
    type: String,
    default: 'Bestätigen',
  },
  approve_color: {
    type: String,
    default: 'bg-blue-600 hover:bg-blue-700',
  },
  discard_color: {
    type: String,
    default: 'bg-gray-200 hover:bg-gray-50',
  },
  discard_message: {
    type: String,
    default: 'Abbrechen',
  },
  header_icon: {
    type: String,
    default: null,
  },
  width: {
    type: Number,
    default: 0.3,
  },
});

const modal = ref(null);

// Computed properties
const hashtagOverlayId = computed(() => `#${props.overlayId}`);

// Methods
const promptUserConformation = async () => {
  await nextTick(); // Ensure DOM updates are complete. Without this, modal window itself opens and closes, but backdrop remains there

  // HSOverlay.open(modalRef.value);  // works
  // modal.value.open();  // does NOT work - even though close does
  HSOverlay.open(hashtagOverlayId.value); // works

};

const approveModal = async () => {
  modal.value.close();
  await nextTick(); // Ensure DOM updates are complete
};

const declineModal = async () => {
  modal.value.close(); // this manages to close the modal (even though modal.value.open does not work, cf. above)
  // HSOverlay.close(hashtagOverlayId.value);  // this reopens modal diretly after closing
  // HSOverlay.close(modalRef.value);  // this reopens modal diretly after closing

  await nextTick();
};

onMounted(async () => {
  modal.value = new HSOverlay(document.querySelector(hashtagOverlayId.value));
});

const modalRef = ref(null);

defineExpose({
  promptUserConformation,
});
</script>

<template>
  <div
    ref="modalRef"
    :id="overlayId"
    class="hs-overlay hidden w-full h-full fixed top-0 left-0 z-[100] overflow-x-hidden overflow-y-auto"
  >
    {{ hashtagOverlayId }}
    <div
      class="hs-overlay-open:mt-7 hs-overlay-open:opacity-100 hs-overlay-open:duration-500 mt-14 opacity-0 ease-out transition-all sm:max-w-xl sm:w-full m-3 sm:mx-auto"
    >
      <div
        class="flex flex-col bg-white border shadow-sm rounded-xl dark:bg-neutral-800 dark:border-gray-700 dark:shadow-neutral-700/[.7]"
      >
        <div class="flex justify-between items-center py-3 px-4 border-b dark:border-gray-700">
          <h3 class="font-bold text-gray-800 dark:text-white flex items-center gap-x-4">
            <span v-if="header_icon" class="material-symbols-outlined">
              {{ header_icon }}
            </span>
            {{ heading }}
          </h3>
          <button
            type="button"
            class="hs-dropup-toggle inline-flex flex-shrink-0 justify-center items-center h-8 w-8 rounded-md text-gray-500 hover:text-gray-400 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 focus:ring-offset-white transition-all text-sm dark:focus:ring-gray-700 dark:focus:ring-offset-gray-800"
            :data-hs-overlay="hashtagOverlayId"
            @click.stop="declineModal"
          >
            X (close ion)
          </button>
        </div>
        <div class="p-4 overflow-y-auto">
          <div class="mt-1 text-gray-800 dark:text-gray-400">
            <div class="modal-content">
              <slot></slot>
            </div>
          </div>
        </div>
        <div class="flex justify-end items-center gap-x-2 py-3 px-4 border-t dark:border-gray-700">
          <button
            type="button"
            class="hs-dropup-toggle py-3 px-4 inline-flex justify-center items-center gap-2 rounded-md border font-medium bg-white text-gray-700 shadow-sm align-middle focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-white focus:ring-blue-600 transition-all text-sm dark:bg-neutral-900 dark:hover:bg-neutral-800 dark:border-gray-700 dark:text-gray-400 dark:hover:text-white dark:focus:ring-offset-gray-800"
            :class="discard_color"
            :data-hs-overlay="hashtagOverlayId"
            @click.stop="declineModal"
          >
            {{ discard_message }}
          </button>
          <a
            class="py-3 px-4 inline-flex justify-center items-center gap-2 rounded-md border border-transparent font-semibold text-white focus:outline-none focus:ring-2 focus:ring-blue-600 focus:ring-offset-2 transition-all text-sm dark:focus:ring-offset-gray-800"
            :class="approve_color"
            href="#"
            :data-hs-overlay="hashtagOverlayId"
            @click.stop="approveModal"
          >
            {{ approve_message }}
          </a>
        </div>
      </div>
    </div>
  </div>
</template>

So the only two working combinations of methods are:

  1. Open the modal by HSOverlay.open(modalRef.value); or HSOverlay.open(hashtagOverlayId.value);. modal.value.open();does not do anything.
  2. And close it by modal.value.close();. HSOverlay.open(...) in both variants let the modal vanish for a second and directly reopen it then. Every time, another backdrop is opened so the backdropped window is getting darker and darker.

In addition, when I change a reactive variable just before opening the modal or when it is still open, also the working combination of methods fail, in the sense that the modal window itself closes but the backdrop remains open forever:

  attemptingToDeleteSection.value[textItemIndex] = true;  // okay if nextTick before open, triggers backdrop remaining open if modal is directly opened without nextText

  userConformationModalHandleDeleteTextItem.value.promptUserConformation()

  // wait 2s
  await new Promise((resolve) => setTimeout(resolve, 2000));

  attemptingToDeleteSection.value[textItemIndex] = false;  // backdrop remains open forever if modal closed AFTER this update. If closed before, everything works like charm.

The attemptingToDeleteSection merely sets a background color in a v-for-div. This works like charm, so no problem here.

Note that on App startup I encouter https://github.com/htmlstreamofficial/preline/issues/491 , so maybe this is related.

Demo Link

N/A

Expected Behavior

  1. All documented methods to open and close a modal should work. At least, opening and closing should work by the same kind of methods.
  2. Reactive updates to other components before opening the modal or with it open should leave the modal unaffacted.

Actual Behavior

  1. Only a combination of methods opens and closes the modal
  2. Changing the color of a div in another component by a reactive variable and a conditional class with the modal open lets the backdrop remain open forever when later closing the modal.

Screenshots

No response