Simplify tippy api
This commit is contained in:
parent
749e87f228
commit
a6bf874280
13 changed files with 149 additions and 146 deletions
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import cx from "classnames"
|
||||
import {type Instance} from "tippy.js"
|
||||
import {hash, formatTimestampAsTime} from "@welshman/lib"
|
||||
import type {Maybe} from "@welshman/lib"
|
||||
import type {TrustedEvent, EventContent} from "@welshman/util"
|
||||
import {Thunks} from "@welshman/app"
|
||||
import {isMobile} from "@lib/html"
|
||||
|
|
@ -10,6 +10,7 @@
|
|||
import Button from "@lib/components/Button.svelte"
|
||||
import Tippy from "@lib/components/Tippy.svelte"
|
||||
import TapTarget from "@lib/components/TapTarget.svelte"
|
||||
import type {TippyController} from "@lib/components/Tippy.svelte"
|
||||
import ProfileCircle from "@app/components/ProfileCircle.svelte"
|
||||
import {publishWrappedReaction, retractWrappedReaction} from "@app/reactions"
|
||||
import Content from "@app/components/Content.svelte"
|
||||
|
|
@ -52,17 +53,9 @@
|
|||
|
||||
const showMobileMenu = () => pushModal(ChatMessageMenuMobile, {event, pubkeys, reply, edit})
|
||||
|
||||
const togglePopover = () => {
|
||||
if (popoverIsVisible) {
|
||||
popover?.hide()
|
||||
} else {
|
||||
showPopover()
|
||||
}
|
||||
}
|
||||
const togglePopover = () => tippy?.toggle()
|
||||
|
||||
let popover: Instance | undefined = $state()
|
||||
let showPopover: () => void = $state(() => {})
|
||||
let popoverIsVisible = $state(false)
|
||||
let tippy: Maybe<TippyController> = $state()
|
||||
</script>
|
||||
|
||||
<ThunkFailure showToastOnRetry {thunk} class="mt-1" />
|
||||
|
|
@ -71,20 +64,10 @@
|
|||
class={cx("group flex items-center justify-end gap-1 px-2", {"flex-row-reverse": !isOwn})}>
|
||||
{#if !isMobile}
|
||||
<Tippy
|
||||
bind:popover
|
||||
bind:show={showPopover}
|
||||
bind:controller={tippy}
|
||||
component={ChatMessageMenu}
|
||||
props={{event, pubkeys, popover, replyTo, edit}}
|
||||
params={{
|
||||
interactive: true,
|
||||
trigger: "manual",
|
||||
onShow() {
|
||||
popoverIsVisible = true
|
||||
},
|
||||
onHidden() {
|
||||
popoverIsVisible = false
|
||||
},
|
||||
}}>
|
||||
props={{event, pubkeys, tippy, replyTo, edit}}
|
||||
params={{interactive: true, trigger: "manual"}}>
|
||||
<button
|
||||
type="button"
|
||||
class="opacity-0 transition-all"
|
||||
|
|
|
|||
|
|
@ -8,13 +8,13 @@
|
|||
import Reply from "@assets/icons/reply-2.svg?dataurl"
|
||||
import Code2 from "@assets/icons/code-2.svg?dataurl"
|
||||
|
||||
const {event, pubkeys, popover, replyTo, edit} = $props()
|
||||
const {event, pubkeys, tippy, replyTo, edit} = $props()
|
||||
|
||||
const reply = () => replyTo(event)
|
||||
const onEdit = () => edit?.()
|
||||
|
||||
const showInfo = () => {
|
||||
popover.hide()
|
||||
tippy.hide()
|
||||
pushModal(EventInfo, {event})
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
<script lang="ts">
|
||||
import {spec} from "@welshman/lib"
|
||||
import cx from "classnames"
|
||||
import {writable} from "svelte/store"
|
||||
import type {Writable} from "svelte/store"
|
||||
import type {Instance} from "tippy.js"
|
||||
import {spec} from "@welshman/lib"
|
||||
import type {Maybe} from "@welshman/lib"
|
||||
import {preventDefault} from "@lib/html"
|
||||
import {createSearch} from "@welshman/app"
|
||||
import {currencyOptions, displayCurrency} from "@lib/currency"
|
||||
|
|
@ -12,6 +13,7 @@
|
|||
import AltArrowDown from "@assets/icons/alt-arrow-down.svg?dataurl"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Tippy from "@lib/components/Tippy.svelte"
|
||||
import type {TippyController} from "@lib/components/Tippy.svelte"
|
||||
|
||||
interface Props {
|
||||
value: string
|
||||
|
|
@ -36,7 +38,7 @@
|
|||
const selectCurrency = (code: string) => {
|
||||
value = code
|
||||
term.set("")
|
||||
popover?.hide()
|
||||
tippy?.hide()
|
||||
}
|
||||
|
||||
const clearAndFocus = () => {
|
||||
|
|
@ -46,7 +48,7 @@
|
|||
}
|
||||
|
||||
const onKeyDown = (e: Event) => {
|
||||
if (instance?.onKeyDown(e)) {
|
||||
if (tippy?.content?.onKeyDown(e)) {
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
|
|
@ -54,15 +56,13 @@
|
|||
const currency = $derived(currencyOptions.find(spec({code: value})))
|
||||
|
||||
let wrapper: Element | undefined = $state()
|
||||
let popover: Instance | undefined = $state()
|
||||
let showPopover: () => void = $state(() => {})
|
||||
let instance: any = $state()
|
||||
let tippy: Maybe<TippyController> = $state()
|
||||
|
||||
$effect(() => {
|
||||
if ($term) {
|
||||
showPopover()
|
||||
tippy?.show()
|
||||
} else {
|
||||
popover?.hide()
|
||||
tippy?.hide()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
|
@ -82,9 +82,7 @@
|
|||
<input {autofocus} class="grow" type="text" bind:value={$term} onkeydown={onKeyDown} />
|
||||
{/if}
|
||||
<Tippy
|
||||
bind:popover
|
||||
bind:show={showPopover}
|
||||
bind:instance
|
||||
bind:controller={tippy}
|
||||
component={Suggestions}
|
||||
props={{
|
||||
term,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import type {Snippet} from "svelte"
|
||||
import type {Instance} from "tippy.js"
|
||||
import type {Maybe} from "@welshman/lib"
|
||||
import type {NativeEmoji} from "emoji-picker-element/shared"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import {tagSpec, tagValue} from "@welshman/util"
|
||||
|
|
@ -9,6 +9,7 @@
|
|||
import MenuDots from "@assets/icons/menu-dots.svg?dataurl"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Tippy from "@lib/components/Tippy.svelte"
|
||||
import type {TippyController} from "@lib/components/Tippy.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import EmojiButton from "@lib/components/EmojiButton.svelte"
|
||||
import ZapButton from "@app/components/ZapButton.svelte"
|
||||
|
|
@ -28,7 +29,9 @@
|
|||
|
||||
const shouldProtect = $relays.hasNip(url, 70)
|
||||
|
||||
const hidePopover = () => popover?.hide()
|
||||
const showPopover = () => tippy?.show()
|
||||
|
||||
const hidePopover = () => tippy?.hide()
|
||||
|
||||
const onEmoji = async (emoji: NativeEmoji) => {
|
||||
const protect = await shouldProtect
|
||||
|
|
@ -45,8 +48,7 @@
|
|||
return command.publishToRelays([url])
|
||||
}
|
||||
|
||||
let popover: Instance | undefined = $state()
|
||||
let showPopover: () => void = $state(() => {})
|
||||
let tippy: Maybe<TippyController> = $state()
|
||||
</script>
|
||||
|
||||
<div class="items-center join">
|
||||
|
|
@ -60,8 +62,7 @@
|
|||
</EmojiButton>
|
||||
<Button onclick={showPopover} class="flex join-item button button-neutral button-xs">
|
||||
<Tippy
|
||||
bind:popover
|
||||
bind:show={showPopover}
|
||||
bind:controller={tippy}
|
||||
component={EventMenu}
|
||||
props={{url, noun, event, customActions, onClick: hidePopover}}
|
||||
params={{trigger: "manual", interactive: true}}>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
<script lang="ts">
|
||||
import type {Snippet} from "svelte"
|
||||
import type {Instance} from "tippy.js"
|
||||
import type {Maybe} from "@welshman/lib"
|
||||
import MenuDots from "@assets/icons/menu-dots.svg?dataurl"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Tippy from "@lib/components/Tippy.svelte"
|
||||
import type {TippyController} from "@lib/components/Tippy.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import ProfileMenuList from "@app/components/ProfileMenuList.svelte"
|
||||
|
||||
|
|
@ -15,16 +16,16 @@
|
|||
|
||||
const {pubkey, url, customActions}: Props = $props()
|
||||
|
||||
const hidePopover = () => popover?.hide()
|
||||
const showPopover = () => tippy?.show()
|
||||
|
||||
let popover: Instance | undefined = $state()
|
||||
let showPopover: () => void = $state(() => {})
|
||||
const hidePopover = () => tippy?.hide()
|
||||
|
||||
let tippy: Maybe<TippyController> = $state()
|
||||
</script>
|
||||
|
||||
<Button onclick={showPopover} class="button button-circle button-ghost button-sm">
|
||||
<Tippy
|
||||
bind:popover
|
||||
bind:show={showPopover}
|
||||
bind:controller={tippy}
|
||||
component={ProfileMenuList}
|
||||
props={{pubkey, url, customActions, onClick: hidePopover}}
|
||||
params={{trigger: "manual", interactive: true, placement: "bottom-end"}}>
|
||||
|
|
|
|||
|
|
@ -3,12 +3,14 @@
|
|||
import type {Writable} from "svelte/store"
|
||||
import {type Instance} from "tippy.js"
|
||||
import {append, remove, uniq} from "@welshman/lib"
|
||||
import type {Maybe} from "@welshman/lib"
|
||||
import {decodePubkey} from "@lib/util"
|
||||
import Suggestions from "@lib/components/Suggestions.svelte"
|
||||
import CloseCircle from "@assets/icons/close-circle.svg?dataurl"
|
||||
import Magnifier from "@assets/icons/magnifier.svg?dataurl"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Tippy from "@lib/components/Tippy.svelte"
|
||||
import type {TippyController} from "@lib/components/Tippy.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import Badge from "@lib/components/Badge.svelte"
|
||||
import ProfileSuggestion from "@app/editor/ProfileSuggestion.svelte"
|
||||
|
|
@ -31,7 +33,7 @@
|
|||
|
||||
const selectPubkey = (pubkey: string) => {
|
||||
term.set("")
|
||||
popover?.hide()
|
||||
tippy?.hide()
|
||||
value = uniq(append(pubkey, value))
|
||||
}
|
||||
|
||||
|
|
@ -48,24 +50,22 @@
|
|||
}
|
||||
|
||||
const onKeyDown = (e: Event) => {
|
||||
if (instance?.onKeyDown(e)) {
|
||||
if (tippy?.content?.onKeyDown(e)) {
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
let label: Element | undefined = $state()
|
||||
let popover: Instance | undefined = $state()
|
||||
let showPopover: () => void = $state(() => {})
|
||||
let instance: any = $state()
|
||||
let tippy: Maybe<TippyController> = $state()
|
||||
|
||||
$effect(() => {
|
||||
// @ts-ignore
|
||||
oninput?.($term)
|
||||
|
||||
if ($term) {
|
||||
showPopover()
|
||||
tippy?.show()
|
||||
} else {
|
||||
popover?.hide()
|
||||
tippy?.hide()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
|
@ -97,9 +97,7 @@
|
|||
onkeydown={onKeyDown} />
|
||||
</label>
|
||||
<Tippy
|
||||
bind:popover
|
||||
bind:show={showPopover}
|
||||
bind:instance
|
||||
bind:controller={tippy}
|
||||
component={Suggestions}
|
||||
props={{
|
||||
term,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import type {Instance} from "tippy.js"
|
||||
import {writable} from "svelte/store"
|
||||
import type {Maybe} from "@welshman/lib"
|
||||
import type {EventContent} from "@welshman/util"
|
||||
import {isMobile, preventDefault} from "@lib/html"
|
||||
import GallerySend from "@assets/icons/gallery-send.svg?dataurl"
|
||||
|
|
@ -9,6 +9,7 @@
|
|||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import Tippy from "@lib/components/Tippy.svelte"
|
||||
import type {TippyController} from "@lib/components/Tippy.svelte"
|
||||
import Spinner from "@lib/components/Spinner.svelte"
|
||||
import ComposeMenu from "@app/components/ComposeMenu.svelte"
|
||||
import EditorContent from "@app/editor/EditorContent.svelte"
|
||||
|
|
@ -52,7 +53,9 @@
|
|||
|
||||
const uploadFiles = () => editor.then(ed => ed.chain().selectFiles().run())
|
||||
|
||||
const hidePopover = () => popover?.hide()
|
||||
const showPopover = () => tippy?.show()
|
||||
|
||||
const hidePopover = () => tippy?.hide()
|
||||
|
||||
const submit = async () => {
|
||||
if ($uploading) return
|
||||
|
|
@ -67,8 +70,7 @@
|
|||
ed.chain().clearContent().run()
|
||||
}
|
||||
|
||||
let popover: Instance | undefined = $state()
|
||||
let showPopover: () => void = $state(() => {})
|
||||
let tippy: Maybe<TippyController> = $state()
|
||||
let content = $state(
|
||||
initialValues?.type === "text" ? initialValues.value : (draftKey?.get()?.content ?? ""),
|
||||
)
|
||||
|
|
@ -124,8 +126,7 @@
|
|||
onclick={showPopover}
|
||||
class="join-item h-10 w-10 min-w-10 button button-neutral">
|
||||
<Tippy
|
||||
bind:popover
|
||||
bind:show={showPopover}
|
||||
bind:controller={tippy}
|
||||
component={ComposeMenu}
|
||||
props={{url, h, onClick: hidePopover}}
|
||||
params={{trigger: "manual", interactive: true}}>
|
||||
|
|
|
|||
|
|
@ -1,48 +1,42 @@
|
|||
<script lang="ts">
|
||||
import {type Instance} from "tippy.js"
|
||||
import {between} from "@welshman/lib"
|
||||
import type {Maybe} from "@welshman/lib"
|
||||
import MenuDots from "@assets/icons/menu-dots.svg?dataurl"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import Tippy from "@lib/components/Tippy.svelte"
|
||||
import type {TippyController} from "@lib/components/Tippy.svelte"
|
||||
import RoomItemMenu from "@app/components/RoomItemMenu.svelte"
|
||||
|
||||
const {url, event} = $props()
|
||||
|
||||
const onClick = () => popover?.hide()
|
||||
const open = () => tippy?.show()
|
||||
|
||||
const onShow = () => {
|
||||
visible = true
|
||||
}
|
||||
const onClick = () => tippy?.hide()
|
||||
|
||||
const onHidden = () => {
|
||||
visible = false
|
||||
}
|
||||
const onMouseMove = ({clientX, clientY}: MouseEvent) => {
|
||||
const rect = tippy?.rect()
|
||||
|
||||
const onMouseMove = ({clientX, clientY}: any) => {
|
||||
if (popover) {
|
||||
const {x, y, width, height} = popover.popper.getBoundingClientRect()
|
||||
if (rect) {
|
||||
const {x, y, width, height} = rect
|
||||
|
||||
if (!between([x, x + width], clientX) || !between([y - 50, y + height + 50], clientY)) {
|
||||
popover.hide()
|
||||
tippy!.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let popover: Instance | undefined = $state()
|
||||
let open: () => void = $state(() => {})
|
||||
let visible = $state(false)
|
||||
let tippy: Maybe<TippyController> = $state()
|
||||
</script>
|
||||
|
||||
<svelte:document onmousemove={visible ? onMouseMove : undefined} />
|
||||
<svelte:document onmousemove={tippy?.visible ? onMouseMove : undefined} />
|
||||
|
||||
<Button onclick={open} class="button button-xs button-neutral join-item">
|
||||
<Tippy
|
||||
bind:popover
|
||||
bind:show={open}
|
||||
bind:controller={tippy}
|
||||
component={RoomItemMenu}
|
||||
props={{url, event, onClick}}
|
||||
params={{trigger: "manual", interactive: true, onShow, onHidden}}>
|
||||
params={{trigger: "manual", interactive: true}}>
|
||||
<Icon icon={MenuDots} size={4} />
|
||||
</Tippy>
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import type {Writable} from "svelte/store"
|
||||
import type {Instance} from "tippy.js"
|
||||
import {remove, reject, spec, uniq} from "@welshman/lib"
|
||||
import type {Maybe} from "@welshman/lib"
|
||||
import {Topics, createSearch} from "@welshman/app"
|
||||
import {normalizeTopic} from "@lib/util"
|
||||
import Suggestions from "@lib/components/Suggestions.svelte"
|
||||
|
|
@ -10,6 +11,7 @@
|
|||
import Magnifier from "@assets/icons/magnifier.svg?dataurl"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Tippy from "@lib/components/Tippy.svelte"
|
||||
import type {TippyController} from "@lib/components/Tippy.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import TopicSuggestion from "@app/components/TopicSuggestion.svelte"
|
||||
import {app} from "@app/core"
|
||||
|
|
@ -41,7 +43,7 @@
|
|||
}
|
||||
|
||||
term.set("")
|
||||
popover?.hide()
|
||||
tippy?.hide()
|
||||
}
|
||||
|
||||
const removeTopic = (topic: string) => {
|
||||
|
|
@ -49,7 +51,7 @@
|
|||
}
|
||||
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
if (instance?.onKeyDown(e)) {
|
||||
if (tippy?.content?.onKeyDown(e)) {
|
||||
e.preventDefault()
|
||||
return
|
||||
}
|
||||
|
|
@ -62,19 +64,17 @@
|
|||
|
||||
const onBlur = () => {
|
||||
term.set("")
|
||||
popover?.hide()
|
||||
tippy?.hide()
|
||||
}
|
||||
|
||||
let label: Element | undefined = $state()
|
||||
let popover: Instance | undefined = $state()
|
||||
let showPopover: () => void = $state(() => {})
|
||||
let instance: any = $state()
|
||||
let tippy: Maybe<TippyController> = $state()
|
||||
|
||||
$effect(() => {
|
||||
if ($term.trim()) {
|
||||
showPopover()
|
||||
tippy?.show()
|
||||
} else {
|
||||
popover?.hide()
|
||||
tippy?.hide()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
|
@ -101,9 +101,7 @@
|
|||
onblur={onBlur} />
|
||||
</label>
|
||||
<Tippy
|
||||
bind:popover
|
||||
bind:show={showPopover}
|
||||
bind:instance
|
||||
bind:controller={tippy}
|
||||
component={Suggestions}
|
||||
props={{
|
||||
term,
|
||||
|
|
|
|||
|
|
@ -1,51 +1,45 @@
|
|||
<script lang="ts">
|
||||
import {type Instance} from "tippy.js"
|
||||
import type {NativeEmoji} from "emoji-picker-element/shared"
|
||||
import {between, throttle} from "@welshman/lib"
|
||||
import type {Maybe} from "@welshman/lib"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import Tippy from "@lib/components/Tippy.svelte"
|
||||
import type {TippyController} from "@lib/components/Tippy.svelte"
|
||||
import EmojiPicker from "@lib/components/EmojiPicker.svelte"
|
||||
|
||||
const {tippyParams = {}, ...props} = $props()
|
||||
|
||||
const open = () => tippy?.show()
|
||||
|
||||
const onClick = (emoji: NativeEmoji) => {
|
||||
props.onEmoji(emoji)
|
||||
popover?.hide()
|
||||
}
|
||||
|
||||
const onShow = () => {
|
||||
visible = true
|
||||
}
|
||||
|
||||
const onHidden = () => {
|
||||
visible = false
|
||||
tippy?.hide()
|
||||
}
|
||||
|
||||
const onMouseMove = throttle(300, ({clientX, clientY}: MouseEvent) => {
|
||||
if (popover) {
|
||||
const {x, y, width, height} = popover.popper.getBoundingClientRect()
|
||||
const rect = tippy?.rect()
|
||||
|
||||
if (rect) {
|
||||
const {x, y, width, height} = rect
|
||||
|
||||
if (!between([x, x + width], clientX) || !between([y - 100, y + height + 100], clientY)) {
|
||||
popover.hide()
|
||||
tippy!.hide()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
let popover: Instance | undefined = $state()
|
||||
let open: () => void = $state(() => {})
|
||||
let visible = $state(false)
|
||||
let tippy: Maybe<TippyController> = $state()
|
||||
</script>
|
||||
|
||||
<svelte:document onmousemove={visible ? onMouseMove : undefined} />
|
||||
<svelte:document onmousemove={tippy?.visible ? onMouseMove : undefined} />
|
||||
|
||||
<Button onclick={open} class={props.class}>
|
||||
<Tippy
|
||||
bind:popover
|
||||
bind:show={open}
|
||||
bind:controller={tippy}
|
||||
class="flex"
|
||||
component={EmojiPicker}
|
||||
props={{onClick}}
|
||||
params={{trigger: "manual", interactive: true, ...tippyParams, onShow, onHidden}}>
|
||||
params={{trigger: "manual", interactive: true, ...tippyParams}}>
|
||||
{@render props.children?.()}
|
||||
</Tippy>
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
<script lang="ts">
|
||||
import {type Instance} from "tippy.js"
|
||||
import {between, throttle} from "@welshman/lib"
|
||||
import type {Maybe} from "@welshman/lib"
|
||||
import {isMobile} from "@lib/html"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import Tippy from "@lib/components/Tippy.svelte"
|
||||
import type {TippyController} from "@lib/components/Tippy.svelte"
|
||||
import IconPickerModal from "@app/components/IconPickerModal.svelte"
|
||||
import IconPickerPopover from "@app/components/IconPickerPopover.svelte"
|
||||
import {pushModal, popModal} from "@app/modal"
|
||||
|
|
@ -14,7 +15,7 @@
|
|||
if (isMobile) {
|
||||
pushModal(IconPickerModal, {onSelect: onClick}, {nested: true})
|
||||
} else {
|
||||
showPopover()
|
||||
tippy?.show()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -22,7 +23,7 @@
|
|||
if (isMobile) {
|
||||
popModal()
|
||||
} else {
|
||||
popover?.hide()
|
||||
tippy?.hide()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -31,25 +32,21 @@
|
|||
close()
|
||||
}
|
||||
|
||||
const onMouseMove = throttle(300, ({clientX, clientY}: any) => {
|
||||
if (popover) {
|
||||
const {x, width} = popover.popper.getBoundingClientRect()
|
||||
const onMouseMove = throttle(300, ({clientX}: MouseEvent) => {
|
||||
const rect = tippy?.rect()
|
||||
|
||||
if (!between([x - 50, x + width + 50], clientX)) {
|
||||
popover.hide()
|
||||
}
|
||||
if (rect && !between([rect.x - 50, rect.x + rect.width + 50], clientX)) {
|
||||
tippy!.hide()
|
||||
}
|
||||
})
|
||||
|
||||
let popover: Instance | undefined = $state()
|
||||
let showPopover: () => void = $state(() => {})
|
||||
let tippy: Maybe<TippyController> = $state()
|
||||
</script>
|
||||
|
||||
<svelte:document onmousemove={onMouseMove} />
|
||||
|
||||
<Tippy
|
||||
bind:popover
|
||||
bind:show={showPopover}
|
||||
bind:controller={tippy}
|
||||
component={IconPickerPopover}
|
||||
props={{onSelect: onClick}}
|
||||
params={{trigger: "manual", interactive: true, placement: "top-end"}}>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
<script lang="ts">
|
||||
import type {Component} from "svelte"
|
||||
import type {Instance} from "tippy.js"
|
||||
import type {Maybe} from "@welshman/lib"
|
||||
import MenuDots from "@assets/icons/menu-dots.svg?dataurl"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Tippy from "@lib/components/Tippy.svelte"
|
||||
import type {TippyController} from "@lib/components/Tippy.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
|
||||
type Props = {
|
||||
|
|
@ -22,16 +23,16 @@
|
|||
"aria-label": ariaLabel,
|
||||
}: Props = $props()
|
||||
|
||||
const hidePopover = () => popover?.hide()
|
||||
const showPopover = () => tippy?.show()
|
||||
|
||||
let popover: Instance | undefined = $state()
|
||||
let showPopover: () => void = $state(() => {})
|
||||
const hidePopover = () => tippy?.hide()
|
||||
|
||||
let tippy: Maybe<TippyController> = $state()
|
||||
</script>
|
||||
|
||||
<Button class={className} aria-label={ariaLabel} onclick={showPopover}>
|
||||
<Tippy
|
||||
bind:popover
|
||||
bind:show={showPopover}
|
||||
bind:controller={tippy}
|
||||
{component}
|
||||
props={{...componentProps, onClick: hidePopover}}
|
||||
params={{trigger: "manual", interactive: true, placement: "bottom-end"}}>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,16 @@
|
|||
<script lang="ts" module>
|
||||
import type {Maybe} from "@welshman/lib"
|
||||
|
||||
export type TippyController<Content = Record<string, any>> = {
|
||||
show: () => void
|
||||
hide: () => void
|
||||
toggle: () => void
|
||||
visible: boolean
|
||||
rect: () => Maybe<DOMRect>
|
||||
content: Maybe<Content>
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import "tippy.js/animations/shift-away.css"
|
||||
|
||||
|
|
@ -11,13 +24,11 @@
|
|||
children = undefined,
|
||||
props = {},
|
||||
params = {},
|
||||
popover = $bindable(),
|
||||
instance = $bindable(),
|
||||
show = $bindable(),
|
||||
controller = $bindable(),
|
||||
...restProps
|
||||
} = $props()
|
||||
|
||||
let element: Element
|
||||
const target = document.createElement("div")
|
||||
|
||||
// `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
|
||||
|
|
@ -26,7 +37,7 @@
|
|||
|
||||
// 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`.
|
||||
// front — tippy is the one listening for it. A manual one can wait to be shown.
|
||||
const create = () => {
|
||||
popover ??= tippy(element, {
|
||||
content: target,
|
||||
|
|
@ -34,19 +45,45 @@
|
|||
appendTo: getTippyTarget(),
|
||||
trigger: isMobile ? "click" : "mouseenter focus",
|
||||
...params,
|
||||
onShow: (tippyInstance: Instance) => {
|
||||
instance ??= mount(component, {target, props: mountedProps})
|
||||
onShow: (instance: Instance) => {
|
||||
content ??= mount(component, {target, props: mountedProps})
|
||||
visible = true
|
||||
|
||||
return params.onShow?.(tippyInstance)
|
||||
return params.onShow?.(instance)
|
||||
},
|
||||
onHidden: (instance: Instance) => {
|
||||
visible = false
|
||||
|
||||
return params.onHidden?.(instance)
|
||||
},
|
||||
})
|
||||
|
||||
return popover
|
||||
}
|
||||
|
||||
const target = document.createElement("div")
|
||||
let element: Element
|
||||
let popover: Maybe<Instance>
|
||||
let content: Maybe<Record<string, any>>
|
||||
let visible = $state(false)
|
||||
|
||||
show = () => create().show()
|
||||
controller = {
|
||||
show: () => create().show(),
|
||||
hide: () => popover?.hide(),
|
||||
toggle: () => {
|
||||
if (visible) {
|
||||
popover?.hide()
|
||||
} else {
|
||||
create().show()
|
||||
}
|
||||
},
|
||||
rect: () => popover?.popper.getBoundingClientRect(),
|
||||
get visible() {
|
||||
return visible
|
||||
},
|
||||
get content() {
|
||||
return content
|
||||
},
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
Object.assign(mountedProps, props)
|
||||
|
|
@ -60,8 +97,8 @@
|
|||
return () => {
|
||||
popover?.destroy()
|
||||
|
||||
if (instance) {
|
||||
unmount(instance)
|
||||
if (content) {
|
||||
unmount(content)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in a new issue