Pair content-visibility with its render-ahead observer in one component
This commit is contained in:
parent
1485e290e9
commit
dd7c15153e
14 changed files with 96 additions and 24 deletions
|
|
@ -4,6 +4,7 @@
|
||||||
import {getAddress} from "@welshman/util"
|
import {getAddress} from "@welshman/util"
|
||||||
import {Article} from "@welshman/domain"
|
import {Article} from "@welshman/domain"
|
||||||
import Link from "@lib/components/Link.svelte"
|
import Link from "@lib/components/Link.svelte"
|
||||||
|
import Cv from "@lib/components/Cv.svelte"
|
||||||
import Content from "@app/components/Content.svelte"
|
import Content from "@app/components/Content.svelte"
|
||||||
import ProfileLink from "@app/components/ProfileLink.svelte"
|
import ProfileLink from "@app/components/ProfileLink.svelte"
|
||||||
import ArticleActions from "@app/components/ArticleActions.svelte"
|
import ArticleActions from "@app/components/ArticleActions.svelte"
|
||||||
|
|
@ -27,7 +28,7 @@
|
||||||
const image = $derived(article.image())
|
const image = $derived(article.image())
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div data-component="ArticleItem" class="cv relative w-full card card-interactive">
|
<Cv data-component="ArticleItem" class="relative w-full card card-interactive">
|
||||||
<!-- An overlay rather than a wrapper: the card carries a profile button and the room and action
|
<!-- An overlay rather than a wrapper: the card carries a profile button and the room and action
|
||||||
links, and none of those may sit inside an anchor. -->
|
links, and none of those may sit inside an anchor. -->
|
||||||
<Link class="absolute inset-0 rounded-2xl" href={path} aria-label={title || "Untitled"} />
|
<Link class="absolute inset-0 rounded-2xl" href={path} aria-label={title || "Untitled"} />
|
||||||
|
|
@ -58,4 +59,4 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Cv>
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
import {getAddress, tagValue, tagSpec} from "@welshman/util"
|
import {getAddress, tagValue, tagSpec} from "@welshman/util"
|
||||||
import Link from "@lib/components/Link.svelte"
|
import Link from "@lib/components/Link.svelte"
|
||||||
|
import Cv from "@lib/components/Cv.svelte"
|
||||||
import CalendarEventActions from "@app/components/CalendarEventActions.svelte"
|
import CalendarEventActions from "@app/components/CalendarEventActions.svelte"
|
||||||
import type {FeedContext} from "@app/feeds"
|
import type {FeedContext} from "@app/feeds"
|
||||||
import CalendarEventHeader from "@app/components/CalendarEventHeader.svelte"
|
import CalendarEventHeader from "@app/components/CalendarEventHeader.svelte"
|
||||||
|
|
@ -22,7 +23,7 @@
|
||||||
const h = $derived(tagValue(tagSpec("h"), event.tags))
|
const h = $derived(tagValue(tagSpec("h"), event.tags))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Link class="cv relative flex flex-col gap-3 card card-interactive w-full" href={path}>
|
<Cv tag={Link} class="relative flex flex-col gap-3 card card-interactive w-full" href={path}>
|
||||||
<UnreadDot {path} class="absolute right-3 top-3" />
|
<UnreadDot {path} class="absolute right-3 top-3" />
|
||||||
<CalendarEventHeader {event} />
|
<CalendarEventHeader {event} />
|
||||||
<div class="flex w-full flex-col items-end justify-between gap-2 sm:flex-row">
|
<div class="flex w-full flex-col items-end justify-between gap-2 sm:flex-row">
|
||||||
|
|
@ -34,4 +35,4 @@
|
||||||
</span>
|
</span>
|
||||||
<CalendarEventActions showActivity {url} {event} {context} />
|
<CalendarEventActions showActivity {url} {event} {context} />
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Cv>
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
import {getAddress} from "@welshman/util"
|
import {getAddress} from "@welshman/util"
|
||||||
import {Classified} from "@welshman/domain"
|
import {Classified} from "@welshman/domain"
|
||||||
import Link from "@lib/components/Link.svelte"
|
import Link from "@lib/components/Link.svelte"
|
||||||
|
import Cv from "@lib/components/Cv.svelte"
|
||||||
import CurrencySymbol from "@lib/components/CurrencySymbol.svelte"
|
import CurrencySymbol from "@lib/components/CurrencySymbol.svelte"
|
||||||
import {reader} from "@app/core"
|
import {reader} from "@app/core"
|
||||||
import ContentLinkBlock from "@app/components/ContentLinkBlock.svelte"
|
import ContentLinkBlock from "@app/components/ContentLinkBlock.svelte"
|
||||||
|
|
@ -31,7 +32,7 @@
|
||||||
const price = $derived(classified.price())
|
const price = $derived(classified.price())
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Link class="cv relative flex flex-col gap-2 card card-interactive w-full" href={path}>
|
<Cv tag={Link} class="relative flex flex-col gap-2 card card-interactive w-full" href={path}>
|
||||||
<UnreadDot {path} class="absolute right-3 top-3" />
|
<UnreadDot {path} class="absolute right-3 top-3" />
|
||||||
{#if title}
|
{#if title}
|
||||||
<div class="flex w-full items-center justify-between gap-2">
|
<div class="flex w-full items-center justify-between gap-2">
|
||||||
|
|
@ -64,4 +65,4 @@
|
||||||
</span>
|
</span>
|
||||||
<ClassifiedActions showActivity {url} {event} {context} />
|
<ClassifiedActions showActivity {url} {event} {context} />
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Cv>
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
import {ZapGoal} from "@welshman/domain"
|
import {ZapGoal} from "@welshman/domain"
|
||||||
import Link from "@lib/components/Link.svelte"
|
import Link from "@lib/components/Link.svelte"
|
||||||
|
import Cv from "@lib/components/Cv.svelte"
|
||||||
import {reader} from "@app/core"
|
import {reader} from "@app/core"
|
||||||
import Content from "@app/components/Content.svelte"
|
import Content from "@app/components/Content.svelte"
|
||||||
import ProfileLink from "@app/components/ProfileLink.svelte"
|
import ProfileLink from "@app/components/ProfileLink.svelte"
|
||||||
|
|
@ -28,7 +29,7 @@
|
||||||
const h = goal.room()
|
const h = goal.room()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Link class="cv relative flex flex-col gap-2 card card-interactive w-full" href={path}>
|
<Cv tag={Link} class="relative flex flex-col gap-2 card card-interactive w-full" href={path}>
|
||||||
<UnreadDot {path} class="absolute right-3 top-3" />
|
<UnreadDot {path} class="absolute right-3 top-3" />
|
||||||
<p class="text-2xl">{title}</p>
|
<p class="text-2xl">{title}</p>
|
||||||
<Content
|
<Content
|
||||||
|
|
@ -47,4 +48,4 @@
|
||||||
</span>
|
</span>
|
||||||
<GoalActions showActivity {url} {event} {context} />
|
<GoalActions showActivity {url} {event} {context} />
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Cv>
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@
|
||||||
let muted = $state($isEventMuted(event))
|
let muted = $state($isEventMuted(event))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-col gap-2 {restProps.class}" style={restProps.style}>
|
<div {...restProps} class={cx("flex flex-col gap-2", restProps.class)}>
|
||||||
{#if muted}
|
{#if muted}
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div class="flex gap-2 relative">
|
<div class="flex gap-2 relative">
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
import SmileCircle from "@assets/icons/smile-circle.svg?dataurl"
|
import SmileCircle from "@assets/icons/smile-circle.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import EmojiButton from "@lib/components/EmojiButton.svelte"
|
import EmojiButton from "@lib/components/EmojiButton.svelte"
|
||||||
|
import Cv from "@lib/components/Cv.svelte"
|
||||||
import NoteContent from "@app/components/NoteContent.svelte"
|
import NoteContent from "@app/components/NoteContent.svelte"
|
||||||
import {publishReaction, retractReaction} from "@app/reactions"
|
import {publishReaction, retractReaction} from "@app/reactions"
|
||||||
import NoteCard from "@app/components/NoteCard.svelte"
|
import NoteCard from "@app/components/NoteCard.svelte"
|
||||||
|
|
@ -33,7 +34,7 @@
|
||||||
const onEmoji = (emoji: NativeEmoji) => createReaction({content: emoji.unicode, tags: []})
|
const onEmoji = (emoji: NativeEmoji) => createReaction({content: emoji.unicode, tags: []})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<NoteCard {event} {url} class="cv card card-interactive">
|
<Cv tag={NoteCard} {event} {url} class="card card-interactive">
|
||||||
<NoteContent {event} expandMode="inline" />
|
<NoteContent {event} expandMode="inline" />
|
||||||
<div class="flex w-full justify-between gap-2">
|
<div class="flex w-full justify-between gap-2">
|
||||||
<ReactionSummary
|
<ReactionSummary
|
||||||
|
|
@ -52,4 +53,4 @@
|
||||||
</ReactionSummary>
|
</ReactionSummary>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
</div>
|
</div>
|
||||||
</NoteCard>
|
</Cv>
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
import UserCircle from "@assets/icons/user-circle.svg?dataurl"
|
import UserCircle from "@assets/icons/user-circle.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Link from "@lib/components/Link.svelte"
|
import Link from "@lib/components/Link.svelte"
|
||||||
|
import Cv from "@lib/components/Cv.svelte"
|
||||||
import Profile from "@app/components/Profile.svelte"
|
import Profile from "@app/components/Profile.svelte"
|
||||||
import ProfileAbout from "@app/components/ProfileAbout.svelte"
|
import ProfileAbout from "@app/components/ProfileAbout.svelte"
|
||||||
import ProfileBadges from "@app/components/ProfileBadges.svelte"
|
import ProfileBadges from "@app/components/ProfileBadges.svelte"
|
||||||
|
|
@ -15,7 +16,7 @@
|
||||||
const {pubkey, url}: Props = $props()
|
const {pubkey, url}: Props = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="cv card card-interactive flex flex-col gap-4">
|
<Cv class="card card-interactive flex flex-col gap-4">
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<Profile {pubkey} {url} />
|
<Profile {pubkey} {url} />
|
||||||
<Link href={makeProfilePath(pubkey)} class="button button-primary hidden sm:flex">
|
<Link href={makeProfilePath(pubkey)} class="button button-primary hidden sm:flex">
|
||||||
|
|
@ -29,4 +30,4 @@
|
||||||
<Icon icon={UserCircle} />
|
<Icon icon={UserCircle} />
|
||||||
View Profile
|
View Profile
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</Cv>
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
import {tagSpec, tagValue} from "@welshman/util"
|
import {tagSpec, tagValue} from "@welshman/util"
|
||||||
import Link from "@lib/components/Link.svelte"
|
import Link from "@lib/components/Link.svelte"
|
||||||
|
import Cv from "@lib/components/Cv.svelte"
|
||||||
import NoteContent from "@app/components/NoteContent.svelte"
|
import NoteContent from "@app/components/NoteContent.svelte"
|
||||||
import PollActions from "@app/components/PollActions.svelte"
|
import PollActions from "@app/components/PollActions.svelte"
|
||||||
import type {FeedContext} from "@app/feeds"
|
import type {FeedContext} from "@app/feeds"
|
||||||
|
|
@ -22,7 +23,7 @@
|
||||||
const h = tagValue(tagSpec("h"), event.tags)
|
const h = tagValue(tagSpec("h"), event.tags)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Link class="cv relative flex flex-col gap-2 card card-interactive w-full" href={path}>
|
<Cv tag={Link} class="relative flex flex-col gap-2 card card-interactive w-full" href={path}>
|
||||||
<UnreadDot {path} class="absolute right-3 top-3" />
|
<UnreadDot {path} class="absolute right-3 top-3" />
|
||||||
<NoteContent {event} {url} />
|
<NoteContent {event} {url} />
|
||||||
<div class="flex w-full flex-col items-end justify-between gap-2 sm:flex-row">
|
<div class="flex w-full flex-col items-end justify-between gap-2 sm:flex-row">
|
||||||
|
|
@ -34,4 +35,4 @@
|
||||||
</span>
|
</span>
|
||||||
<PollActions showActivity {url} {event} {context} />
|
<PollActions showActivity {url} {event} {context} />
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Cv>
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
|
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
|
import Cv from "@lib/components/Cv.svelte"
|
||||||
import NoteContentMinimal from "@app/components/NoteContentMinimal.svelte"
|
import NoteContentMinimal from "@app/components/NoteContentMinimal.svelte"
|
||||||
import ProfileCircle from "@app/components/ProfileCircle.svelte"
|
import ProfileCircle from "@app/components/ProfileCircle.svelte"
|
||||||
import RoomNameWithImage from "@app/components/RoomNameWithImage.svelte"
|
import RoomNameWithImage from "@app/components/RoomNameWithImage.svelte"
|
||||||
|
|
@ -25,7 +26,7 @@
|
||||||
const onClick = () => navigate(h ? makeRoomPath(url, h) : makeSpaceChatPath(url))
|
const onClick = () => navigate(h ? makeRoomPath(url, h) : makeSpaceChatPath(url))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Button class="cv card card-interactive" onclick={onClick}>
|
<Cv tag={Button} class="card card-interactive" onclick={onClick}>
|
||||||
<div class="flex flex-col gap-3">
|
<div class="flex flex-col gap-3">
|
||||||
<div class="flex items-center gap-2 text-sm">
|
<div class="flex items-center gap-2 text-sm">
|
||||||
{#if h}
|
{#if h}
|
||||||
|
|
@ -55,4 +56,4 @@
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Cv>
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
import {LOCAL_RELAY_URL} from "@welshman/net"
|
import {LOCAL_RELAY_URL} from "@welshman/net"
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
import Card from "@lib/components/Card.svelte"
|
import Card from "@lib/components/Card.svelte"
|
||||||
|
import Cv from "@lib/components/Cv.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import Profile from "@app/components/Profile.svelte"
|
import Profile from "@app/components/Profile.svelte"
|
||||||
import ProfileName from "@app/components/ProfileName.svelte"
|
import ProfileName from "@app/components/ProfileName.svelte"
|
||||||
|
|
@ -36,7 +37,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Card sm class="cv flex flex-col gap-4">
|
<Cv tag={Card} sm class="flex flex-col gap-4">
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<div>
|
<div>
|
||||||
<ProfileName pubkey={event.pubkey} {url} />
|
<ProfileName pubkey={event.pubkey} {url} />
|
||||||
|
|
@ -80,4 +81,4 @@
|
||||||
<Profile pubkey={ptag[1]} />
|
<Profile pubkey={ptag[1]} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Cv>
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
import {COMMENT, tagSpec, tagValue} from "@welshman/util"
|
import {COMMENT, tagSpec, tagValue} from "@welshman/util"
|
||||||
import Link from "@lib/components/Link.svelte"
|
import Link from "@lib/components/Link.svelte"
|
||||||
|
import Cv from "@lib/components/Cv.svelte"
|
||||||
import ProfileCircle from "@app/components/ProfileCircle.svelte"
|
import ProfileCircle from "@app/components/ProfileCircle.svelte"
|
||||||
import ProfileName from "@app/components/ProfileName.svelte"
|
import ProfileName from "@app/components/ProfileName.svelte"
|
||||||
import UnreadDot from "@app/components/UnreadDot.svelte"
|
import UnreadDot from "@app/components/UnreadDot.svelte"
|
||||||
|
|
@ -30,9 +31,10 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if stacked}
|
{#if stacked}
|
||||||
<Link
|
<Cv
|
||||||
|
tag={Link}
|
||||||
href={path}
|
href={path}
|
||||||
class="cv hover:bg-surface-less flex w-full flex-col gap-2 border-b border-solid border-line px-4 py-3 text-left text-sm transition-colors">
|
class="hover:bg-surface-less flex w-full flex-col gap-2 border-b border-solid border-line px-4 py-3 text-left text-sm transition-colors">
|
||||||
<p class="truncate font-medium">
|
<p class="truncate font-medium">
|
||||||
<UnreadDot {path} class="mr-1" />{title || "Untitled thread"}
|
<UnreadDot {path} class="mr-1" />{title || "Untitled thread"}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -46,7 +48,7 @@
|
||||||
<span>{replyCount} {replyCount === 1 ? "reply" : "replies"}</span>
|
<span>{replyCount} {replyCount === 1 ? "reply" : "replies"}</span>
|
||||||
<span>{formatTimestamp(lastActive)}</span>
|
<span>{formatTimestamp(lastActive)}</span>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Cv>
|
||||||
{:else}
|
{:else}
|
||||||
<tr
|
<tr
|
||||||
onclick={onClick}
|
onclick={onClick}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
import {tagSpec, tagValue} from "@welshman/util"
|
import {tagSpec, tagValue} from "@welshman/util"
|
||||||
import Link from "@lib/components/Link.svelte"
|
import Link from "@lib/components/Link.svelte"
|
||||||
|
import Cv from "@lib/components/Cv.svelte"
|
||||||
import Content from "@app/components/Content.svelte"
|
import Content from "@app/components/Content.svelte"
|
||||||
import ProfileLink from "@app/components/ProfileLink.svelte"
|
import ProfileLink from "@app/components/ProfileLink.svelte"
|
||||||
import ThreadActions from "@app/components/ThreadActions.svelte"
|
import ThreadActions from "@app/components/ThreadActions.svelte"
|
||||||
|
|
@ -24,7 +25,7 @@
|
||||||
const h = tagValue(tagSpec("h"), event.tags)
|
const h = tagValue(tagSpec("h"), event.tags)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Link class="cv relative flex flex-col gap-2 card card-interactive w-full" href={path}>
|
<Cv tag={Link} class="relative flex flex-col gap-2 card card-interactive w-full" href={path}>
|
||||||
<UnreadDot {path} class="absolute right-3 top-3" />
|
<UnreadDot {path} class="absolute right-3 top-3" />
|
||||||
{#if title}
|
{#if title}
|
||||||
<div class="flex w-full items-center justify-between gap-2">
|
<div class="flex w-full items-center justify-between gap-2">
|
||||||
|
|
@ -49,4 +50,4 @@
|
||||||
</span>
|
</span>
|
||||||
<ThreadActions showActivity {url} {event} {context} />
|
<ThreadActions showActivity {url} {event} {context} />
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Cv>
|
||||||
|
|
|
||||||
58
src/lib/components/Cv.svelte
Normal file
58
src/lib/components/Cv.svelte
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
<script lang="ts" module>
|
||||||
|
import type {Maybe} from "@welshman/lib"
|
||||||
|
|
||||||
|
let instance: Maybe<IntersectionObserver>
|
||||||
|
|
||||||
|
/** Browsers only paint a `content-visibility: auto` element within half a viewport of the scroll
|
||||||
|
* position, which a fast scroll outruns, leaving empty cards behind. There is no css control over
|
||||||
|
* that distance, so the element is painted two viewports ahead instead. The observer has to watch
|
||||||
|
* the element carrying `cv` — a descendant of one is inside the subtree being skipped, which is
|
||||||
|
* why the class and the attachment live in the same component. */
|
||||||
|
const renderAhead = (element: HTMLElement) => {
|
||||||
|
const observer = (instance ||= new IntersectionObserver(
|
||||||
|
entries => {
|
||||||
|
for (const entry of entries) {
|
||||||
|
const target = entry.target as HTMLElement
|
||||||
|
|
||||||
|
target.style.contentVisibility = entry.isIntersecting ? "visible" : ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{rootMargin: "200% 0px"},
|
||||||
|
))
|
||||||
|
|
||||||
|
observer.observe(element)
|
||||||
|
|
||||||
|
return () => observer.unobserve(element)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import type {Component, Snippet} from "svelte"
|
||||||
|
import cx from "classnames"
|
||||||
|
|
||||||
|
// The root of anything rendered in a long list. `cv` lets the browser skip an offscreen one,
|
||||||
|
// and `renderAhead` is what keeps a fast scroll from outrunning that; `tag` takes whatever the
|
||||||
|
// item was already using as its root, so nothing gains a wrapper element and everything else
|
||||||
|
// passes through to it.
|
||||||
|
type Props = {
|
||||||
|
children?: Snippet
|
||||||
|
tag?: string | Component<any>
|
||||||
|
class?: string
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
const {children, tag = "div", ...restProps}: Props = $props()
|
||||||
|
|
||||||
|
const className = $derived(cx("cv", restProps.class))
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if typeof tag === "string"}
|
||||||
|
<svelte:element this={tag} {...restProps} class={className} {@attach renderAhead}>
|
||||||
|
{@render children?.()}
|
||||||
|
</svelte:element>
|
||||||
|
{:else}
|
||||||
|
{@const Host = tag}
|
||||||
|
<Host {...restProps} class={className} {@attach renderAhead}>
|
||||||
|
{@render children?.()}
|
||||||
|
</Host>
|
||||||
|
{/if}
|
||||||
|
|
@ -99,6 +99,8 @@
|
||||||
@utility superheading {
|
@utility superheading {
|
||||||
@apply font-heading text-center text-4xl font-extrabold;
|
@apply font-heading text-center text-4xl font-extrabold;
|
||||||
}
|
}
|
||||||
|
/* Applied by Cv.svelte, which pairs it with the observer that paints ahead of a fast scroll.
|
||||||
|
Nothing else should reach for it. */
|
||||||
@utility cv {
|
@utility cv {
|
||||||
content-visibility: auto;
|
content-visibility: auto;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue