feat: prevent scrolling of containers when using focused number inputs (#4629)

* feat: prevent scrolling of containers when using focused number inputs
#4607

* feat: enhance number input wheel behavior with slider sync and Firefox support

* refactor: optimize number input wheel handler with native DOM methods and type checks

* Refactor: Move into dom-handlers folder

* refactor: use optional chaining for slider element selection

* Simplify file paths

* Unlock scroll to edit in misc controls

* refactor: throttle wheel event handler updates

* refactor: Extract value update into a function

* fix: NaN-aware value clamping

* fix: Add sanity checks for input value calculations

---------

Co-authored-by: Wolfsblvt <wolfsblvt@gmail.com>
This commit is contained in:
Cohee
2025-10-10 21:42:44 +03:00
committed by GitHub
parent aaad129d1b
commit df8f2a477a
3 changed files with 72 additions and 4 deletions
+4 -4
View File
@@ -253,7 +253,7 @@
</div>
<div id="common-gen-settings-block" class="width100p">
<div id="pro-settings-block" class="flex-container gap10h5v justifyCenter">
<div id="amount_gen_block" class="alignitemscenter flex-container marginBot5 flexFlowColumn flexBasis48p flexGrow flexShrink gap0">
<div id="amount_gen_block" class="range-block-range-and-counter alignitemscenter flex-container marginBot5 flexFlowColumn flexBasis48p flexGrow flexShrink gap0">
<small data-i18n="response legth(tokens)">Response (tokens)</small>
<input class="neo-range-slider" type="range" id="amount_gen" name="volume" min="16" max="2048" step="1">
<div data-randomization-disabled="true" class="wide100p">
@@ -284,7 +284,7 @@
</label>
</div>
</div>
<div id="max_context_block" class="alignitemscenter flex-container marginBot5 flexFlowColumn flexBasis48p flexGrow flexShrink gap0">
<div id="max_context_block" class="range-block-range-and-counter alignitemscenter flex-container marginBot5 flexFlowColumn flexBasis48p flexGrow flexShrink gap0">
<small data-i18n="context size(tokens)">Context (tokens)</small>
<input class="neo-range-slider" type="range" id="max_context" name="volume" min="512" max="8192" step="64">
<div data-randomization-disabled="true" class="wide100p">
@@ -648,7 +648,7 @@
Max Response Length (tokens)
</div>
<div class="wide100p">
<input type="number" id="openai_max_tokens" name="openai_max_tokens" class="text_pole" min="1" max="65536">
<input type="number" id="openai_max_tokens" name="openai_max_tokens" class="text_pole" min="1" max="65536" step="1">
</div>
</div>
<div class="range-block" data-source="openai,custom,xai,aimlapi,moonshot,azure_openai">
@@ -4214,7 +4214,7 @@
Token Padding
</small>
</div>
<input id="token_padding" class="text_pole textarea_compact" type="number" min="-2048" max="2048" />
<input id="token_padding" class="text_pole textarea_compact" type="number" min="-2048" max="2048" step="1" />
</div>
</div>
<div>
+2
View File
@@ -270,6 +270,7 @@ import { getSystemMessageByType, initSystemMessages, SAFETY_CHAT, sendSystemMess
import { event_types, eventSource } from './scripts/events.js';
import { initAccessibility } from './scripts/a11y.js';
import { applyStreamFadeIn } from './scripts/util/stream-fadein.js';
import { initDomHandlers } from './scripts/dom-handlers.js';
// API OBJECT FOR EXTERNAL WIRING
globalThis.SillyTavern = {
@@ -640,6 +641,7 @@ async function firstLoadInit() {
showLoader();
registerPromptManagerMigration();
initDomHandlers();
initStandaloneMode();
initLibraryShims();
addShowdownPatch(showdown);
+66
View File
@@ -0,0 +1,66 @@
import { throttle } from './utils.js';
export function initDomHandlers() {
handleInputWheel();
}
/**
* Trap mouse wheel inside of focused number inputs to prevent scrolling their containers.
* Instead of firing wheel events, manually update both slider and input values.
* This also makes wheel work inside Firefox.
*/
function handleInputWheel() {
const minInterval = 25; // ms
/**
* Update input and slider values based on wheel delta
* @param {HTMLInputElement} input The number input element
* @param {HTMLInputElement|null} slider The associated range input element, if any
* @param {number} deltaY The wheel deltaY value
*/
function updateValue(input, slider, deltaY) {
const currentValue = parseFloat(input.value);
const step = parseFloat(input.step);
const min = parseFloat(input.min);
const max = parseFloat(input.max);
// Sanity checks before trying to calculate new value
if (isNaN(currentValue) || isNaN(step) || step <= 0 || deltaY === 0) return;
// Calculate new value based on wheel movement delta (negative = up, positive = down)
let newValue = currentValue + (deltaY > 0 ? -step : step);
// Ensure it's a multiple of step
newValue = Math.round(newValue / step) * step;
// Ensure it's within the min and max range (NaN-aware)
newValue = !isNaN(min) ? Math.max(newValue, min) : newValue;
newValue = !isNaN(max) ? Math.min(newValue, max) : newValue;
// Simple fix for floating point precision issues
newValue = Math.round(newValue * 1e10) / 1e10;
// Update both input and slider values
input.value = newValue.toString();
if (slider) slider.value = newValue.toString();
// Trigger input event (just ONE) to update any listeners
const inputEvent = new Event('input', { bubbles: true });
input.dispatchEvent(inputEvent);
}
const updateValueThrottled = throttle(updateValue, minInterval);
document.addEventListener('wheel', (e) => {
// Try to carefully narrow down if we even need to fire this handler
const input = document.activeElement instanceof HTMLInputElement ? document.activeElement : null;
if (input && input.type === 'number' && input.hasAttribute('step')) {
const parent = input.closest('.range-block-range-and-counter') ?? input.closest('div') ?? input.parentElement;
const slider = /** @type {HTMLInputElement} */ (parent?.querySelector('input[type="range"]'));
// Stop propagation for either target
if (e.target === input || (slider && e.target === slider)) {
e.stopPropagation();
e.preventDefault();
updateValueThrottled(input, slider, e.deltaY);
}
}
}, { passive: false });
}