This repository has no description
1<script lang="ts">
2 import { untrack } from "svelte";
3 import { File, type FileOptions } from "@pierre/diffs";
4 import { pierreFileOptions } from "./pierre";
5
6 interface Props {
7 // used for the header and to infer the highlighting language
8 name: string;
9 contents: string;
10 // pierre's native overflow: wrap keeps highlighting instead of scrolling
11 wrap?: boolean;
12 // server-prerendered shadow dom, only set for the initial ssr
13 prerenderedHTML?: string;
14 // forwarded to pierre, fires after every (async-safe) render pass
15 onPostRender?: FileOptions<undefined>["onPostRender"];
16 }
17
18 let { name, contents, wrap = false, prerenderedHTML, onPostRender }: Props = $props();
19
20 let element: HTMLElement | undefined = $state();
21
22 let instance: File | undefined;
23
24 // same manual updates as PierreDiff, this effect owns the instance,
25 // a contents swap re-renders in place instead of a shadow dom teardown
26 $effect(() => {
27 const host = element;
28 if (!host) return;
29 // scroll and wrap lay out differently enough in grid to re-hydrate on
30 // toggle, and the toggle is rare
31 const options: FileOptions<undefined> = {
32 ...pierreFileOptions(wrap),
33 onPostRender
34 };
35
36 // sveltekit re-runs the load on hydration, the prop never reaches the
37 // client. pierre marks its ssr output with data-dehydrated
38 const dehydrated = host.shadowRoot?.querySelector("[data-dehydrated]") != null;
39 if (prerenderedHTML === undefined && !dehydrated) host.shadowRoot?.replaceChildren();
40 const created = new File(options, undefined, true);
41 // untracked, the render effect below owns updates
42 created.hydrate({
43 file: untrack(() => ({ name, contents })),
44 fileContainer: host,
45 prerenderedHTML
46 });
47 instance = created;
48
49 return () => {
50 created.cleanUp();
51 instance = undefined;
52 };
53 });
54
55 // hydrate paints the initial file, this pass covers later name/contents
56 // changes
57 let firstRender = true;
58 $effect(() => {
59 const file = { name, contents };
60 if (firstRender) {
61 firstRender = false;
62 return;
63 }
64 instance?.render({ file, forceRender: true });
65 });
66</script>
67
68<diffs-container bind:this={element} class="diffs-container">
69 {#if prerenderedHTML}
70 <!-- pierre's own renderer output, token text is escaped by its hast builder -->
71 <template shadowrootmode="open">{@html prerenderedHTML}</template>
72 {/if}
73</diffs-container>