Jared Smith
Jared Smith

Reputation: 21965

Google maps does not relinquish key presses to input in shadow DOM

Sorry for the length of the code to reproduce. The problem is that the google maps API seems to not forward certain key presses to an element in shadow DOM. If you load the following code (supplying your own API key), pan the map with the arrow keys, then try to type in the top input on the screen you'll see that the 'm', 'k', and arrow keys (at least) do not make it to the input. Strangely, the keypress and keydown events for those keys get triggered on the input, but not, importantly, input. The arrow keys continue to be able to pan the map despite it no longer having focus. Typing into the bottom input (in regular DOM) works as expected.

NOTE:

  1. if you don't pan the map with the arrow keys the input works as expected.
  2. if you don't place the input in shadow DOM it works as expected.

So the problem seems to be that once the map gets keyboard focus it refuses to relinquish it. I tried giving the div with the input a tabindex and focusing it. Does not resolve the problem.

<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, minimum-scale=1.0">
<meta charset="utf-8"/>
<style>
html, body {
  font-family: sans-serif;
  width: 100%;
  height: 100%;
  margin: 0;
}

#map-div {
  width: 100%;
  height: 100%;
}

#shadow {
  position: fixed;
  top: 50px;
  left: 50px;
  z-index: 1000;
}

#input {
  position: fixed;
  top: 100px;
  left: 50px;
  z-index: 1000;
}
</style>
</head>
<body>
  <div id="shadow"></div>
  <input id="input"></input>
  <div id="map-div"></div>
<script>
  document.querySelector('#shadow')
    .attachShadow({mode:'open'})
    .appendChild(document.createElement('input'));

  window._mapsLoaded = () => {
    let map = new google.maps.Map(document.querySelector('#map-div'), {
      center: new google.maps.LatLng(39.75, -86.16),
      zoom: 11,
    });
  }
</script>
<script src="https://maps.googleapis.com/maps/api/js?key=$API_KEY&callback=_mapsLoaded"></script>
</body>
</html>

Upvotes: 4

Views: 472

Answers (2)

Jared Smith
Jared Smith

Reputation: 21965

This is a bug in the google maps API. I have temporarily worked around it as follows:

let mapDiv = document.querySelector('#map-div');
let map = new google.maps.Map(mapDiv, {
  center: new google.maps.LatLng(39.75, -86.16),
  zoom: 11,
  keyboardShortcuts: false, // disables the API keyboard handlers
});

// The added cases on the arrows are to cope with Edge's
// non-standard key values
let keymap = key => {
  switch (key) {
    case 'ArrowUp':
    case 'Up':
      return 38;

    case 'ArrowDown':
    case 'Down':
      return 40;

    case 'ArrowLeft':
    case 'Left':
      return 37;

    case 'ArrowRight':
    case 'Right':
      return 39;

    case 'Add':
    case 'Equal':
      return 187;

    case 'Minus':
    case 'Subtract':
      return 189;

    default: return 0; // browsers give keyCode 0 for unknown
  }
};

let keydownHandler = e => {
  let value = e.keyCode || keymap(e.key);
  switch (value) {
    case 107: // numpad add
    case 187: // equals/plus
      map.setZoom(map.getZoom() + 1);
      break;

    case 109: // numpad subtract
    case 189: // minus
      map.setZoom(map.getZoom() - 1);
      break;

    case 37: // left
      map.panBy(-50, 0);
      break;

    case 38: // up
      map.panBy(0, 50);
      break;

    case 39: // right
      map.panBy(50, 0);
      break;

    case 40: // down
      map.panBy(0, -50);
      break;

  }
};

// Here we'll track keydowns only when the mouse is over
// the map container:
mapDiv.addEventListener('mouseenter', e => {
  document.addEventListener('keydown', keydownHandler);
});

mapDiv.addEventListener('mouseleave', e => {
  document.removeEventListener('keydown', keydownHandler);
});

Basically I disable the maps API keyboard handling and substitute my own. You can adjust the pan values on the arrow keys for smoother scrolling, but I've timed out on this for now.

Upvotes: 3

Franco Vizcarra
Franco Vizcarra

Reputation: 426

As workaround you can go to the input's location on the dom document using the window.location method. I've modified your sample to by adding an onfocus event to the #shadow input.

<script>
document.querySelector('#shadow')
  .attachShadow({mode:'open'})
  .appendChild(document.createElement('input'));

window._mapsLoaded = () => {
  let map = new google.maps.Map(document.querySelector('#map-div'), {
    center: new google.maps.LatLng(39.75, -86.16),
    zoom: 11,
  });
}

function focusOnShadow() {
    window.location.hash = '#tries';
}
</script>

Here is the working sample on jsfiddle.

Update: I see that I've only managed to fix the 'm' and 'k' problem but the arrow key behavior persists.

Upvotes: 1

Related Questions