|
1 | | -window.enableDetailsPaneResizer = () => { |
| 1 | +const detailsPaneState = { |
| 2 | + activeDocumentListeners: [], |
| 3 | + controller: null, |
| 4 | + dotNetRef: null |
| 5 | +}; |
| 6 | + |
| 7 | +function trackDetailsDocumentListener(event, handler) { |
| 8 | + const entry = { event, handler }; |
| 9 | + const options = detailsPaneState.controller ? { signal: detailsPaneState.controller.signal } : undefined; |
| 10 | + document.addEventListener(event, handler, options); |
| 11 | + detailsPaneState.activeDocumentListeners.push(entry); |
| 12 | + |
| 13 | + return () => { |
| 14 | + document.removeEventListener(event, handler); |
| 15 | + const i = detailsPaneState.activeDocumentListeners.indexOf(entry); |
| 16 | + if (i !== -1) { |
| 17 | + detailsPaneState.activeDocumentListeners.splice(i, 1); |
| 18 | + } |
| 19 | + }; |
| 20 | +} |
| 21 | + |
| 22 | +window.enableDetailsPaneResizer = (dotNetRef, savedHeight) => { |
| 23 | + window.disposeDetailsPaneResizer(); |
| 24 | + |
2 | 25 | const detailsPane = document.getElementById("details-pane"); |
3 | 26 | const resizer = document.getElementById("details-resizer"); |
4 | 27 |
|
5 | | - if (detailsPane == null || resizer == null) { return; } |
| 28 | + if (detailsPane == null || resizer == null) { |
| 29 | + return; |
| 30 | + } |
| 31 | + |
| 32 | + // Apply persisted height (if any) before user interaction. CSS supplies |
| 33 | + // the default height when no saved value exists. Clamp so a height saved |
| 34 | + // on a larger window can't overwhelm a smaller viewport on next launch. |
| 35 | + if (savedHeight && savedHeight > 0) { |
| 36 | + const maxHeight = Math.max(60, Math.floor(window.innerHeight * 0.8)); |
| 37 | + detailsPane.style.height = `${Math.min(savedHeight, maxHeight)}px`; |
| 38 | + } |
| 39 | + |
| 40 | + detailsPaneState.dotNetRef = dotNetRef; |
| 41 | + detailsPaneState.controller = new AbortController(); |
| 42 | + const signal = detailsPaneState.controller.signal; |
6 | 43 |
|
7 | | - let y, h = 0; |
| 44 | + let y = 0; |
| 45 | + let h = 0; |
| 46 | + let untrackMove = null; |
| 47 | + let untrackUp = null; |
8 | 48 |
|
9 | 49 | const mouseMoveHandler = function(e) { |
10 | 50 | const distance = e.clientY - y; |
| 51 | + // Match the column-resize minimum (avoids the pane vanishing entirely |
| 52 | + // and being un-grabbable). CSS min-height still applies on top. |
| 53 | + const newHeight = Math.max(30, h - distance); |
11 | 54 |
|
12 | | - detailsPane.style.height = `${h - distance}px`; |
| 55 | + detailsPane.style.height = `${newHeight}px`; |
13 | 56 | }; |
14 | 57 |
|
15 | 58 | const mouseUpHandler = function() { |
16 | | - document.removeEventListener("mousemove", mouseMoveHandler); |
17 | | - document.removeEventListener("mouseup", mouseUpHandler); |
| 59 | + if (untrackMove) { |
| 60 | + untrackMove(); |
| 61 | + untrackMove = null; |
| 62 | + } |
| 63 | + if (untrackUp) { |
| 64 | + untrackUp(); |
| 65 | + untrackUp = null; |
| 66 | + } |
| 67 | + |
| 68 | + const ref = detailsPaneState.dotNetRef; |
| 69 | + |
| 70 | + if (ref && detailsPane.isConnected) { |
| 71 | + const newHeight = parseInt(window.getComputedStyle(detailsPane).height, 10); |
| 72 | + // Catch rejection in case the .NET object was disposed mid-drag. |
| 73 | + ref.invokeMethodAsync("OnDetailsPaneHeightChanged", newHeight).catch(() => {}); |
| 74 | + } |
18 | 75 | }; |
19 | 76 |
|
20 | 77 | const mouseDownHandler = function(e) { |
| 78 | + // Only respond to primary (left) button so right-click context menus |
| 79 | + // and middle-click are not intercepted. |
| 80 | + if (e.button !== 0) { |
| 81 | + return; |
| 82 | + } |
| 83 | + |
21 | 84 | y = e.clientY; |
22 | 85 |
|
23 | 86 | const styles = window.getComputedStyle(detailsPane); |
24 | 87 | h = parseInt(styles.height, 10); |
25 | 88 |
|
26 | | - document.addEventListener("mousemove", mouseMoveHandler); |
27 | | - document.addEventListener("mouseup", mouseUpHandler); |
| 89 | + untrackMove = trackDetailsDocumentListener("mousemove", mouseMoveHandler); |
| 90 | + untrackUp = trackDetailsDocumentListener("mouseup", mouseUpHandler); |
28 | 91 | }; |
29 | 92 |
|
30 | | - resizer.addEventListener("mousedown", mouseDownHandler); |
| 93 | + resizer.addEventListener("mousedown", mouseDownHandler, { signal }); |
| 94 | +}; |
| 95 | + |
| 96 | +window.disposeDetailsPaneResizer = () => { |
| 97 | + if (detailsPaneState.controller) { |
| 98 | + detailsPaneState.controller.abort(); |
| 99 | + detailsPaneState.controller = null; |
| 100 | + } |
| 101 | + |
| 102 | + for (const { event, handler } of detailsPaneState.activeDocumentListeners) { |
| 103 | + document.removeEventListener(event, handler); |
| 104 | + } |
| 105 | + |
| 106 | + detailsPaneState.activeDocumentListeners = []; |
| 107 | + detailsPaneState.dotNetRef = null; |
31 | 108 | }; |
0 commit comments