Files
svelte-hint/src/lib/Hint.svelte
2022-01-16 01:27:04 +01:00

94 lines
2.4 KiB
Svelte

<script lang="ts" context="module">
import type { DOMDetectOverflowOptions, Placement } from '@floating-ui/dom'
</script>
<script lang="ts">
import { computePosition, flip, shift, offset as offsetMiddleware, autoPlacement } from '@floating-ui/dom'
import { onMount } from 'svelte'
export let placement: Placement | undefined = undefined
export let text: string | null = null
export let boundary: DOMDetectOverflowOptions['boundary'] = 'clippingParents'
export let offset: Parameters<typeof offsetMiddleware>[0] = 4
export let auto: boolean | 'start' | 'end' = false
let trigger: HTMLSpanElement | null = null
let hint: HTMLSpanElement | null = null
let dataShow = false
let id = ''
async function update() {
if (!hint || !trigger) return
const options = {
placement: placement as Placement,
middleware: auto
? [offsetMiddleware(offset), autoPlacement({ boundary, alignment: typeof auto === 'string' ? auto : null })]
: [offsetMiddleware(offset), flip({ boundary }), shift({ boundary, padding: 4 })],
}
const { x, y } = await computePosition(trigger, hint, options)
Object.assign(hint!.style, {
left: `${x}px`,
top: `${y}px`,
})
}
async function show() {
await update()
dataShow = true
}
function hide() {
dataShow = false
}
onMount(() => {
update()
id = Math.random().toString(16).slice(2) // Random id for the tooltip
// Bind events
const events: [keyof HTMLElementEventMap, EventListener][] = [
['mouseenter', show],
['focus', show],
['mouseleave', hide],
['blur', hide],
]
events.forEach(([event, handler]) => trigger?.addEventListener(event, handler))
return () => {
events.forEach(([event, handler]) => trigger?.removeEventListener(event, handler))
}
})
</script>
<div bind:this={trigger} aria-describedby={id}>
<slot />
</div>
<div bind:this={hint} {id} data-show={dataShow} role="tooltip" class="svelte-hint-tooltip">
{#if text}
<span>{text}</span>
{:else}
<slot name="hint" />
{/if}
</div>
<style>
.svelte-hint-tooltip {
position: absolute;
visibility: hidden;
}
.svelte-hint-tooltip[data-show='true'] {
visibility: visible;
}
span {
display: inline-block;
background-color: #111;
color: #fff;
padding: 0.125rem 0.25rem;
font-size: 0.8rem;
width: max-content;
border-radius: 0.25rem;
}
</style>