Optimize tippy by lazily instantiation
This commit is contained in:
parent
ee09c975f8
commit
749e87f228
14 changed files with 66 additions and 29 deletions
|
|
@ -56,11 +56,12 @@
|
||||||
if (popoverIsVisible) {
|
if (popoverIsVisible) {
|
||||||
popover?.hide()
|
popover?.hide()
|
||||||
} else {
|
} else {
|
||||||
popover?.show()
|
showPopover()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let popover: Instance | undefined = $state()
|
let popover: Instance | undefined = $state()
|
||||||
|
let showPopover: () => void = $state(() => {})
|
||||||
let popoverIsVisible = $state(false)
|
let popoverIsVisible = $state(false)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -71,6 +72,7 @@
|
||||||
{#if !isMobile}
|
{#if !isMobile}
|
||||||
<Tippy
|
<Tippy
|
||||||
bind:popover
|
bind:popover
|
||||||
|
bind:show={showPopover}
|
||||||
component={ChatMessageMenu}
|
component={ChatMessageMenu}
|
||||||
props={{event, pubkeys, popover, replyTo, edit}}
|
props={{event, pubkeys, popover, replyTo, edit}}
|
||||||
params={{
|
params={{
|
||||||
|
|
|
||||||
|
|
@ -55,11 +55,12 @@
|
||||||
|
|
||||||
let wrapper: Element | undefined = $state()
|
let wrapper: Element | undefined = $state()
|
||||||
let popover: Instance | undefined = $state()
|
let popover: Instance | undefined = $state()
|
||||||
|
let showPopover: () => void = $state(() => {})
|
||||||
let instance: any = $state()
|
let instance: any = $state()
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if ($term) {
|
if ($term) {
|
||||||
popover?.show()
|
showPopover()
|
||||||
} else {
|
} else {
|
||||||
popover?.hide()
|
popover?.hide()
|
||||||
}
|
}
|
||||||
|
|
@ -82,6 +83,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
<Tippy
|
<Tippy
|
||||||
bind:popover
|
bind:popover
|
||||||
|
bind:show={showPopover}
|
||||||
bind:instance
|
bind:instance
|
||||||
component={Suggestions}
|
component={Suggestions}
|
||||||
props={{
|
props={{
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,6 @@
|
||||||
|
|
||||||
const shouldProtect = $relays.hasNip(url, 70)
|
const shouldProtect = $relays.hasNip(url, 70)
|
||||||
|
|
||||||
const showPopover = () => popover?.show()
|
|
||||||
|
|
||||||
const hidePopover = () => popover?.hide()
|
const hidePopover = () => popover?.hide()
|
||||||
|
|
||||||
const onEmoji = async (emoji: NativeEmoji) => {
|
const onEmoji = async (emoji: NativeEmoji) => {
|
||||||
|
|
@ -48,6 +46,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
let popover: Instance | undefined = $state()
|
let popover: Instance | undefined = $state()
|
||||||
|
let showPopover: () => void = $state(() => {})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="items-center join">
|
<div class="items-center join">
|
||||||
|
|
@ -62,6 +61,7 @@
|
||||||
<Button onclick={showPopover} class="flex join-item button button-neutral button-xs">
|
<Button onclick={showPopover} class="flex join-item button button-neutral button-xs">
|
||||||
<Tippy
|
<Tippy
|
||||||
bind:popover
|
bind:popover
|
||||||
|
bind:show={showPopover}
|
||||||
component={EventMenu}
|
component={EventMenu}
|
||||||
props={{url, noun, event, customActions, onClick: hidePopover}}
|
props={{url, noun, event, customActions, onClick: hidePopover}}
|
||||||
params={{trigger: "manual", interactive: true}}>
|
params={{trigger: "manual", interactive: true}}>
|
||||||
|
|
|
||||||
|
|
@ -15,16 +15,16 @@
|
||||||
|
|
||||||
const {pubkey, url, customActions}: Props = $props()
|
const {pubkey, url, customActions}: Props = $props()
|
||||||
|
|
||||||
const showPopover = () => popover?.show()
|
|
||||||
|
|
||||||
const hidePopover = () => popover?.hide()
|
const hidePopover = () => popover?.hide()
|
||||||
|
|
||||||
let popover: Instance | undefined = $state()
|
let popover: Instance | undefined = $state()
|
||||||
|
let showPopover: () => void = $state(() => {})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Button onclick={showPopover} class="button button-circle button-ghost button-sm">
|
<Button onclick={showPopover} class="button button-circle button-ghost button-sm">
|
||||||
<Tippy
|
<Tippy
|
||||||
bind:popover
|
bind:popover
|
||||||
|
bind:show={showPopover}
|
||||||
component={ProfileMenuList}
|
component={ProfileMenuList}
|
||||||
props={{pubkey, url, customActions, onClick: hidePopover}}
|
props={{pubkey, url, customActions, onClick: hidePopover}}
|
||||||
params={{trigger: "manual", interactive: true, placement: "bottom-end"}}>
|
params={{trigger: "manual", interactive: true, placement: "bottom-end"}}>
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@
|
||||||
|
|
||||||
let label: Element | undefined = $state()
|
let label: Element | undefined = $state()
|
||||||
let popover: Instance | undefined = $state()
|
let popover: Instance | undefined = $state()
|
||||||
|
let showPopover: () => void = $state(() => {})
|
||||||
let instance: any = $state()
|
let instance: any = $state()
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
|
|
@ -62,7 +63,7 @@
|
||||||
oninput?.($term)
|
oninput?.($term)
|
||||||
|
|
||||||
if ($term) {
|
if ($term) {
|
||||||
popover?.show()
|
showPopover()
|
||||||
} else {
|
} else {
|
||||||
popover?.hide()
|
popover?.hide()
|
||||||
}
|
}
|
||||||
|
|
@ -97,6 +98,7 @@
|
||||||
</label>
|
</label>
|
||||||
<Tippy
|
<Tippy
|
||||||
bind:popover
|
bind:popover
|
||||||
|
bind:show={showPopover}
|
||||||
bind:instance
|
bind:instance
|
||||||
component={Suggestions}
|
component={Suggestions}
|
||||||
props={{
|
props={{
|
||||||
|
|
|
||||||
|
|
@ -52,8 +52,6 @@
|
||||||
|
|
||||||
const uploadFiles = () => editor.then(ed => ed.chain().selectFiles().run())
|
const uploadFiles = () => editor.then(ed => ed.chain().selectFiles().run())
|
||||||
|
|
||||||
const showPopover = () => popover?.show()
|
|
||||||
|
|
||||||
const hidePopover = () => popover?.hide()
|
const hidePopover = () => popover?.hide()
|
||||||
|
|
||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
|
|
@ -70,6 +68,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
let popover: Instance | undefined = $state()
|
let popover: Instance | undefined = $state()
|
||||||
|
let showPopover: () => void = $state(() => {})
|
||||||
let content = $state(
|
let content = $state(
|
||||||
initialValues?.type === "text" ? initialValues.value : (draftKey?.get()?.content ?? ""),
|
initialValues?.type === "text" ? initialValues.value : (draftKey?.get()?.content ?? ""),
|
||||||
)
|
)
|
||||||
|
|
@ -126,6 +125,7 @@
|
||||||
class="join-item h-10 w-10 min-w-10 button button-neutral">
|
class="join-item h-10 w-10 min-w-10 button button-neutral">
|
||||||
<Tippy
|
<Tippy
|
||||||
bind:popover
|
bind:popover
|
||||||
|
bind:show={showPopover}
|
||||||
component={ComposeMenu}
|
component={ComposeMenu}
|
||||||
props={{url, h, onClick: hidePopover}}
|
props={{url, h, onClick: hidePopover}}
|
||||||
params={{trigger: "manual", interactive: true}}>
|
params={{trigger: "manual", interactive: true}}>
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,6 @@
|
||||||
|
|
||||||
const {url, event} = $props()
|
const {url, event} = $props()
|
||||||
|
|
||||||
const open = () => popover?.show()
|
|
||||||
|
|
||||||
const onClick = () => popover?.hide()
|
const onClick = () => popover?.hide()
|
||||||
|
|
||||||
const onShow = () => {
|
const onShow = () => {
|
||||||
|
|
@ -32,6 +30,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
let popover: Instance | undefined = $state()
|
let popover: Instance | undefined = $state()
|
||||||
|
let open: () => void = $state(() => {})
|
||||||
let visible = $state(false)
|
let visible = $state(false)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -40,6 +39,7 @@
|
||||||
<Button onclick={open} class="button button-xs button-neutral join-item">
|
<Button onclick={open} class="button button-xs button-neutral join-item">
|
||||||
<Tippy
|
<Tippy
|
||||||
bind:popover
|
bind:popover
|
||||||
|
bind:show={open}
|
||||||
component={RoomItemMenu}
|
component={RoomItemMenu}
|
||||||
props={{url, event, onClick}}
|
props={{url, event, onClick}}
|
||||||
params={{trigger: "manual", interactive: true, onShow, onHidden}}>
|
params={{trigger: "manual", interactive: true, onShow, onHidden}}>
|
||||||
|
|
|
||||||
|
|
@ -67,11 +67,12 @@
|
||||||
|
|
||||||
let label: Element | undefined = $state()
|
let label: Element | undefined = $state()
|
||||||
let popover: Instance | undefined = $state()
|
let popover: Instance | undefined = $state()
|
||||||
|
let showPopover: () => void = $state(() => {})
|
||||||
let instance: any = $state()
|
let instance: any = $state()
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if ($term.trim()) {
|
if ($term.trim()) {
|
||||||
popover?.show()
|
showPopover()
|
||||||
} else {
|
} else {
|
||||||
popover?.hide()
|
popover?.hide()
|
||||||
}
|
}
|
||||||
|
|
@ -101,6 +102,7 @@
|
||||||
</label>
|
</label>
|
||||||
<Tippy
|
<Tippy
|
||||||
bind:popover
|
bind:popover
|
||||||
|
bind:show={showPopover}
|
||||||
bind:instance
|
bind:instance
|
||||||
component={Suggestions}
|
component={Suggestions}
|
||||||
props={{
|
props={{
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,6 @@
|
||||||
|
|
||||||
const {tippyParams = {}, ...props} = $props()
|
const {tippyParams = {}, ...props} = $props()
|
||||||
|
|
||||||
const open = () => popover?.show()
|
|
||||||
|
|
||||||
const onClick = (emoji: NativeEmoji) => {
|
const onClick = (emoji: NativeEmoji) => {
|
||||||
props.onEmoji(emoji)
|
props.onEmoji(emoji)
|
||||||
popover?.hide()
|
popover?.hide()
|
||||||
|
|
@ -34,6 +32,7 @@
|
||||||
})
|
})
|
||||||
|
|
||||||
let popover: Instance | undefined = $state()
|
let popover: Instance | undefined = $state()
|
||||||
|
let open: () => void = $state(() => {})
|
||||||
let visible = $state(false)
|
let visible = $state(false)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -42,6 +41,7 @@
|
||||||
<Button onclick={open} class={props.class}>
|
<Button onclick={open} class={props.class}>
|
||||||
<Tippy
|
<Tippy
|
||||||
bind:popover
|
bind:popover
|
||||||
|
bind:show={open}
|
||||||
class="flex"
|
class="flex"
|
||||||
component={EmojiPicker}
|
component={EmojiPicker}
|
||||||
props={{onClick}}
|
props={{onClick}}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
pushModal(IconPickerModal, {onSelect: onClick}, {nested: true})
|
pushModal(IconPickerModal, {onSelect: onClick}, {nested: true})
|
||||||
} else {
|
} else {
|
||||||
popover?.show()
|
showPopover()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -42,12 +42,14 @@
|
||||||
})
|
})
|
||||||
|
|
||||||
let popover: Instance | undefined = $state()
|
let popover: Instance | undefined = $state()
|
||||||
|
let showPopover: () => void = $state(() => {})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:document onmousemove={onMouseMove} />
|
<svelte:document onmousemove={onMouseMove} />
|
||||||
|
|
||||||
<Tippy
|
<Tippy
|
||||||
bind:popover
|
bind:popover
|
||||||
|
bind:show={showPopover}
|
||||||
component={IconPickerPopover}
|
component={IconPickerPopover}
|
||||||
props={{onSelect: onClick}}
|
props={{onSelect: onClick}}
|
||||||
params={{trigger: "manual", interactive: true, placement: "top-end"}}>
|
params={{trigger: "manual", interactive: true, placement: "top-end"}}>
|
||||||
|
|
|
||||||
|
|
@ -22,16 +22,16 @@
|
||||||
"aria-label": ariaLabel,
|
"aria-label": ariaLabel,
|
||||||
}: Props = $props()
|
}: Props = $props()
|
||||||
|
|
||||||
const showPopover = () => popover?.show()
|
|
||||||
|
|
||||||
const hidePopover = () => popover?.hide()
|
const hidePopover = () => popover?.hide()
|
||||||
|
|
||||||
let popover: Instance | undefined = $state()
|
let popover: Instance | undefined = $state()
|
||||||
|
let showPopover: () => void = $state(() => {})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Button class={className} aria-label={ariaLabel} onclick={showPopover}>
|
<Button class={className} aria-label={ariaLabel} onclick={showPopover}>
|
||||||
<Tippy
|
<Tippy
|
||||||
bind:popover
|
bind:popover
|
||||||
|
bind:show={showPopover}
|
||||||
{component}
|
{component}
|
||||||
props={{...componentProps, onClick: hidePopover}}
|
props={{...componentProps, onClick: hidePopover}}
|
||||||
params={{trigger: "manual", interactive: true, placement: "bottom-end"}}>
|
params={{trigger: "manual", interactive: true, placement: "bottom-end"}}>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
import tippy from "tippy.js"
|
import tippy from "tippy.js"
|
||||||
import type {Instance} from "tippy.js"
|
import type {Instance} from "tippy.js"
|
||||||
import {onMount, mount, unmount} from "svelte"
|
import {onMount, mount, unmount} from "svelte"
|
||||||
import {isMobile} from "@lib/html"
|
import {getTippyTarget, isMobile} from "@lib/html"
|
||||||
|
|
||||||
let {
|
let {
|
||||||
component,
|
component,
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
params = {},
|
params = {},
|
||||||
popover = $bindable(),
|
popover = $bindable(),
|
||||||
instance = $bindable(),
|
instance = $bindable(),
|
||||||
|
show = $bindable(),
|
||||||
...restProps
|
...restProps
|
||||||
} = $props()
|
} = $props()
|
||||||
|
|
||||||
|
|
@ -23,17 +24,14 @@
|
||||||
// the props it was first mounted with, showing stale data after the source updates.
|
// the props it was first mounted with, showing stale data after the source updates.
|
||||||
const mountedProps = $state({...props})
|
const mountedProps = $state({...props})
|
||||||
|
|
||||||
$effect(() => {
|
// Building a tippy costs a popper element and a set of listeners, which is wasted on the
|
||||||
Object.assign(mountedProps, props)
|
// 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 = () => {
|
||||||
onMount(() => {
|
popover ??= tippy(element, {
|
||||||
const target = document.createElement("div")
|
|
||||||
|
|
||||||
popover = tippy(element, {
|
|
||||||
content: target,
|
content: target,
|
||||||
animation: "shift-away",
|
animation: "shift-away",
|
||||||
appendTo: document.querySelector(".tippy-target")!,
|
appendTo: getTippyTarget(),
|
||||||
trigger: isMobile ? "click" : "mouseenter focus",
|
trigger: isMobile ? "click" : "mouseenter focus",
|
||||||
...params,
|
...params,
|
||||||
onShow: (tippyInstance: Instance) => {
|
onShow: (tippyInstance: Instance) => {
|
||||||
|
|
@ -43,6 +41,22 @@
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return popover
|
||||||
|
}
|
||||||
|
|
||||||
|
const target = document.createElement("div")
|
||||||
|
|
||||||
|
show = () => create().show()
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
Object.assign(mountedProps, props)
|
||||||
|
})
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if (params.trigger !== "manual") {
|
||||||
|
create()
|
||||||
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
popover?.destroy()
|
popover?.destroy()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@
|
||||||
|
|
||||||
import tippy from "tippy.js"
|
import tippy from "tippy.js"
|
||||||
import {onMount} from "svelte"
|
import {onMount} from "svelte"
|
||||||
import {isMobile} from "@lib/html"
|
import {getTippyTarget, isMobile} from "@lib/html"
|
||||||
|
|
||||||
let {
|
let {
|
||||||
content,
|
content,
|
||||||
|
|
@ -50,7 +50,7 @@
|
||||||
interactive,
|
interactive,
|
||||||
animation: "shift-away",
|
animation: "shift-away",
|
||||||
theme: "tooltip",
|
theme: "tooltip",
|
||||||
appendTo: document.querySelector(".tippy-target")!,
|
appendTo: getTippyTarget(),
|
||||||
trigger: isMobile ? "click" : "mouseenter focus",
|
trigger: isMobile ? "click" : "mouseenter focus",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import {readable} from "svelte/store"
|
import {readable} from "svelte/store"
|
||||||
import {sleep, randomId} from "@welshman/lib"
|
import {sleep, randomId} from "@welshman/lib"
|
||||||
|
import type {Maybe} from "@welshman/lib"
|
||||||
import {Capacitor} from "@capacitor/core"
|
import {Capacitor} from "@capacitor/core"
|
||||||
export {preventDefault, stopPropagation} from "svelte/legacy"
|
export {preventDefault, stopPropagation} from "svelte/legacy"
|
||||||
|
|
||||||
|
|
@ -152,6 +153,18 @@ export const createScroller = ({
|
||||||
|
|
||||||
export const isMobile = "ontouchstart" in document.documentElement
|
export const isMobile = "ontouchstart" in document.documentElement
|
||||||
|
|
||||||
|
// The layout's single popover host. Cached, because with a couple of popovers per chat row
|
||||||
|
// this runs thousands of times against a document that is itself thousands of nodes.
|
||||||
|
let tippyTarget: Maybe<Element>
|
||||||
|
|
||||||
|
export const getTippyTarget = () => {
|
||||||
|
if (!tippyTarget?.isConnected) {
|
||||||
|
tippyTarget = document.querySelector(".tippy-target")!
|
||||||
|
}
|
||||||
|
|
||||||
|
return tippyTarget
|
||||||
|
}
|
||||||
|
|
||||||
export const downloadText = async (filename: string, text: string) => {
|
export const downloadText = async (filename: string, text: string) => {
|
||||||
// The <a download> blob trick is a no-op in native WebViews (Android in
|
// The <a download> blob trick is a no-op in native WebViews (Android in
|
||||||
// particular never triggers a download), so on device we write the file and
|
// particular never triggers a download), so on device we write the file and
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue