UNPKG

@zag-js/tooltip

Version:

Core logic for the tooltip widget implemented as a state machine

159 lines (158 loc) 5.73 kB
// src/tooltip.connect.ts import { dataAttr, isLeftClick } from "@zag-js/dom-query"; import { isFocusVisible } from "@zag-js/focus-visible"; import { getPlacementSide, getPlacementStyles } from "@zag-js/popper"; import { parts } from "./tooltip.anatomy.mjs"; import * as dom from "./tooltip.dom.mjs"; import { store } from "./tooltip.store.mjs"; function connect(service, normalize) { const { state, context, send, scope, prop, event: _event } = service; const id = prop("id"); const hasAriaLabel = !!prop("aria-label"); const open = state.matches("open", "closing"); const triggerValue = context.get("triggerValue"); const currentPlacement = context.get("currentPlacement"); const currentPlacementSide = currentPlacement ? getPlacementSide(currentPlacement) : void 0; const contentId = dom.getContentId(scope); const disabled = prop("disabled"); const popperStyles = getPlacementStyles({ ...prop("positioning"), placement: currentPlacement }); return { open, setOpen(nextOpen) { const open2 = state.matches("open", "closing"); if (open2 === nextOpen) return; send({ type: nextOpen ? "open" : "close" }); }, triggerValue, setTriggerValue(value) { send({ type: "triggerValue.set", value: value ?? void 0 }); }, reposition(options = {}) { send({ type: "positioning.set", options }); }, getTriggerProps(props = {}) { const { value } = props; const current = value == null ? false : triggerValue === value; const triggerId = dom.getTriggerId(scope, value); return normalize.button({ ...parts.trigger.attrs, id: triggerId, "data-ownedby": scope.id, "data-value": value, "data-current": dataAttr(current), dir: prop("dir"), "data-expanded": dataAttr(open), "data-state": open ? "open" : "closed", "aria-describedby": open ? contentId : void 0, onClick(event) { if (event.defaultPrevented) return; if (disabled) return; if (!prop("closeOnClick")) return; const shouldSwitch = open && value != null && !current; send({ type: shouldSwitch ? "triggerValue.set" : "close", src: "trigger.click", value, triggerId }); }, onFocus(event) { if (event.defaultPrevented) return; if (disabled) return; if (!isFocusVisible()) return; const shouldSwitch = open && value != null && !current; send({ type: shouldSwitch ? "triggerValue.set" : "open", src: "trigger.focus", value, triggerId }); }, onBlur(event) { if (event.defaultPrevented) return; if (disabled) return; if (id !== store.get("id")) return; const activeEl = event.relatedTarget ?? scope.getDoc().activeElement; const focusedAnotherTrigger = activeEl?.closest(`[data-ownedby="${scope.id}"]`) != null; if (!focusedAnotherTrigger) { send({ type: "close", src: "trigger.blur", value, triggerId }); } }, onPointerDown(event) { if (event.defaultPrevented) return; if (disabled) return; if (!isLeftClick(event)) return; if (!prop("closeOnPointerDown")) return; if (id === store.get("id")) { send({ type: "close", src: "trigger.pointerdown", value, triggerId }); } }, onPointerMove(event) { if (event.defaultPrevented) return; if (disabled) return; if (event.pointerType === "touch") return; const shouldSwitch = open && value != null && !current; send({ type: shouldSwitch ? "triggerValue.set" : "pointer.move", value, triggerId }); }, onPointerOver(event) { if (event.defaultPrevented) return; if (disabled) return; if (event.pointerType === "touch") return; send({ type: "pointer.move", value, triggerId }); }, onPointerLeave() { if (disabled) return; send({ type: "pointer.leave" }); }, onPointerCancel() { if (disabled) return; send({ type: "pointer.leave" }); } }); }, getArrowProps() { return normalize.element({ id: dom.getArrowId(scope), ...parts.arrow.attrs, dir: prop("dir"), style: popperStyles.arrow }); }, getArrowTipProps() { return normalize.element({ ...parts.arrowTip.attrs, dir: prop("dir"), style: popperStyles.arrowTip }); }, getPositionerProps() { return normalize.element({ id: dom.getPositionerId(scope), ...parts.positioner.attrs, dir: prop("dir"), style: popperStyles.floating }); }, getContentProps() { const isCurrentTooltip = store.get("id") === id; const isPrevTooltip = store.get("prevId") === id; const instant = store.get("instant") && (open && isCurrentTooltip || isPrevTooltip); return normalize.element({ ...parts.content.attrs, dir: prop("dir"), hidden: !open, "data-state": open ? "open" : "closed", "data-instant": dataAttr(instant), role: hasAriaLabel ? void 0 : "tooltip", id: hasAriaLabel ? void 0 : contentId, "data-placement": currentPlacement, "data-side": currentPlacementSide, onPointerEnter() { send({ type: "content.pointer.move" }); }, onPointerLeave() { send({ type: "content.pointer.leave" }); }, style: { pointerEvents: prop("interactive") ? "auto" : "none" } }); } }; } export { connect };