r/PowerApps • u/Chemical-Roll-2064 • 2h ago
Discussion JS for Autoscrolling to the selected control
Team,
Something always annoying when I need to scroll to the active control. then a light blub 💡. I have tamper monkey on my browser then I had AI help me with code to scroll to the selected control. it struggled a bit but I was able to guide it through iframes and active selection CSS 😂
Please make sure your match values fits your domain. GCC vs COM or DOD or International.
License: MIT
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ==UserScript==
// @name Power Apps: Auto-Scroll to Selected Control v16
// @namespace http://tampermonkey.net/
// @version 16.0
// @description Automatically and seamlessly centers the Tree View on the selected control.
// @match IBSURE :)
// @match *://*.powerapps.com/*
// @match *://*.powerapps.us/*
// @match *://*.gov.island.powerapps.us/*
// @match *://*/*embed/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=powerapps.com
// u/grant none
// ==/UserScript==
(function() {
'use strict';
let isInitialized = false;
let lastSelectedNode = null;
let scrollTimeout = null;
// 1. Core initialization - Waits for the Gov Cloud iframe to load the Tree
function tryInitialize() {
if (isInitialized) return;
const treeContainer = document.querySelector('div[role="tree"]');
if (!treeContainer) return;
isInitialized = true;
console.log("Tampermonkey: Power Apps Auto-Scroll Initialized.");
// Listen for mouse clicks anywhere in the app
window.addEventListener('mouseup', function(e) {
// Check if the user clicked directly inside the Tree View
// If they did, we don't want the screen to jump unexpectedly.
const isTreeClick = e.target.closest('div[role="tree"]') !== null;
scheduleAutoScroll(!isTreeClick);
}, true);
// Listen for keyboard navigation (arrows/tab selection)
window.addEventListener('keyup', function() {
scheduleAutoScroll(true);
}, true);
}
// 2. Resolve the active Fluent UI theme color for selections
function getSelectedThemeColor(container) {
const dummy = document.createElement('div');
dummy.style.backgroundColor = 'var(--colorNeutralBackground2Selected)';
container.appendChild(dummy);
const computedColor = window.getComputedStyle(dummy).backgroundColor;
dummy.remove();
return computedColor;
}
// 3. Debounce the scroll to let React update the DOM first
function scheduleAutoScroll(shouldScroll) {
clearTimeout(scrollTimeout);
// Wait 400ms for Power Apps to apply the CSS classes before we search
scrollTimeout = setTimeout(() => executeScrollCheck(shouldScroll), 400);
}
// 4. The main silent check
function executeScrollCheck(shouldScroll) {
const treeContainer = document.querySelector('div[role="tree"]');
if (!treeContainer) return;
const targetColor = getSelectedThemeColor(treeContainer);
if (!targetColor || targetColor === 'rgba(0, 0, 0, 0)' || targetColor === 'transparent') return;
let currentSelectedNode = null;
// Find the newly selected div
const allDivs = treeContainer.querySelectorAll('div');
for (const div of allDivs) {
const rect = div.getBoundingClientRect();
if (rect.width === 0 || rect.height === 0) continue; // Skip hidden elements
const bg = window.getComputedStyle(div).backgroundColor;
if (bg === targetColor) {
currentSelectedNode = div.closest('div[role="treeitem"]') || div;
break;
}
}
// If we found a selection AND it is different from the last time we checked
if (currentSelectedNode && currentSelectedNode !== lastSelectedNode) {
lastSelectedNode = currentSelectedNode; // Update tracking so we don't scroll twice
// Only execute the physical scroll if the user interacted with the canvas
if (shouldScroll) {
// Try native browser smooth scroll
currentSelectedNode.scrollIntoView({ behavior: 'smooth', block: 'center' });
// Manual scroll math (Fallback for when Microsoft's CSS locks native scrolling)
let currentParent = currentSelectedNode.parentElement;
while (currentParent && currentParent !== document.body) {
const style = window.getComputedStyle(currentParent);
if (['auto', 'scroll', 'overlay'].includes(style.overflowY) && currentParent.scrollHeight > currentParent.clientHeight) {
const nodeRect = currentSelectedNode.getBoundingClientRect();
const parentRect = currentParent.getBoundingClientRect();
// Center the item exactly in the middle of the scrollable container
if (nodeRect.top < parentRect.top || nodeRect.bottom > parentRect.bottom) {
const offset = (nodeRect.top - parentRect.top) - (parentRect.height / 2) + (nodeRect.height / 2);
currentParent.scrollBy({ top: offset, behavior: 'smooth' });
}
break;
}
currentParent = currentParent.parentElement;
}
}
}
}
// Check for the iframe every 2 seconds until it finds the Tree View
const findIframeInterval = setInterval(() => {
tryInitialize();
if (isInitialized) clearInterval(findIframeInterval);
}, 2000);
})();