@grail-ui/svelte
Version:
[](https://www.npmjs.com/package/@grail-ui/svelte) [](https://bundlephobia.com/package/@grail-ui/svelte) [ • 3.51 kB
JavaScript
import { derived, readable, writable } from 'svelte/store';
import { usePortal } from '../portal/portal.js';
import { addEventListener } from '../eventListener/eventListener.js';
import { getPlacement, arrowAttrs } from '../floating/placement.js';
import { createTimeout } from '../timeout/timeout.js';
import { chain } from '../util/chain.js';
import { uniqueId } from '../util/id.js';
import { ESCAPE } from '../util/keyboard.js';
import { writableEffect } from '../util/store.js';
import { noop } from '../util/noop.js';
export const createTooltip = ({ positioning = {}, open = false, openDelay = 1000, closeDelay = 500, portal = 'body', onOpenChange, } = {}) => {
const id = uniqueId('tooltip');
const open$ = writableEffect(open, onOpenChange);
let cleanup = noop;
const triggerAttrs = derived(open$, (open) => (open ? { 'aria-describedby': id } : {}));
const tooltipAttrs = readable({ id: id, role: 'tooltip' });
function init(triggerElement, tooltipElement) {
cleanup();
cleanup = getPlacement(triggerElement, tooltipElement, positioning);
}
const { start: startShowTimer, stop: stopShowTimer, delay: delayShowTimer, } = createTimeout(() => open$.set(true), 0, { immediate: false });
const { start: startHideTimer, stop: stopHideTimer, delay: delayHideTimer, } = createTimeout(() => open$.set(false), closeDelay, {
immediate: false,
});
const tooltipElement$ = writable(null);
const useTooltipTrigger = (element) => {
const removeEvents = chain(addEventListener(element, 'focus', () => show()), addEventListener(element, 'blur', hide), addEventListener(element, 'pointerenter', () => show(openDelay)), addEventListener(element, 'pointerleave', () => hide(closeDelay)), addEventListener(element, 'click', hide));
const unsubscribe = derived([open$, tooltipElement$], (values) => values).subscribe(([$open, $tooltipElement]) => {
if ($open && $tooltipElement) {
init(element, $tooltipElement);
}
else {
cleanup();
}
});
return {
destroy() {
cleanup();
removeEvents();
unsubscribe();
hide();
},
};
};
const useTooltip = (element) => {
const portalAction = portal ? usePortal(element, { target: portal }) : undefined;
tooltipElement$.set(element);
const removeEvents = chain(addEventListener(element, 'pointerenter', () => show()), addEventListener(element, 'pointerleave', hide), addEventListener(document, 'keydown', (e) => {
if (!e.defaultPrevented && e.key === ESCAPE) {
e.preventDefault();
hide();
}
}));
return {
destroy() {
removeEvents();
portalAction?.destroy?.();
tooltipElement$.set(null);
},
};
};
const show = (delay = 0) => {
stopHideTimer();
delayShowTimer.set(delay);
startShowTimer();
};
const hide = (delay = 0) => {
stopShowTimer();
delayHideTimer.set(delay);
startHideTimer();
};
const toggle = () => open$.update((value) => !value);
return {
useTooltipTrigger,
triggerAttrs,
useTooltip,
tooltipAttrs,
arrowAttrs: arrowAttrs(),
open: open$,
show,
hide,
toggle,
};
};