Sfoglia il codice sorgente

Fix frozen pane selection overlays

Keep sheet selection overlays visible and clipped correctly when frozen rows or columns are active. This adds mirrored range, spill, and fill-handle overlays inside each frozen pane, reuses shared overlay placement logic, and lets fill drag interactions work from the mirrored handles as well.
Toby Chui 1 settimana fa
parent
commit
a0ea82b69b
2 ha cambiato i file con 91 aggiunte e 48 eliminazioni
  1. 21 6
      src/web/Office/sheets/sheets.css
  2. 70 42
      src/web/Office/sheets/sheets.js

+ 21 - 6
src/web/Office/sheets/sheets.css

@@ -229,28 +229,42 @@ body.dark {
 .sh-cell.i { font-style: italic; }
 .sh-cell.u { text-decoration: underline; }
 
+/* The selection overlays sit in the scrolling content just under the frozen
+   panes (12-14), so a selection scrolled up/left slides behind them instead of
+   painting over them. Each frozen pane carries a clipped mirror of the
+   overlays (.sh-frozen-ov, synced by placeOverlay) for selections inside it. */
+.sh-frozen-ov {
+    position: absolute;
+    left: 0;
+    top: 0;
+    width: 0;
+    height: 0;
+    overflow: hidden;
+    pointer-events: none;
+}
+
 /* the outline of the spilled array the active cell belongs to */
-#shSpillBox {
+#shSpillBox, .sh-ov-spill {
     position: absolute;
     border: 1px dashed var(--of-accent);
     pointer-events: none;
     box-sizing: border-box;
     display: none;
-    z-index: 15;
+    z-index: 11;
 }
 /* formula bar showing the anchor formula of a spilled cell */
 #shFxInput.sh-fx-ghost { color: var(--of-fg-soft); }
 
 /* active cell + range border + fill handle */
-#shRangeBox {
+#shRangeBox, .sh-ov-range {
     position: absolute;
     border: 2px solid var(--of-accent);
     pointer-events: none;
     box-sizing: border-box;
     display: none;
-    z-index: 15;
+    z-index: 11;
 }
-#shFillHandle {
+#shFillHandle, .sh-ov-fill {
     position: absolute;
     width: 8px;
     height: 8px;
