flotilla/src/lib/components/Tippy.svelte

73 lines
1.8 KiB
Svelte
Raw Normal View History

2024-09-25 20:46:10 +00:00
<script lang="ts">
2024-10-15 19:02:11 +00:00
import "tippy.js/animations/shift-away.css"
2024-10-14 19:35:08 +00:00
2025-02-03 23:40:00 +00:00
import tippy from "tippy.js"
2026-07-28 00:13:14 +00:00
import type {Instance} from "tippy.js"
import {onMount, mount, unmount} from "svelte"
2026-08-29 17:12:09 +00:00
import {getTippyTarget, isMobile} from "@lib/html"
2024-09-25 20:46:10 +00:00
2025-02-03 23:50:19 +00:00
let {
component,
children = undefined,
props = {},
params = {},
popover = $bindable(),
instance = $bindable(),
2026-08-29 17:12:09 +00:00
show = $bindable(),
2025-02-03 23:50:19 +00:00
...restProps
} = $props()
2025-02-03 23:40:00 +00:00
let element: Element
2024-09-25 20:46:10 +00:00
2026-07-03 23:48:23 +00:00
// `mount` only tracks prop changes when the props come from a `$state` object,
// so sync incoming props into a reactive one. Without this the popover keeps
// the props it was first mounted with, showing stale data after the source updates.
const mountedProps = $state({...props})
2026-08-29 17:12:09 +00:00
// Building a tippy costs a popper element and a set of listeners, which is wasted on the
// hover menus of a chat row nobody ever opens. Only a real trigger needs the instance up
// front — tippy is the one listening for it. A manual one can wait for `show`.
const create = () => {
popover ??= tippy(element, {
2025-02-03 23:50:19 +00:00
content: target,
animation: "shift-away",
2026-08-29 17:12:09 +00:00
appendTo: getTippyTarget(),
trigger: isMobile ? "click" : "mouseenter focus",
2025-02-03 23:50:19 +00:00
...params,
2026-07-28 00:13:14 +00:00
onShow: (tippyInstance: Instance) => {
instance ??= mount(component, {target, props: mountedProps})
2025-02-03 23:50:19 +00:00
2026-07-28 00:13:14 +00:00
return params.onShow?.(tippyInstance)
},
})
2025-02-03 23:50:19 +00:00
2026-08-29 17:12:09 +00:00
return popover
}
const target = document.createElement("div")
show = () => create().show()
$effect(() => {
Object.assign(mountedProps, props)
})
onMount(() => {
if (params.trigger !== "manual") {
create()
}
2025-02-03 23:50:19 +00:00
return () => {
popover?.destroy()
2026-07-28 00:13:14 +00:00
if (instance) {
unmount(instance)
}
2024-09-25 20:46:10 +00:00
}
})
</script>
2025-02-03 23:40:00 +00:00
<div bind:this={element} class={restProps.class}>
{@render children?.()}
2024-10-03 21:32:55 +00:00
</div>