fix(ContentEditable): change logic to support controlled state

This commit is contained in:
Ilia Mashkov
2026-01-18 14:35:35 +03:00
parent 86e7b2c1ec
commit 7e62acce49
2 changed files with 58 additions and 3 deletions

View File

@@ -0,0 +1,37 @@
<!--
Component: FontSampler
Displays a sample text with a given font in a contenteditable element.
-->
<script lang="ts">
import { appliedFontsManager } from '$entities/Font';
import { ContentEditable } from '$shared/ui';
interface Props {
fontId: string;
text?: string;
fontSize?: number;
lineHeight?: number;
letterSpacing?: number;
}
let {
fontId,
...restProps
}: Props = $props();
// Ensure the font is registered as soon as this sampler appears
$effect(() => {
appliedFontsManager.registerFonts([fontId]);
});
</script>
<div
class="
w-full rounded-xl
bg-white p-6 border border-slate-200
shadow-sm dark:border-slate-800 dark:bg-slate-950
"
style:font-family={fontId}
>
<ContentEditable {...restProps} />
</div>

View File

@@ -7,7 +7,7 @@ interface Props {
/**
* Visible text
*/
text?: string;
text: string;
/**
* Font settings
*/
@@ -17,19 +17,38 @@ interface Props {
}
let {
text = 'The quick brown fox jumps over the lazy dog',
text = $bindable('The quick brown fox jumps over the lazy dog.'),
fontSize = 48,
lineHeight = 1.2,
letterSpacing = 0,
}: Props = $props();
let element: HTMLDivElement | undefined = $state();
// Initial Sync: Set the text ONLY ONCE when the element is created.
// This prevents Svelte from "owning" the innerHTML/innerText.
$effect(() => {
if (element && element.innerText !== text) {
element.innerText = text;
}
});
// Handle changes: Update the outer state without re-rendering the div.
function handleInput(e: Event) {
const target = e.target as HTMLDivElement;
// Update the bindable prop directly
text = target.innerText;
}
</script>
<div
bind:this={element}
contenteditable="plaintext-only"
spellcheck="false"
role="textbox"
tabindex="0"
data-placeholder="Type something to test..."
oninput={handleInput}
class="
w-full min-h-[1.2em] outline-none transition-all duration-200
empty:before:content-[attr(data-placeholder)] empty:before:text-slate-400
@@ -40,5 +59,4 @@ let {
style:line-height={lineHeight}
style:letter-spacing="{letterSpacing}em"
>
{text}
</div>