@@ -259,7 +273,8 @@ body.dark {
     box-sizing: border-box;
     cursor: crosshair;
     display: none;
-    z-index: 16;
+    z-index: 11;
+    pointer-events: auto;
 }
 #shGrid.sh-filling, #shGrid.sh-filling .sh-cell { cursor: crosshair; }
 /* grabbing the selection border moves the block - the rule has to name

+ 70 - 42
src/web/Office/sheets/sheets.js

@@ -94,6 +94,7 @@ var SheetsApp = (function () {
     var rafPending = false, lastPointerEvt = null;
     var gridEl, cellsEl, spacerEl, colHeadIn, rowHeadIn, inputEl, rangeBoxEl, fillEl, spillBoxEl;
     var frozenRowsEl, frozenColsEl, frozenCornerEl;
+    var paneOvs = [];   // clipped overlay mirrors, one per frozen pane
 
     function esc(t) { return OfficeApp.escapeHtml(t); }
     function deep(o) { return JSON.parse(JSON.stringify(o)); }
@@ -730,6 +731,7 @@ var SheetsApp = (function () {
         if (el._shHtml === html) return;
         el._shHtml = html;
         el.innerHTML = html;
+        if (el._shOv) el.appendChild(el._shOv);   // keep the pane's overlay mirror
     }
     function renderHeaders(c1, c2, r1, r2, sl, st, sel) {
         var s = sheet(), fz = s.freeze;
@@ -784,35 +786,76 @@ var SheetsApp = (function () {
         rowHeadIn.innerHTML = out.join("");
         rowHeadIn.style.transform = "translateY(" + (-st) + "px)";
     }
+    /* The overlays (range box, spill outline, fill handle) scroll with the
+       cells underneath the frozen panes, so a selection scrolled up/left
+       hides behind them. Each frozen pane holds a mirror of the overlays,
+       clipped to the pane, which draws selections that fall inside it. */
+    function initPaneOverlays() {
+        [frozenRowsEl, frozenColsEl, frozenCornerEl].forEach(function (layer) {
+            var clip = document.createElement("div");
+            clip.className = "sh-frozen-ov";
+            clip.innerHTML = '<div class="sh-ov-spill"></div><div class="sh-ov-range"></div>' +
+                '<div class="sh-ov-fill" title="Drag to fill"></div>';
+            layer._shOv = clip;
+            layer.appendChild(clip);
+            paneOvs.push({
+                layer: layer, clip: clip,
+                spill: clip.children[0], range: clip.children[1], fill: clip.children[2]
+            });
+        });
+    }
+    function isFillHandle(el) {
+        return el === fillEl || (el && el.classList && el.classList.contains("sh-ov-fill"));
+    }
+    // size each pane's clip to the frozen region it covers
+    function sizePaneOverlays() {
+        var s = sheet(), fz = s.freeze;
+        var fw = colX[fz.c] || 0, fh = rowY[fz.r] || 0;
+        var tw = colX[s.cols] || 0, th = rowY[s.rows] || 0;
+        paneOvs.forEach(function (p) {
+            var w = p.layer === frozenRowsEl ? tw : fw;
+            var h = p.layer === frozenColsEl ? th : fh;
+            p.clip.style.width = w + "px";
+            p.clip.style.height = h + "px";
+        });
+    }
+    // show an overlay at sheet coordinates (null hides it) in the scrolling
+    // content and in every frozen pane's mirror
+    function placeOverlay(name, box) {
+        var main = { range: rangeBoxEl, spill: spillBoxEl, fill: fillEl }[name];
+        var els = [main].concat(paneOvs.map(function (p) { return p[name]; }));
+        els.forEach(function (el) {
+            if (!el) return;
+            if (!box) { el.style.display = "none"; return; }
+            el.style.display = "block";
+            el.style.left = box.x + "px";
+            el.style.top = box.y + "px";
+            if (box.w != null) {
+                el.style.width = box.w + "px";
+                el.style.height = box.h + "px";
+            }
+        });
+    }
+    function rangeRectBox(c1, r1, c2, r2) {
+        var a = cellRect(c1, r1), b = cellRect(c2, r2);
+        return { x: a.x, y: a.y, w: b.x + b.w - a.x, h: b.y + b.h - a.y };
+    }
     function renderSpillBox() {
         if (!spillBoxEl) return;
         var sp = !editing && calc && calc.spillAt ? calc.spillAt(head.c, head.r, body.active) : null;
-        if (!sp) { spillBoxEl.style.display = "none"; return; }
-        var a = cellRect(sp.c1, sp.r1), b = cellRect(sp.c2, sp.r2);
-        spillBoxEl.style.display = "block";
-        spillBoxEl.style.left = a.x + "px";
-        spillBoxEl.style.top = a.y + "px";
-        spillBoxEl.style.width = (b.x + b.w - a.x) + "px";
-        spillBoxEl.style.height = (b.y + b.h - a.y) + "px";
+        placeOverlay("spill", sp ? rangeRectBox(sp.c1, sp.r1, sp.c2, sp.r2) : null);
     }
     function renderRangeBox(sel) {
+        sizePaneOverlays();
         renderSpillBox();
         if (!sel || editing) {
-            rangeBoxEl.style.display = "none";
-            fillEl.style.display = "none";
+            placeOverlay("range", null);
+            placeOverlay("fill", null);
             return;
         }
-        var a = cellRect(sel.c1, sel.r1);
-        var b = cellRect(sel.c2, sel.r2);
-        var x = a.x, y = a.y, w = b.x + b.w - a.x, h = b.y + b.h - a.y;
-        rangeBoxEl.style.display = "block";
-        rangeBoxEl.style.left = x + "px";
-        rangeBoxEl.style.top = y + "px";
-        rangeBoxEl.style.width = w + "px";
-        rangeBoxEl.style.height = h + "px";
-        fillEl.style.display = "block";
-        fillEl.style.left = (x + w - 4) + "px";
-        fillEl.style.top = (y + h - 4) + "px";
+        var bx = rangeRectBox(sel.c1, sel.r1, sel.c2, sel.r2);
+        placeOverlay("range", bx);
+        placeOverlay("fill", { x: bx.x + bx.w - 4, y: bx.y + bx.h - 4 });
     }
 
     /* ================= sheet tabs ================= */
