diff --git a/examples/website/src/index.ts b/examples/website/src/index.ts index bf6ebdb..2057e0e 100644 --- a/examples/website/src/index.ts +++ b/examples/website/src/index.ts @@ -99,65 +99,72 @@ function updateMergedText(): void { selectedTokenizer ); - let selectionStart: number = Number.NEGATIVE_INFINITY; - let selectionEnd: number = Number.NEGATIVE_INFINITY; - if ((results.cursors?.length ?? 0) > 0) { - selectionStart = results.cursors![0].position; - selectionEnd = results.cursors![1].position; - } - const isSelection = selectionStart !== selectionEnd; + const selection = + results.cursors?.length === 2 + ? { + start: results.cursors[0].position, + end: results.cursors[1].position, + side: leftCursors ? 'left' : 'right', + } + : undefined; - const selectionSide = leftCursors ? 'left' : 'right'; const fragment = document.createDocumentFragment(); + function appendSpan(text: string, className: string): void { + if (text.length === 0) { + return; + } + + const span = document.createElement('span'); + span.className = className; + span.textContent = text; + fragment.appendChild(span); + } + + function appendCaret(): void { + if (!selection) { + return; + } + + fragment.appendChild( + createSelectionOverlay( + selection.side === 'left', + selection.start !== selection.end + ) + ); + } + let currentPosition = 0; - if (selectionEnd === 0) { - fragment.appendChild(createSelectionOverlay(selectionSide === 'left', isSelection)); + if (selection?.end === 0) { + appendCaret(); } for (const { text, history } of results.history) { const isDelete = history === 'RemovedFromLeft' || history === 'RemovedFromRight'; - let spanChars: string[] = []; - let currentClass = ''; - - const flushSpan = () => { - if (spanChars.length > 0) { - const span = document.createElement('span'); - span.className = currentClass; - span.textContent = spanChars.join(''); - fragment.appendChild(span); - spanChars = []; - } - }; - - for (const character of text) { - let className = history; - if ( - !isDelete && - selectionStart <= currentPosition && - currentPosition < selectionEnd - ) { - className += ` selection-${selectionSide}`; - } - - if (className !== currentClass) { - flushSpan(); - currentClass = className; - } - spanChars.push(character); - - if (!isDelete) { - if (currentPosition === selectionEnd - 1) { - flushSpan(); - fragment.appendChild( - createSelectionOverlay(selectionSide === 'left', isSelection) - ); - } - currentPosition++; - } + if (isDelete || !selection) { + appendSpan(text, history); + continue; } - flushSpan(); + // Reconcile positions count Unicode scalars, rather than UTF-16 code units. + const chars = [...text]; + const start = Math.max(0, Math.min(chars.length, selection.start - currentPosition)); + const end = Math.max(0, Math.min(chars.length, selection.end - currentPosition)); + appendSpan(chars.slice(0, start).join(''), history); + appendSpan( + chars.slice(start, end).join(''), + `${history} selection-${selection.side}` + ); + + if ( + currentPosition < selection.end && + selection.end <= currentPosition + chars.length + ) { + appendCaret(); + } + + appendSpan(chars.slice(end).join(''), history); + currentPosition += chars.length; } mergedTextArea.innerHTML = ''; diff --git a/examples/website/src/style.scss b/examples/website/src/style.scss index 9cc7684..3783ebb 100644 --- a/examples/website/src/style.scss +++ b/examples/website/src/style.scss @@ -25,7 +25,6 @@ $gradient-end: #e0e7ef; * { box-sizing: border-box; margin: 0; - user-select: none; } html, @@ -76,13 +75,6 @@ header > h1 { text-align: center; } -h1, -code, -p, -p * { - user-select: text; -} - code { background: $code-bg; color: $code-text; @@ -121,6 +113,7 @@ main { } .radio-group { + user-select: none; display: flex; gap: 16px; justify-content: center; @@ -265,6 +258,7 @@ main { } label { + user-select: none; display: inline-block; margin-bottom: 8px; font-weight: 600; @@ -309,7 +303,6 @@ textarea { box-sizing: border-box; resize: none; cursor: text; - user-select: text; caret-color: $primary-blue; outline: 2px solid transparent; outline-offset: 2px; @@ -334,7 +327,6 @@ textarea:focus { #merged { width: 100%; - user-select: text; > * { position: relative; @@ -349,14 +341,9 @@ textarea:focus { } } -.Unchanged { - user-select: text; -} - .Left, .AddedFromLeft, .RemovedFromLeft { - user-select: text; background: $green; } @@ -381,7 +368,6 @@ textarea:focus { .Right, .AddedFromRight, .RemovedFromRight { - user-select: text; background: $light-blue; } @@ -396,6 +382,7 @@ $CARET_WIDTH: 2; $DOT_RADIUS: 4; .selection-caret { + user-select: none; position: relative; z-index: 1000; @@ -453,7 +440,6 @@ $DOT_RADIUS: 4; top: -1.3em; left: calc(-#{$CARET_WIDTH} / 2 * 1px); font-size: 0.9em; - user-select: none; color: white; padding: 0 2px; transition: opacity 0.3s ease-in-out;