|
5 | 5 | --> |
6 | 6 |
|
7 | 7 | <script lang="ts"> |
8 | | - import { onMount, type Snippet } from 'svelte'; |
9 | 8 | import type { Carta } from '../carta'; |
| 9 | + import type { UIEventHandler } from 'svelte/elements'; |
10 | 10 | import type { TextAreaProps } from '../textarea-props'; |
| 11 | + import { onMount, type Snippet } from 'svelte'; |
11 | 12 | import { debounce } from '../utils'; |
12 | 13 | import { BROWSER } from 'esm-env'; |
13 | 14 | import { speculativeHighlightUpdate } from '../speculative'; |
14 | | - import type { UIEventHandler } from 'svelte/elements'; |
15 | 15 |
|
16 | 16 | interface Props { |
17 | 17 | /** |
|
61 | 61 | let textarea: HTMLTextAreaElement; |
62 | 62 | let highlightElem: HTMLDivElement; |
63 | 63 | let wrapperElem: HTMLDivElement; |
64 | | - let highlighted = $state(value); |
65 | | - let mounted = $state(false); |
66 | 64 | let currentlyHighlightedValue = value; |
67 | 65 |
|
68 | 66 | const simpleUUID = Math.random().toString(36).substring(2); |
|
94 | 92 | } |
95 | 93 | }; |
96 | 94 |
|
| 95 | + /** |
| 96 | + * Focus the textarea element. |
| 97 | + */ |
97 | 98 | const focus = () => { |
98 | 99 | // Allow text selection |
99 | 100 | const selectedText = window.getSelection()?.toString(); |
|
106 | 107 | * Highlight the text in the textarea. |
107 | 108 | * @param text The text to highlight. |
108 | 109 | */ |
109 | | - const highlight = async (text: string) => { |
| 110 | + async function highlight(text: string) { |
110 | 111 | const highlighter = await carta.highlighter(); |
111 | | - if (!highlighter) return; |
| 112 | + if (!highlighter) return null; |
112 | 113 |
|
113 | 114 | const html = highlighter.codeToHtml(text); |
| 115 | + const timestamp = new Date().getTime(); |
114 | 116 |
|
115 | 117 | if (carta.sanitizer) { |
116 | | - highlighted = carta.sanitizer(html); |
| 118 | + return { html: carta.sanitizer(html), timestamp }; |
117 | 119 | } else { |
118 | | - highlighted = html; |
| 120 | + return { html, timestamp }; |
119 | 121 | } |
| 122 | + } |
120 | 123 |
|
121 | | - currentlyHighlightedValue = value; |
| 124 | + /** |
| 125 | + * Debounced version of the highlight function. |
| 126 | + */ |
| 127 | + const debouncedHighlight = debounce(highlight, highlightDelay); |
122 | 128 |
|
123 | | - requestAnimationFrame(resize); |
124 | | - }; |
| 129 | + /** |
| 130 | + * Returns the highlighted text using a speculative update. |
| 131 | + * @param text The text to highlight. |
| 132 | + */ |
| 133 | + function speculativeHighlight(value: string) { |
| 134 | + const timestamp = new Date().getTime(); |
125 | 135 |
|
126 | | - const debouncedHighlight = debounce(highlight, highlightDelay); |
| 136 | + if (!mounted) return { html: '', timestamp }; |
| 137 | +
|
| 138 | + const currentOverlay = highlightElem.innerHTML; |
| 139 | + if (highlightElem) { |
| 140 | + try { |
| 141 | + const html = speculativeHighlightUpdate(currentlyHighlightedValue, value, currentOverlay); |
| 142 | + currentlyHighlightedValue = value; |
| 143 | +
|
| 144 | + return { html, timestamp }; |
| 145 | + } catch (e) { |
| 146 | + console.error(`Error executing speculative update: ${e}.`); |
| 147 | + } |
| 148 | + } |
| 149 | +
|
| 150 | + return { |
| 151 | + html: highlightElem.innerHTML, |
| 152 | + timestamp |
| 153 | + }; |
| 154 | + } |
127 | 155 |
|
128 | 156 | /** |
129 | 157 | * Highlight the nested languages in the markdown, loading the necessary |
|
137 | 165 |
|
138 | 166 | if (!highlighter) return; |
139 | 167 | const { updated } = await loadNestedLanguages(highlighter, text); |
140 | | - if (updated) debouncedHighlight(text); |
| 168 | + if (updated) overlayPromise = debouncedHighlight(text); |
141 | 169 | }, 300); |
142 | 170 |
|
143 | | - const onValueChange = (value: string) => { |
144 | | - if (highlightElem) { |
145 | | - try { |
146 | | - highlighted = speculativeHighlightUpdate(currentlyHighlightedValue, value, highlighted); |
147 | | - currentlyHighlightedValue = value; |
148 | | - requestAnimationFrame(resize); |
149 | | - } catch (e) { |
150 | | - console.error(`Error executing speculative update: ${e}.`); |
151 | | - } |
152 | | - } |
153 | | -
|
154 | | - debouncedHighlight(value); |
155 | | -
|
| 171 | + /** |
| 172 | + * Value change handler. |
| 173 | + */ |
| 174 | + const onchange = (value: string) => { |
156 | 175 | highlightNestedLanguages(value); |
157 | 176 | }; |
158 | 177 |
|
| 178 | + /** |
| 179 | + * Runes |
| 180 | + */ |
| 181 | + let mounted = $state(false); |
| 182 | + let overlayPromise = $derived(debouncedHighlight(value)); |
| 183 | + let overlay = $state<Awaited<typeof overlayPromise>>(null); |
| 184 | + let speculativeOverlay = $derived(speculativeHighlight(value)); |
| 185 | + let displayedOverlay = $derived( |
| 186 | + overlay && overlay.timestamp > speculativeOverlay.timestamp ? overlay : speculativeOverlay |
| 187 | + ); |
| 188 | +
|
| 189 | + $effect(() => { |
| 190 | + if (BROWSER) onchange(value); |
| 191 | + }); |
| 192 | +
|
| 193 | + $effect(() => { |
| 194 | + overlayPromise.then(async (o) => (overlay = await o)); |
| 195 | + }); |
| 196 | +
|
159 | 197 | $effect(() => { |
160 | | - if (BROWSER) onValueChange(value); |
| 198 | + if (mounted) { |
| 199 | + displayedOverlay; // When the overlay changes |
| 200 | + requestAnimationFrame(resize); // Resize the textarea |
| 201 | + } |
161 | 202 | }); |
162 | 203 |
|
| 204 | + /** |
| 205 | + * Mount callback |
| 206 | + */ |
163 | 207 | onMount(() => { |
164 | 208 | mounted = true; |
165 | 209 | // Resize once the DOM is updated. |
166 | 210 | requestAnimationFrame(resize); |
167 | | - }); |
168 | | - onMount(() => { |
| 211 | +
|
169 | 212 | carta.$setInput(textarea, elem!, () => { |
170 | 213 | value = textarea.value; |
171 | 214 | }); |
|
197 | 240 | aria-hidden="true" |
198 | 241 | bind:this={highlightElem} |
199 | 242 | > |
200 | | - <!-- eslint-disable-line svelte/no-at-html-tags -->{@html highlighted} |
| 243 | + <!-- eslint-disable-next-line svelte/no-at-html-tags --> |
| 244 | + {@html displayedOverlay.html} |
201 | 245 | </div> |
202 | 246 |
|
203 | 247 | <textarea |
|
0 commit comments