Improve splitter UX: visual feedback & smoother resizing

Enhanced the editor's vertical splitter with immediate visual feedback during dragging, including dynamic width, shadow, and handle size. Updated logic to re-render UI and save split ratio in real time. Refined save status updates to reduce unnecessary renders. Improved mobile responsiveness and overall user experience.
This commit is contained in:
Michal Vrba
2026-01-31 13:23:29 +01:00
parent 5a06a307fb
commit efc31f643d
3 changed files with 61 additions and 20 deletions
+31 -17
View File
@@ -82,7 +82,7 @@
<!-- Simple Splitter -->
@if (showPreview)
{
<div class="splitter" @onmousedown="OnSplitterMouseDown" @onmousedown:preventDefault="true">
<div class="splitter @(isDragging ? "dragging" : "")" @onmousedown="OnSplitterMouseDown" @onmousedown:preventDefault="true">
<div class="splitter-handle"></div>
</div>
}
@@ -156,11 +156,11 @@
await InvokeAsync(async () => await AutoSave());
}, null, 2000, Timeout.Infinite); // Increased to 2 seconds for production
// Don't show "Saving..." immediately, wait for actual save
// Only update status if it was previously "Saved"
if (saveStatus == "Saved")
{
saveStatus = "Modified";
await InvokeAsync(StateHasChanged);
StateHasChanged();
}
}
@@ -177,9 +177,11 @@
if (string.IsNullOrEmpty(sessionId) || !hasUnsavedChanges)
return;
var previousStatus = saveStatus;
// Set saving status only when actually starting to save
saveStatus = "Saving...";
await InvokeAsync(StateHasChanged);
StateHasChanged(); // Use StateHasChanged instead of InvokeAsync
try
{
@@ -194,7 +196,7 @@
catch (OperationCanceledException)
{
saveStatus = "Timeout";
// Retry after 5 seconds
// Simplified retry - no immediate re-rendering
_ = Task.Delay(5000).ContinueWith(async _ =>
{
if (hasUnsavedChanges)
@@ -206,7 +208,7 @@
catch (Exception)
{
saveStatus = "Error";
// Retry after 3 seconds
// Simplified retry - no immediate re-rendering
_ = Task.Delay(3000).ContinueWith(async _ =>
{
if (hasUnsavedChanges)
@@ -216,7 +218,11 @@
});
}
await InvokeAsync(StateHasChanged);
// Only trigger re-render if status actually changed
if (saveStatus != previousStatus)
{
StateHasChanged();
}
}
private void TogglePreview()
@@ -396,25 +402,33 @@
{
if (!isDragging || containerWidth <= 0) return;
var newRatio = clientX / containerWidth;
splitRatio = Math.Max(0.2, Math.Min(0.8, newRatio)); // Clamp between 20% and 80%
var newRatio = Math.Max(0.2, Math.Min(0.8, clientX / containerWidth)); // Clamp between 20% and 80%
// Save to localStorage
// Update immediately for smooth dragging
splitRatio = newRatio;
// Use StateHasChanged for immediate visual feedback
StateHasChanged();
}
[JSInvokable]
public async Task StopDragging()
{
if (!isDragging) return;
isDragging = false;
// Save split ratio to localStorage when dragging stops
try
{
await JSRuntime.InvokeVoidAsync("localStorage.setItem", "notemark-split-ratio", splitRatio.ToString());
await JSRuntime.InvokeVoidAsync("localStorage.setItem", "notemark-split-ratio", splitRatio.ToString("F2"));
}
catch
{
// Ignore localStorage errors
}
// Single StateHasChanged at the end
StateHasChanged();
}
[JSInvokable]
public void StopDragging()
{
isDragging = false;
}
}
+17 -2
View File
@@ -438,11 +438,18 @@
justify-content: center;
position: relative;
flex-shrink: 0;
transition: background-color 0.2s ease;
transition: all 0.15s ease;
}
.splitter:hover {
background-color: var(--accent);
width: 10px;
}
.splitter.dragging {
background-color: var(--accent);
width: 12px;
box-shadow: 0 0 8px rgba(0, 122, 204, 0.3);
}
.splitter-handle {
@@ -450,11 +457,19 @@
height: 40px;
background-color: var(--secondary-text);
border-radius: 1px;
transition: background-color 0.2s ease;
transition: all 0.15s ease;
}
.splitter:hover .splitter-handle {
background-color: white;
width: 3px;
height: 50px;
}
.splitter.dragging .splitter-handle {
background-color: white;
width: 4px;
height: 60px;
}
/* Mobile Responsive - Place at end to override desktop styles */
+13 -1
View File
@@ -59,9 +59,15 @@ window.startSplitterDrag = function(blazorInstance) {
document.addEventListener('mousemove', handleSplitterMove);
document.addEventListener('mouseup', handleSplitterUp);
// Prevent text selection
// Improve visual feedback during drag
document.body.style.userSelect = 'none';
document.body.style.cursor = 'col-resize';
// Add dragging class for visual feedback
const splitter = document.querySelector('.splitter');
if (splitter) {
splitter.classList.add('dragging');
}
};
function handleSplitterMove(e) {
@@ -88,6 +94,12 @@ function handleSplitterUp(e) {
document.body.style.cursor = '';
document.body.style.userSelect = '';
// Remove dragging visual state
const splitter = document.querySelector('.splitter');
if (splitter) {
splitter.classList.remove('dragging');
}
// Notify Blazor that dragging stopped
if (splitterState.blazorInstance) {
splitterState.blazorInstance.invokeMethodAsync('StopDragging');