Selaa lähdekoodia

Fix: stepper popover broken on task-assignment cells + not centred

Two bugs from the Phase 17 stepper (b457896) / hover tweak (c07af1c):

1. **Popover buttons did nothing in tables other than the Arbeitstage
   grid.** readBounds() read `boundInput.min` / `boundInput.max` — the
   IDL properties, which return the empty string when the attribute
   isn't set. `Number("")` is 0 (not NaN), so task-assignment cells —
   which declare `min="0"` but have no `max` — were treated as bounded
   to [0, 0]. clampToStep dutifully clamped every step back to 0 and
   the cell refused to increment. Switched to getAttribute(), which
   returns null for missing attributes; parse only when the string is
   non-empty, otherwise return NaN so the bound is treated as open
   and the range slider correctly stays hidden.

2. **Popover aligned to the input's left edge, looked off-centre over
   narrow day / RTB / assignment cells.** Changed the horizontal
   anchor to centre over the input's midpoint
   (`rect.left + (rect.width - pw) / 2`), keeping the existing
   viewport clamp on both sides so it doesn't run off-screen.

No schema / routes / audit changes. phpunit: 88 / 208 unchanged.
node --check clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
achiappa 2 viikkoa sitten
vanhempi
sitoutus
832b25614f
1 muutettua tiedostoa jossa 18 lisäystä ja 7 poistoa
  1. 18 7
      public/assets/js/number-stepper.js

+ 18 - 7
public/assets/js/number-stepper.js

@@ -83,14 +83,25 @@
         pop.addEventListener('pointerleave', scheduleClose);
     }
 
+    // Read bounds via getAttribute so an absent `max` reports as NaN
+    // instead of being coerced through Number("") === 0. Without this,
+    // task-assignment cells (which set min="0" but have no max) would
+    // be clamped to [0, 0] and refuse to increment in any table but
+    // the Arbeitstage grid.
     function readBounds() {
-        const step = Number(boundInput.step);
-        const min  = Number(boundInput.min);
-        const max  = Number(boundInput.max);
+        function parseAttr(name) {
+            const raw = boundInput.getAttribute(name);
+            if (raw === null || raw === '') { return NaN; }
+            const n = Number(raw);
+            return Number.isFinite(n) ? n : NaN;
+        }
+        const step = parseAttr('step');
+        const min  = parseAttr('min');
+        const max  = parseAttr('max');
         return {
             step: Number.isFinite(step) && step > 0 ? step : 1,
-            min:  Number.isFinite(min)  ? min  : NaN,
-            max:  Number.isFinite(max)  ? max  : NaN,
+            min:  min,
+            max:  max,
         };
     }
 
@@ -131,8 +142,8 @@
         } else {
             top = rect.bottom + GAP;
         }
-        // Horizontal: align to the input's left edge, clamped to viewport.
-        let left = rect.left;
+        // Horizontal: centre over the input's midpoint, clamped to viewport.
+        let left = rect.left + (rect.width - pw) / 2;
         const MARGIN = 4;
         if (left + pw > vw - MARGIN) { left = vw - pw - MARGIN; }
         if (left < MARGIN) { left = MARGIN; }