Something went wrong. Try again.
Monorepo for Tangled
Something went wrong. Try again.
Svelte
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697<script lang="ts"> import { untrack } from "svelte"; import { File, type FileOptions } from "@pierre/diffs"; import { clearPierreRender, pierreFileOptions } from "$lib/components/repo/pierre"; import BlameOverlay from "$lib/components/repo/blame/BlameOverlay.svelte"; import { BlameHover } from "$lib/components/repo/blame/hover.svelte"; import type { Blame } from "$lib/components/repo/blame/types";
interface Props { // used for the header and to infer the highlighting language name: string; contents: string; // pierre's native overflow: wrap keeps highlighting instead of scrolling wrap?: boolean; // server-prerendered shadow dom, only set for the initial ssr prerenderedHTML?: string; // forwarded to pierre, fires after every (async-safe) render pass onPostRender?: FileOptions<undefined, undefined>["onPostRender"]; // one run per stretch of lines sharing a commit, absent when blame is off blame?: Blame; }
let { name, contents, wrap = false, prerenderedHTML, onPostRender, blame }: Props = $props();
let element: HTMLElement | undefined = $state(); let wrapper: HTMLElement | undefined = $state();
const hover = new BlameHover(() => wrapper);
const options = (forWrap: boolean): FileOptions<undefined, undefined> => ({ ...pierreFileOptions(forWrap), onPostRender, onLineEnter: ({ lineNumber, lineElement, event }) => { if (!blame || event.pointerType === "touch") return; hover.enter(lineNumber, lineElement); }, onLineLeave: () => hover.leave() });
$effect(() => { if (!blame) hover.hide(); });
let instance: File | undefined;
// same manual updates as PierreDiff, this effect owns the instance, // a contents swap re-renders in place instead of a shadow dom teardown $effect(() => { const host = element; if (!host) return; // scroll and wrap lay out differently enough in grid to re-hydrate on // toggle, and the toggle is rare // sveltekit re-runs the load on hydration, the prop never reaches the // client. pierre marks its ssr output with data-dehydrated. a wrap toggle // lands here with the previous render: drop it, keep pierre's style nodes const dehydrated = host.shadowRoot?.querySelector("[data-dehydrated]") != null; if (prerenderedHTML === undefined && !dehydrated) clearPierreRender(host); const created = new File(options(wrap), undefined, true); // untracked, the render effect below owns updates created.hydrate({ file: untrack(() => ({ name, contents })), fileContainer: host, prerenderedHTML }); instance = created;
return () => { created.cleanUp(); instance = undefined; }; });
// hydrate paints the initial file, this pass covers later name/contents // changes let firstRender = true; $effect(() => { const file = { name, contents }; if (firstRender) { firstRender = false; return; } instance?.render({ file, forceRender: true }); });</script>
<div bind:this={wrapper} class="relative"> <diffs-container bind:this={element} class="diffs-container"> {#if prerenderedHTML} <!-- eslint-disable-next-line svelte/no-at-html-tags -- pierre's own renderer output, token text is escaped by its hast builder --> <template shadowrootmode="open">{@html prerenderedHTML}</template> {/if} </diffs-container> {#if blame} <BlameOverlay {blame} {hover} /> {/if}</div>