focus: keeping it politely

by David Iglesias

HTML

<div tabindex="0" id="container">
  <input type="text" id="potato" />
  <br />
  <label>
    <input type="checkbox" id="transfer" />
    Move focus to #container
  </label>
</div>
<!-- Log -->
<pre id="output" tabindex="0">activeElement</pre>

CSS

/* Highlight the current activeElement */
body:not(:focus-within), :focus {
  background-color: #fabada;
}

/* Eye candy */
* {
  box-sizing: border-box;
  font-family: sans-serif;
}
html, body {
  height: 100%; margin: 0;
  overflow: hidden;
}
#container {
  padding: 8px;
}
pre {
  font-family: monospace;
  position: fixed;
  bottom: 0; right: 0; left: 0;
  border: 1px solid #999;
  border-radius: 4px;
  background: #ddd;
  margin: 4px; padding: 4px;
  max-height: 100px;
  overflow-y: scroll;
}

JavaScript

// Blur potato by transferring focus to container
function _transfer() {
	// This if can happen if you tab out of `potato`.
	if (document.activeElement !== potato) return; 
  container.focus({ preventScroll: true });
}

// Blur potato by using the blur method
function _blur() {
  potato.blur();
}

let blurTimeout;
// "Blurs" the `potato` input.
function scheduleBlur() {
  if (blurTimeout) {
    clearTimeout(blurTimeout);
  }
  if (transfer.checked) {
  	log('* Moving focus to \#container in 5 seconds...');
    blurTimeout = setTimeout(_transfer, 5000);
  } else {
    log('* Calling .blur() in 5 seconds...');
    blurTimeout = setTimeout(_blur, 5000);  
  }
}

potato.addEventListener('focus', scheduleBlur);
transfer.addEventListener('change', () => {
  potato.focus();
});
potato.addEventListener('blur', () => {
  log('* potato blur event')
});

function log(what) {
  output.innerText = `${what}\n${output.innerText}`;
}

// Watch the active Element
var lastActiveElement;
setInterval(() => {
  var currentActiveElement = document.activeElement;
  if (currentActiveElement != lastActiveElement) {
    log(`> ${currentActiveElement.tagName}#${currentActiveElement.id}`);
		lastActiveElement = currentActiveElement;
  }
}, 100);