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

Responsive Layout - pop-up negatively interfering #235

Open
uvarov-frontend opened this issue Mar 11, 2024 Discussed in #176 · 4 comments
Open

Responsive Layout - pop-up negatively interfering #235

uvarov-frontend opened this issue Mar 11, 2024 Discussed in #176 · 4 comments
Labels
bug Something isn't working

Comments

@uvarov-frontend
Copy link
Owner

Discussed in #176

Originally posted by pablopereira27 December 30, 2023
For small screens (Boostrap sm ceil breakpoint = 767.98px), I applied the following change to the multiple month calendar.

@media (max-width: 767.98px) { .vanilla-calendar_multiple { width: 100%; max-width: 550px; } }

If it weren't for the pop-ups that can go out the calendar, it would be great.

image
image

overflow: hidden in the .calendar prevents the entire site from being affected, but prevents the user from seeing pop-ups that exceed the calendar.

image
image

I couldn't find a way to solve it via CSS. Could you help me with any tips or any future adjustments to the calendar javascript to avoid this leak of calendar pop-ups?

@uvarov-frontend uvarov-frontend added the bug Something isn't working label Mar 11, 2024
@tfsumon
Copy link

tfsumon commented Mar 13, 2024

Hi @uvarov-frontend,

I encountered an issue where the entire calendar (not just a popup) was overflowing outside the body container at a specific breakpoint (1070px).

I tried fixing it with CSS initially, but wasn't successful. I then implemented a JavaScript solution that resolved the problem.

See the original issue in this example: website (1070px breakpoint).

Here's the fixed version for your reference: website

Here is the JavaScript code I used for the fix.

const options = {
  input: true,
  actions: {
    changeToInput(e, calendar, self) {
      if (!self.HTMLInputElement) return;
      if (self.selectedDates[0]) {
        self.HTMLInputElement.innerHTML = self.selectedDates[0];

        // if you want to hide the calendar after picking a date
        calendar.hide();
      } else {
        self.HTMLInputElement.innerHTML = "Select Date";
      }
    },
    showCalendar(self) {
      const setPositionCalendar = (
        input,
        calendar,
        position,
        css,
      ) => {
        const getPosition = {
          top: -calendar.offsetHeight,
          bottom: input.offsetHeight,
          left: 0,
          center: input.offsetWidth / 2 - calendar.offsetWidth / 2,
          right: input.offsetWidth - calendar.offsetWidth,
        };

        const YPosition = !Array.isArray(position)
          ? "bottom"
          : position[0];
        const XPosition = !Array.isArray(position)
          ? position
          : position[1];

        let left = input.offsetLeft;
        let top = input.offsetTop;

        // Calculate document dimensions
        const docWidth = document.documentElement.clientWidth;
        const docHeight = document.documentElement.clientHeight;

        // Calculate window scroll offsets
        const scrollLeft =
          window.scrollX || document.documentElement.scrollLeft;
        const scrollTop =
          window.scrollY || document.documentElement.scrollTop;

        // Calculate calendar width and set maximum width to 100%
        const calendarWidth = Math.min(
          calendar.offsetWidth,
          docWidth,
        );

        // Check if there's horizontal overflow
        const rightOverflow =
          left + calendarWidth > docWidth + scrollLeft;
        const leftOverflow = left < scrollLeft;

        // Check if there's vertical overflow
        const verticalOverflow =
          (YPosition === "bottom" &&
            top + input.offsetHeight + calendar.offsetHeight >
              docHeight + scrollTop) ||
          (YPosition === "top" &&
            top - calendar.offsetHeight < scrollTop);

        // Adjust positions accordingly
        if (rightOverflow && XPosition !== "left") {
          left = input.offsetLeft + input.offsetWidth - calendarWidth;
        } else if (leftOverflow && XPosition !== "right") {
          left = input.offsetLeft;
        }

        // Center the calendar if the left or right value is negative
        if (left < 0 || left + calendarWidth > docWidth) {
          left =
            document.documentElement.offsetLeft +
            (document.documentElement.offsetWidth - calendarWidth) /
              2;
        }

        if (verticalOverflow) {
          top =
            YPosition === "bottom"
              ? input.offsetTop - calendar.offsetHeight
              : input.offsetTop + input.offsetHeight;
        } else {
          top =
            YPosition === "bottom"
              ? top + input.offsetHeight
              : top - calendar.offsetHeight;
        }

        calendar.classList.add(
          YPosition === "bottom"
            ? css.calendarToInputBottom
            : css.calendarToInputTop,
        );

        Object.assign(calendar.style, {
          left: `${left}px`,
          top: `${top}px`,
          maxWidth: "100%",
        });
      };

      setPositionCalendar(
        self.HTMLInputElement,
        self.HTMLElement,
        "auto",
        self.CSSClasses,
      );

      const actionsInput = (self) => ({
        hide() {
          self.HTMLElement.classList.add(
            self.CSSClasses.calendarHidden,
          );
          if (self.actions.hideCalendar)
            self.actions.hideCalendar(self);
        },
        show() {
          self.HTMLElement.classList.remove(
            self.CSSClasses.calendarHidden,
          );
          if (self.actions.showCalendar)
            self.actions.showCalendar(self);
        },
        self,
      });

      const documentClickEvent = (e) => {
        if (
          !self ||
          e.target === self.HTMLInputElement ||
          self.HTMLElement?.contains(e.target)
        )
          return;

        if (self.HTMLInputElement && self.HTMLElement)
          actionsInput(self).hide();
        window.removeEventListener("resize", handleResize);
        document.removeEventListener("click", documentClickEvent, {
          capture: true,
        });
      };

      const handleResize = () =>
        setPositionCalendar(
          self.HTMLInputElement,
          self.HTMLElement,
          "auto",
          self.CSSClasses,
        );

      self.HTMLInputElement.addEventListener("click", () => {
        window.addEventListener("resize", handleResize);
        document.addEventListener("click", documentClickEvent, {
          capture: true,
        });
      });

      document.addEventListener("click", documentClickEvent, {
        capture: true,
      });

      window.addEventListener("resize", handleResize);
      window.addEventListener("scroll", handleResize);
    },
  },

@whataboutpereira
Copy link
Contributor

Probably easier to use Floating UI for positioning.

@uvarov-frontend
Copy link
Owner Author

uvarov-frontend commented Mar 19, 2024

@tfsumon Thanks for sharing, I’ll think about making the calendar auto-position depending on the breakpoint.
But it was easier for you to use Floating UI, as already written above.

@ghiscoding
Copy link
Contributor

I have the code for auto-positioning in a fork, I can submit a PR for auto-positioning after all my other PRs are reviewed/merged :)

I also assume that my code could help with the original issue since I have a new function that given an element, will calculate the best possible side to reposition itself (by available space depending on element's position in the viewport).

Cheers

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

No branches or pull requests

4 participants