Any y-textarea implementation advice?

I was thinking of having a go at an implementation for y-textarea. I’m pretty new to Yjs. I see that there was a similar implementation for old y-js here: GitHub - y-js/y-text: Text Type for Yjs. So I was thinking of basing the implementation on that. Any advice?.

I took a first pass at implementing using new yjs here:

I would have forked y-textarea but it was an empty repo. Will fork and raise PR if the repo gets populated with something.

Implementation based on y-text implementation, doesn’t have ‘presence’, but does support shared cursors.

In-case anybody finds this and is interested I released put this on npm,
y-textarea - npm.

1 Like

Here’s a very simple standalone textarea binding. (Unbinding is left as an exercise for the reader :slight_smile: )

function yTextArea(ta: HTMLTextAreaElement, text: Y.Text, localTransactionOrigin = 'local') {
    let oldContent = '';

    text.observe((e, txn) => {
        if (txn.origin === localTransactionOrigin) return;

        let pos = 0;
        e.changes.delta.forEach(d => {
            if ('retain' in d) {
                pos += d.retain ?? 0;
            } else if ('delete' in d) {
                ta.setRangeText('', pos, pos + (d.delete ?? 0));
            } else if ('insert' in d) {
                const s = d.insert as string;
                ta.setRangeText(s, pos, pos);
                pos += s.length;
            }
        });

        oldContent = ta.value;
    });

    ta.addEventListener('input', () => {
        const newContent = ta.value;

        let start = 0;
        while ((start < oldContent.length)
            && (start < newContent.length)
            && (oldContent.charCodeAt(start) === newContent.charCodeAt(start)))
        {
            start++;
        }
        const delta = newContent.length - oldContent.length;
        let end = newContent.length;
        while ((end > start)
            && (end - delta > start)
            && (oldContent.charCodeAt(end - delta - 1) === newContent.charCodeAt(end - 1)))
        {
            end--;
        }
        // This technique isn't perfect: consider inserting 'x' into
        // the middle of 'AxxxxB' - we will detect an 'x' added at the
        // *end* of the run of 'x's, rather than in the middle.

        const oldLength = (end - delta) - start;
        text.doc!.transact(() => {
            if (oldLength > 0) text.delete(start, oldLength);
            if (end > start) text.insert(start, newContent.substring(start, end));
        }, localTransactionOrigin);

        oldContent = newContent;
    });
}