fix(ContentEditable): change logic to support controlled state
This commit is contained in:
37
src/features/DisplayFont/ui/FontSampler/FontSampler.svelte
Normal file
37
src/features/DisplayFont/ui/FontSampler/FontSampler.svelte
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user