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>

df8f2a477a3200a8d31409bd482fec97aa79549b

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
3 files changed, +72 -4Ignore whitespace
public/index.html+4 -4
@@ -253,7 +253,7 @@
253253 </div>
254254 <div id="common-gen-settings-block" class="width100p">
255255 <div id="pro-settings-block" class="flex-container gap10h5v justifyCenter">
256256 <div id="amount_gen_block" class="range-block-range-and-counter alignitemscenter flex-container marginBot5 flexFlowColumn flexBasis48p flexGrow flexShrink gap0">
257257 <small data-i18n="response legth(tokens)">Response (tokens)</small>
258258 <input class="neo-range-slider" type="range" id="amount_gen" name="volume" min="16" max="2048" step="1">
259259 <div data-randomization-disabled="true" class="wide100p">
@@ -284,7 +284,7 @@
284284 </label>
285285 </div>
286286 </div>
287287 <div id="max_context_block" class="range-block-range-and-counter alignitemscenter flex-container marginBot5 flexFlowColumn flexBasis48p flexGrow flexShrink gap0">
288288 <small data-i18n="context size(tokens)">Context (tokens)</small>
289289 <input class="neo-range-slider" type="range" id="max_context" name="volume" min="512" max="8192" step="64">
290290 <div data-randomization-disabled="true" class="wide100p">
@@ -648,7 +648,7 @@
648648 Max Response Length (tokens)
649649 </div>
650650 <div class="wide100p">
651651 <input type="number" id="openai_max_tokens" name="openai_max_tokens" class="text_pole" min="1" max="65536" step="1">
652652 </div>
653653 </div>
654654 <div class="range-block" data-source="openai,custom,xai,aimlapi,moonshot,azure_openai">
@@ -4214,7 +4214,7 @@
42144214 Token Padding
42154215 </small>
42164216 </div>
42174217 <input id="token_padding" class="text_pole textarea_compact" type="number" min="-2048" max="2048" step="1" />
42184218 </div>
42194219 </div>
42204220 <div>
public/script.js+2 -0
@@ -270,6 +270,7 @@ import { getSystemMessageByType, initSystemMessages, SAFETY_CHAT, sendSystemMess
270270import { event_types, eventSource } from './scripts/events.js';
271271import { initAccessibility } from './scripts/a11y.js';
272272import { applyStreamFadeIn } from './scripts/util/stream-fadein.js';
273+import { initDomHandlers } from './scripts/dom-handlers.js';
273274
274275// API OBJECT FOR EXTERNAL WIRING
275276globalThis.SillyTavern = {
@@ -640,6 +641,7 @@ async function firstLoadInit() {
640641
641642 showLoader();
642643 registerPromptManagerMigration();
644+ initDomHandlers();
643645 initStandaloneMode();
644646 initLibraryShims();
645647 addShowdownPatch(showdown);
public/scripts/dom-handlers.js+66 -0
@@ -0,0 +1,66 @@
1+import { throttle } from './utils.js';
2+
3+export function initDomHandlers() {
4+ handleInputWheel();
5+}
6+
7+/**
8+ * Trap mouse wheel inside of focused number inputs to prevent scrolling their containers.
9+ * Instead of firing wheel events, manually update both slider and input values.
10+ * This also makes wheel work inside Firefox.
11+ */
12+function handleInputWheel() {
13+ const minInterval = 25; // ms
14+
15+ /**
16+ * Update input and slider values based on wheel delta
17+ * @param {HTMLInputElement} input The number input element
18+ * @param {HTMLInputElement|null} slider The associated range input element, if any
19+ * @param {number} deltaY The wheel deltaY value
20+ */
21+ function updateValue(input, slider, deltaY) {
22+ const currentValue = parseFloat(input.value);
23+ const step = parseFloat(input.step);
24+ const min = parseFloat(input.min);
25+ const max = parseFloat(input.max);
26+
27+ // Sanity checks before trying to calculate new value
28+ if (isNaN(currentValue) || isNaN(step) || step <= 0 || deltaY === 0) return;
29+
30+ // Calculate new value based on wheel movement delta (negative = up, positive = down)
31+ let newValue = currentValue + (deltaY > 0 ? -step : step);
32+ // Ensure it's a multiple of step
33+ newValue = Math.round(newValue / step) * step;
34+ // Ensure it's within the min and max range (NaN-aware)
35+ newValue = !isNaN(min) ? Math.max(newValue, min) : newValue;
36+ newValue = !isNaN(max) ? Math.min(newValue, max) : newValue;
37+ // Simple fix for floating point precision issues
38+ newValue = Math.round(newValue * 1e10) / 1e10;
39+
40+ // Update both input and slider values
41+ input.value = newValue.toString();
42+ if (slider) slider.value = newValue.toString();
43+ // Trigger input event (just ONE) to update any listeners
44+ const inputEvent = new Event('input', { bubbles: true });
45+ input.dispatchEvent(inputEvent);
46+ }
47+
48+ const updateValueThrottled = throttle(updateValue, minInterval);
49+
50+ document.addEventListener('wheel', (e) => {
51+ // Try to carefully narrow down if we even need to fire this handler
52+ const input = document.activeElement instanceof HTMLInputElement ? document.activeElement : null;
53+ if (input && input.type === 'number' && input.hasAttribute('step')) {
54+ const parent = input.closest('.range-block-range-and-counter') ?? input.closest('div') ?? input.parentElement;
55+ const slider = /** @type {HTMLInputElement} */ (parent?.querySelector('input[type="range"]'));
56+
57+ // Stop propagation for either target
58+ if (e.target === input || (slider && e.target === slider)) {
59+ e.stopPropagation();
60+ e.preventDefault();
61+
62+ updateValueThrottled(input, slider, e.deltaY);
63+ }
64+ }
65+ }, { passive: false });
66+}