| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149 |
- import { Tooltip as KobalteTooltip } from "@kobalte/core/tooltip"
- import { createEffect, Match, onCleanup, splitProps, Switch, type JSX } from "solid-js"
- import type { ComponentProps } from "solid-js"
- import { createStore } from "solid-js/store"
- export interface TooltipProps extends ComponentProps<typeof KobalteTooltip> {
- value: JSX.Element
- class?: string
- contentClass?: string
- contentStyle?: JSX.CSSProperties
- inactive?: boolean
- forceOpen?: boolean
- }
- export interface TooltipKeybindProps extends Omit<TooltipProps, "value"> {
- title: string
- keybind: string
- }
- export function TooltipKeybind(props: TooltipKeybindProps) {
- const [local, others] = splitProps(props, ["title", "keybind"])
- return (
- <Tooltip
- {...others}
- value={
- <div data-slot="tooltip-keybind">
- <span>{local.title}</span>
- <span data-slot="tooltip-keybind-key">{local.keybind}</span>
- </div>
- }
- />
- )
- }
- export function Tooltip(props: TooltipProps) {
- let ref: HTMLDivElement | undefined
- const [state, setState] = createStore({
- open: false,
- block: false,
- expand: false,
- })
- const [local, others] = splitProps(props, [
- "children",
- "class",
- "contentClass",
- "contentStyle",
- "inactive",
- "forceOpen",
- "ignoreSafeArea",
- "value",
- ])
- const close = () => setState("open", false)
- const inside = () => {
- const active = document.activeElement
- if (!ref || !active) return false
- return ref.contains(active)
- }
- const drop = (expand = state.expand) => {
- if (expand) return
- if (ref?.matches(":hover")) return
- if (inside()) return
- setState("block", false)
- }
- const sync = () => {
- const expand = !!ref?.querySelector('[aria-expanded="true"], [data-expanded]')
- setState("expand", expand)
- if (expand) {
- setState("block", true)
- close()
- return
- }
- drop(expand)
- }
- const arm = () => {
- setState("block", true)
- close()
- }
- const leave = () => {
- if (!inside()) close()
- drop()
- }
- createEffect(() => {
- if (!ref) return
- sync()
- const obs = new MutationObserver(sync)
- obs.observe(ref, {
- subtree: true,
- childList: true,
- attributes: true,
- attributeFilter: ["aria-expanded", "data-expanded"],
- })
- onCleanup(() => obs.disconnect())
- })
- return (
- <Switch>
- <Match when={local.inactive}>{local.children}</Match>
- <Match when={true}>
- <KobalteTooltip
- gutter={4}
- {...others}
- closeDelay={0}
- ignoreSafeArea={local.ignoreSafeArea ?? true}
- open={local.forceOpen || state.open}
- onOpenChange={(open) => {
- if (local.forceOpen) return
- if (state.block && open) return
- setState("open", open)
- }}
- >
- <KobalteTooltip.Trigger
- ref={ref}
- as={"div"}
- data-component="tooltip-trigger"
- class={local.class}
- onPointerDownCapture={arm}
- onKeyDownCapture={(event: KeyboardEvent) => {
- if (event.key !== "Enter" && event.key !== " ") return
- arm()
- }}
- onPointerLeave={leave}
- onFocusOut={() => requestAnimationFrame(() => drop())}
- >
- {local.children}
- </KobalteTooltip.Trigger>
- <KobalteTooltip.Portal>
- <KobalteTooltip.Content
- data-component="tooltip"
- data-placement={props.placement}
- data-force-open={local.forceOpen}
- class={local.contentClass}
- style={local.contentStyle}
- >
- {local.value}
- {/* <KobalteTooltip.Arrow data-slot="tooltip-arrow" /> */}
- </KobalteTooltip.Content>
- </KobalteTooltip.Portal>
- </KobalteTooltip>
- </Match>
- </Switch>
- )
- }
|