Improve cursor hover effect

This commit is contained in:
Andras Schmelczer 2025-07-10 22:38:59 +01:00
parent cec8950ccc
commit 865d1f5073
No known key found for this signature in database
GPG key ID: FC8F2C3D3D1A718C
5 changed files with 42 additions and 18 deletions

View file

@ -28,7 +28,7 @@
<div class="scroll-container">
<div class="page-wrapper">
<header>
<h1>Reconcile-text: conflict-free 3-way text merging</h1>
<h1><code>reconcile-text</code>: conflict-free 3-way text merging</h1>
<p>
Think
<a

View file

@ -10,7 +10,7 @@ const tokenizerRadios = document.querySelectorAll(
'input[name="tokenizer"]'
) as NodeListOf<HTMLInputElement>;
const sampleText = `The \`reconcile\` Rust library is embedded on this page as a WASM module and powers these text boxes. Experiment with changing the "Original", "First concurrent edit", and "Second concurrent edit" text boxes to see competing changes get merged in real-time within the "Deconflicted result" box. Here, you will see color-coded tokens marking the origin of each token, including ones that got deleted. The result highly depends on the tokenization strategy, for example, deciding how casing or whitespace is taken into account.`;
const sampleText = `The \`reconcile\` Rust library is embedded on this page as a WASM module and powers these text boxes. Experiment with changing the "Original", "First concurrent edit", and "Second concurrent edit" text boxes to see competing changes get merged in real-time within the "Deconflicted result" box. Here, you will see color-coded tokens marking the origin of each token, including ones that got deleted. The result highly depends on the tokenisation strategy, for example, deciding how casing or whitespace is taken into account.`;
async function main(): Promise<void> {
originalTextArea.addEventListener('input', updateMergedText);
@ -76,11 +76,11 @@ function updateMergedText(): void {
}
const selectionSide = leftCursors ? 'left' : 'right';
mergedTextArea.innerHTML = '';
const fragment = document.createDocumentFragment();
let currentPosition = 0;
if (selectionEnd === 0) {
mergedTextArea.appendChild(createCaret(selectionSide === 'left'));
fragment.appendChild(createCaret(selectionSide === 'left'));
}
for (const { text, history } of results.history) {
@ -93,10 +93,10 @@ function updateMergedText(): void {
span.className += ` selection-${selectionSide}`;
}
mergedTextArea.appendChild(span);
fragment.appendChild(span);
if (currentPosition == selectionEnd - 1) {
mergedTextArea.appendChild(createCaret(selectionSide === 'left'));
if (currentPosition === selectionEnd - 1) {
fragment.appendChild(createCaret(selectionSide === 'left'));
}
if (history !== 'RemovedFromLeft' && history !== 'RemovedFromRight') {
@ -105,6 +105,9 @@ function updateMergedText(): void {
}
}
}
mergedTextArea.innerHTML = '';
mergedTextArea.appendChild(fragment);
}
function getCursorsFromActiveTextArea() {
@ -167,8 +170,8 @@ function autoResize(textarea: HTMLTextAreaElement): void {
function focusTextArea(textarea: HTMLTextAreaElement): void {
textarea.focus();
textarea.selectionStart = textarea.value.length;
textarea.selectionEnd = textarea.value.length;
textarea.selectionStart = 0;
textarea.selectionEnd = 0;
}
main();

View file

@ -1,3 +1,5 @@
@use 'sass:color';
// Colour palette
$primary-blue: #2451a6;
$light-blue: #85bff7;
@ -16,6 +18,10 @@ $gradient-end: #e0e7ef;
@return rgba($colour, $opacity);
}
@function caret-colour($colour, $amount: 20%) {
@return color.adjust($colour, $lightness: -$amount);
}
* {
box-sizing: border-box;
margin: 0;
@ -36,6 +42,7 @@ body {
height: 100vh;
height: 100dvh;
overflow-y: auto;
overflow-x: hidden;
}
.background {
@ -274,9 +281,13 @@ textarea {
#merged {
width: 100%;
user-select: text;
display: flex;
flex-wrap: wrap;
> * {
position: relative;
display: block;
white-space: pre-wrap;
}
}
@ -328,13 +339,14 @@ $DOT_RADIUS: 4;
.selection-caret {
position: relative;
z-index: 1000;
&.selection-caret-left {
background: $green;
background: caret-colour($green);
}
&.selection-caret-right {
background: $light-blue;
background: caret-colour($light-blue);
}
> * {
@ -359,13 +371,24 @@ $DOT_RADIUS: 4;
height: #{$DOT_RADIUS * 2}px;
top: -#{$DOT_RADIUS}px;
left: -#{$DOT_RADIUS}px;
transition: transform 0.3s ease-in-out;
transition: opacity 0.3s ease-in-out;
transform-origin: bottom center;
box-sizing: border-box;
&::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 30px;
height: 30px;
border-radius: 50%;
}
}
&:hover > .dot {
transform: scale(0);
opacity: 0;
}
> .info {

View file

@ -11,7 +11,5 @@
"skipLibCheck": true,
"inlineSourceMap": true
},
"exclude": [
"./dist"
]
}
"exclude": ["./dist"]
}

View file

@ -48,4 +48,4 @@
"webpack-cli": "^6.0.1",
"webpack-merge": "^6.0.1"
}
}
}