@@ -1407,13 +1450,8 @@ var SheetsApp = (function () {
         if (editing.viaFx) inputEl.value = el.value;
         else $("#shFxInput").val(el.value);
         // outline the picked range on the grid
-        var ra = cellRect(Math.min(a.c, b.c), Math.min(a.r, b.r));
-        var rb = cellRect(Math.max(a.c, b.c), Math.max(a.r, b.r));
-        rangeBoxEl.style.display = "block";
-        rangeBoxEl.style.left = ra.x + "px";
-        rangeBoxEl.style.top = ra.y + "px";
-        rangeBoxEl.style.width = (rb.x + rb.w - ra.x) + "px";
-        rangeBoxEl.style.height = (rb.y + rb.h - ra.y) + "px";
+        placeOverlay("range", rangeRectBox(Math.min(a.c, b.c), Math.min(a.r, b.r),
+            Math.max(a.c, b.c), Math.max(a.r, b.r)));
     }
     function beginRefPick(pos) {
         var el = refEditorEl();
@@ -1492,7 +1530,7 @@ var SheetsApp = (function () {
             }
         }
         if (e.target.closest && e.target.closest(".sh-chart")) return;   // charts handle their own
-        if (e.target === fillEl) {
+        if (isFillHandle(e.target)) {
             drag = { mode: "fill", startRg: selRange() };
             gridEl.classList.add("sh-filling");
             try { gridEl.setPointerCapture(e.pointerId); } catch (err) { }
@@ -1535,7 +1573,7 @@ var SheetsApp = (function () {
     function onGridPointerMove(e) {
         if (!drag) {
             // hover feedback: the selection border is grabbable
-            var onEdge = e.target !== fillEl && !onGridScrollbar(e) && onSelBorder(gridPos(e));
+            var onEdge = !isFillHandle(e.target) && !onGridScrollbar(e) && onSelBorder(gridPos(e));
             gridEl.classList.toggle("sh-movesel", onEdge);
             return;
         }
@@ -1561,13 +1599,7 @@ var SheetsApp = (function () {
             var dC = clamp(pos.c - drag.grab.c, -srg.c1, sheet().cols - 1 - srg.c2);
             var dR = clamp(pos.r - drag.grab.r, -srg.r1, sheet().rows - 1 - srg.r2);
             drag.mv = { dC: dC, dR: dR };
-            var ga = cellRect(srg.c1 + dC, srg.r1 + dR);
-            var gb = cellRect(srg.c2 + dC, srg.r2 + dR);
-            rangeBoxEl.style.display = "block";
-            rangeBoxEl.style.left = ga.x + "px";
-            rangeBoxEl.style.top = ga.y + "px";
-            rangeBoxEl.style.width = (gb.x + gb.w - ga.x) + "px";
-            rangeBoxEl.style.height = (gb.y + gb.h - ga.y) + "px";
+            placeOverlay("range", rangeRectBox(srg.c1 + dC, srg.r1 + dR, srg.c2 + dC, srg.r2 + dR));
         } else if (drag.mode === "refpick") {
             if (refPick && editing) refPickApply(refPick.start, pos);
         } else if (drag.mode === "fill") {
@@ -1581,12 +1613,7 @@ var SheetsApp = (function () {
                 c1: Math.min(rg.c1, rg.c1 + dC), c2: Math.max(rg.c2, rg.c2 + dC),
                 r1: Math.min(rg.r1, rg.r1 + dR), r2: Math.max(rg.r2, rg.r2 + dR)
             };
-            var a = cellRect(box.c1, box.r1), b = cellRect(box.c2, box.r2);
-            rangeBoxEl.style.display = "block";
-            rangeBoxEl.style.left = a.x + "px";
-            rangeBoxEl.style.top = a.y + "px";
-            rangeBoxEl.style.width = (b.x + b.w - a.x) + "px";
-            rangeBoxEl.style.height = (b.y + b.h - a.y) + "px";
+            placeOverlay("range", rangeRectBox(box.c1, box.r1, box.c2, box.r2));
         }
     }
     function onGridPointerUp(e) {
@@ -3033,6 +3060,7 @@ var SheetsApp = (function () {
         rangeBoxEl = document.getElementById("shRangeBox");
         spillBoxEl = document.getElementById("shSpillBox");
         fillEl = document.getElementById("shFillHandle");
+        initPaneOverlays();
 
         gridEl.setAttribute("tabindex", "0");
         gridEl.addEventListener("scroll", function () {