primefaces / primevue

Next Generation Vue UI Component Library
https://primevue.org
MIT License
10.53k stars 1.23k forks source link

TieredMenu: Subitems go off the side of the screen when button is near edge #2765

Closed agm1984 closed 2 years ago

agm1984 commented 2 years ago

Describe the bug

When a TieredMenu item has subitems, the subitems load off the screen:

image

Here is a minimal reproduction: https://codesandbox.io/s/naughty-taussig-rtpl7s?file=/src/App.vue

Here is the code:

<script setup>
import Button from 'primevue/button';
import TieredMenu from 'primevue/tieredmenu';

const menu = ref();

const menuItems = reactive([
    {
        label: 'Set status to',
        icon: 'icon-logout',
        items: [
            {
                label: 'Active',
            },
            {
                label: 'On hold',
            },
            {
                label: 'Terminated',
            },
        ],
    },
];

const toggleMenu = (event) => {
    menu.value.toggle(event);
};
</script>

<template>
    <Button
        class="!ml-2 p-button-secondary"
        icon="icon-chevron-down"
        icon-pos="right"
        label="Actions"
        @click="toggleMenu"
    />

    <TieredMenu
        ref="menu"
        :model="menuItems"
        popup
    />
</template>

Reproducer

No response

PrimeVue version

3.12.6

Vue version

3.x

Language

ES6

Build / Runtime

Vite

Browser(s)

all

Steps to reproduce the behavior

  1. make a tiered menu that opens directly beside the right side of the screen
  2. open a menu that has submenu items
  3. try to see the submenu items

Expected behavior

Submenu items should open on the left side of the menu when their position would go off the side of the viewport

agm1984 commented 2 years ago

I think this is related too https://github.com/primefaces/primevue/issues/1700

This also seems related: https://github.com/primefaces/primevue/issues/1741

I didn't notice until now, but in my reproducing example, the page gains a horizontal scrollbar when the menu subitems are expanded.

tugcekucukoglu commented 2 years ago

Duplicate #1700

agm1984 commented 2 years ago

I've been using the ContextMenu component instead of the TieredMenu component because it works properly when items would go off the screen:

image

But it has a problem where it loads relative to the mouse position rather than the button position, but I just managed to fix it by generating my own pointer event and passing that to the toggle function:

Here is an example:

<script setup>
import { ref, reactive, computed } from 'vue';
import Button from 'primevue/button';
import ContextMenu from 'primevue/contextmenu';

const menu = ref();

const toggleMenu = () => {
    const el = document.querySelector('#actions');
    const coords = el.getBoundingClientRect();

    const event = new PointerEvent('click', {
        clientX: (coords.x + coords.width),
        clientY: (coords.y + coords.height),
    });

    menu.value.toggle(event);
};

const menuItems = computed(() => ([
    reactive({
        label: 'Set status to',
        icon: 'icon-logout',
        items: [
            {
                label: 'Active',
            },
            {
                label: 'On hold',
            },
            {
                label: 'Terminated',
            },
        ],
    }),
    reactive({
        label: 'Add to program',
        icon: 'icon-programs',
        command: () => openAddToProgramModal(),
        disabled: !employees.checkedRows.length,
    }),
    reactive({
        label: 'Message',
        icon: 'icon-message',
        command: () => openMessageModal(),
        disabled: !employees.checkedRows.length,
    }),
    reactive({
        label: 'Customize columns',
        icon: 'icon-settings',
        command: () => openCustomizeColumnsModal(),
    }),
]));
</script>

<template>
    <Button
        id="actions"
        class="!ml-2 p-button-secondary"
        icon="icon-chevron-down"
        icon-pos="right"
        label="Actions"
        @click.stop.prevent="toggleMenu"
    />

    <ContextMenu
        ref="menu"
        :model="menuItems"
    />
</template>

If you look close at @click.stop.prevent="toggleMenu", the stop propagation and preventDefault were important to making the menu show. It didn't work without adding those.

The only two pointer event properties you need are clientX and clientY. For some unknown reason, those are translated into pageX and pageY in the PrimeVue component.

The toggleMenu is simply grabbing the button element co-ordinates and passing the bottom-right corner of the button to the ContextMenu component, and that causes the menu to load according to the button position rather than the true mouse position.

It is important to note that my solution is for when the button is close to the right-side of the screen. If you add this logic for a button that loads anywhere there is enough room for the menu-width, you will have to change the code to this:

    const event = new PointerEvent('click', {
        clientX: coords.x,
        clientY: (coords.y + coords.height),
    });