SchwarzIT / onyx

🚀 A design system and Vue.js component library created by Schwarz IT
https://onyx.schwarz
Apache License 2.0
54 stars 6 forks source link

Implement common Outside click util #1176

Open JoCa96 opened 3 months ago

JoCa96 commented 3 months ago

We should create a common util that detects outside-click for given elements, instead of every component registering their own listeners.

Acceptance criteria

Definition of Done

Approval

Implementation details

We should create a common util that detects outside-click for given elements, instead of every component registering their own listeners.

Reference implementations

An implementation already exists in WaWi: ui/components/src/utils/click.ts:

import { computed, ref, watch } from "vue";

export type UnregisterOutsideClick = () => void;

const registeredListeners = ref(new Map<HTMLElement[], () => void>());
const triggerdOnce = ref<boolean>(false);
const listenerArray = computed(() => Array.from(registeredListeners.value.entries()));

const handleGlobalClick = ({ target }: MouseEvent) => {
    const listenersToTrigger = listenerArray.value.filter(
        ([insideElements]) =>
            !insideElements.some((e) => e === target || (target && e.contains?.(target as HTMLElement))),
    );
    listenersToTrigger.forEach(([insideElements, handler]) => {
        triggerdOnce.value && registeredListeners.value.delete(insideElements);
        handler();
    });
};

watch(
    () => registeredListeners.value.size,
    (hasListeners) => {
        if (hasListeners) {
            document.addEventListener("click", handleGlobalClick);
        } else {
            document.removeEventListener("click", handleGlobalClick);
        }
    },
);

const registerOutsideClick = (
    insideElements: HTMLElement[],
    handler: () => void,
    isTriggerdOnce: boolean = false,
): UnregisterOutsideClick => {
    // We want to register the listener AFTER all current events are processed.
    // Otherwise the outsidelickhandler is immediately triggered by the bubbling event.
    triggerdOnce.value = isTriggerdOnce;
    setTimeout(() => registeredListeners.value.set(insideElements, handler));
    return () => registeredListeners.value.delete(insideElements);
};

export const ClickUtil = {
    registerOutsideClick,
};
larsrickert commented 3 months ago

Might already be implemented in https://github.com/SchwarzIT/onyx/blob/main/packages/headless/src/composables/outsideClick.ts