2

I have a div with contenteditable=true and bind:textContent={value} so it behaves pretty much like a textarea.

The only issue I have with it is that I want to override the content of the div by processing the value, but seems like it is not possible.

To test I wrote this

<div contenteditable="true" bind:textContent={value}>testVal</div>

where value is an exported property of the component.

I kind of expected value to be set to testVal, but instead the div contains the value property.

I sort of understand why this is happening and that what I am doing is sort of an edge case, but is it at all possible to change this behaviour to kind of get a one way binding to value?

and I have tried my "normal" way of creating a one way binding (with some hacks to demonstrate issues):

<div contenteditable="true" on:input={e => value = e.target.textContent}>
  {#each (value || "").split("") as part}
    {part}
  {/each}
</div>

this looks fine, but whenever I change type in the div my input gets multiplied, i.e. if I type e the div gets updated with ee. If I add another e I get eeee

munHunger
  • 2,572
  • 5
  • 34
  • 63
  • Maybe a silly question but did you see that the [tutorial](https://svelte.dev/tutorial/contenteditable-bindings) says for `contenteditable` you can `bind:innerHTML={value}` – Christian Mar 05 '21 at 09:01
  • I have seen that tutorial, but I might be missing something. That tutorial is quite easy as it is not manipulating the content of the editable div. (i.e. it keeps the output separate). imagine that I want to write bold and have the editable div update and set it to bold – munHunger Mar 05 '21 at 09:04
  • I'm not sure I understand the problem you're trying to solve. So should the user be able to type something and then, based on what he/she typed, the text just typed gets changed immediately? What kind of changes would you like to happen? Is it just formatting? – Christian Mar 05 '21 at 09:11
  • exactly, the typed text should get changed immediately. and yes it will just be formatting, my first use case is to use it for a hacky syntax highlighting and change colors of text sort of "on the fly" – munHunger Mar 05 '21 at 09:24

1 Answers1

1

I think the way to go is to use your "normal" way of creating a one way binding. Otherwise, using multiple ways of binding on the same element will conflict.

I used a combination of on:input like you described and, inside of the div, {@html html}

The following example formats each other word in bold as you type (there's some glitch when starting with an empty field):

<script>
    import {tick} from "svelte";
    let html = '<p>Write some text!</p>';

    // for the implementation of the two functions below, see 
    // https://stackoverflow.com/a/13950376/4262276
    let saveSelection = (containerEl) => { /**/ };
    let restoreSelection = (containerEl, savedSel) => { /**/ };

    let editor;

    function handleInput(e){
        const savedSelection = saveSelection(editor);
        html = e.target.textContent
            .split(" ")
            .map((t, i) => i % 2 === 0 
                ? `<span style="font-weight:bold">${t}</span>` 
                : t
            )
            .join(" ");
        tick().then(() => { 
            restoreSelection(editor, savedSelection);
        })
    }
</script>

<div
    bind:this={editor}
    contenteditable="true"
    on:input={handleInput}
>{@html html}</div>


<style>
    [contenteditable] {
        padding: 0.5em;
        border: 1px solid #eee;
        border-radius: 4px;
    }
</style>

Christian
  • 1,250
  • 9
  • 12