Repository: boardgameio/boardgame.io (ts)

Issue:
Debug panel styling of selects affects page elements
```
  .debug-panel :global(button, select) {
    cursor: pointer;
    outline: none;
    background: #eee;
    border: 1px solid #bbb;
    color: #555;
    padding: 3px;
    border-radius: 3px;
  }
```

It appears this CSS captures the 'button' styling to be within the debug menu but does not for the 'select' styling.  See Firefox's view of this:

![image](https://user-images.githubusercontent.com/1338129/90780270-35c29c00-e2f6-11ea-915b-79dce316f528.png)

The `button` styling only affects things within the debug panel, but `select` affects all.

Pull request description:
feat(debug): Events shortcuts, stages support & minor fixes
This PR pulls together some small features and bug fixes to improve the debug panel:

- Add keyboard shortcuts for game events. Instead of “End Turn” etc. being shown as buttons, they are rendered using the same component as moves (supporting argument input). I’ve assigned the shortcuts <kbd>7</kbd>, <kbd>8</kbd>, <kbd>9</kbd> for these. (c5683da, e107103)

  <img width="278" alt="Screenshot 2020-09-01 at 02 30 41" src="https://user-images.githubusercontent.com/357379/91781683-2c410a00-ebfb-11ea-89bf-402b6eb63004.png">

- Support dispatching moves as any active player when using stages (as requested in #771). (03f7b2c)

- Make the player ID buttons keyboard accessible. This isn’t quite the same as adding a shortcut for toggling through player IDs, but it seems a fairly good solution to tab through and activate the desired ID. (9a640c4)

- Fix a logic bug in the Info pane. (e757efe)

- Fix a bug when the interactive function component received focus using the tab key. (7868ff3)

- Fix #782 and some other styling refactors/tweaks. (5c4eacd, 08afc9f, cefca05)

Patch:
diff --git a/src/client/debug/Debug.svelte b/src/client/debug/Debug.svelte
index 57721c7..fc06fe1 100644
--- a/src/client/debug/Debug.svelte
+++ b/src/client/debug/Debug.svelte
@@ -80,9 +80,14 @@
     overflow-y: scroll;
   }
 
-  .debug-panel :global(button, select) {
+  .debug-panel :global(button),
+  .debug-panel :global(select) {
     cursor: pointer;
-    outline: none;
+    font-size: 14px;
+    font-family: monospace;
+  }
+
+  .debug-panel :global(select) {
     background: #eee;
     border: 1px solid #bbb;
     color: #555;
@@ -90,20 +95,6 @@
     border-radius: 3px;
   }
 
-  .debug-panel :global(button) {
-    padding-left: 10px;
-    padding-right: 10px;
-  }
-
-  .debug-panel :global(button:hover) {
-    background: #ddd;
-  }
-
-  .debug-panel :global(button:active) {
-    background: #888;
-    color: #fff;
-  }
-
   .debug-panel :global(section) {
     margin-bottom: 20px;
   }
diff --git a/src/client/debug/info/Info.svelte b/src/client/debug/info/Info.svelte
index 4b485ef..ec7ecf9 100644
--- a/src/client/debug/info/Info.svelte
+++ b/src/client/debug/info/Info.svelte
@@ -14,10 +14,7 @@
   <Item name="gameID" value={client.gameID} />
   <Item name="playerID" value={client.playerID} />
   <Item name="isActive" value={$client.isActive} />
-  {#if $client.isMultiplayer}
-    <span>
-      <Item name="isConnected" value={$client.isConnected} />
-      <Item name="isMultiplayer" value={$client.isMultiplayer} />
-    </span>
+  {#if client.multiplayer}
+    <Item name="isConnected" value={$client.isConnected} />
   {/if}
 </section>
diff --git a/src/client/debug/main/InteractiveFunction.svelte b/src/client/debug/main/InteractiveFunction.svelte
index ad5648e..08438a9 100644
--- a/src/client/debug/main/InteractiveFunction.svelte
+++ b/src/client/debug/main/InteractiveFunction.svelte
@@ -76,6 +76,7 @@
   <span
     class="arg-field"
     bind:this={span}
+    on:focus={Activate}
     on:blur={Deactivate}
     on:keypress|stopPropagation={() => {}}
     on:keydown={OnKeyDown}
diff --git a/src/client/debug/main/Main.svelte b/src/client/debug/main/Main.svelte
index f530cf7..bc6bf01 100644
--- a/src/client/debug/main/Main.svelte
+++ b/src/client/debug/main/Main.svelte
@@ -7,7 +7,7 @@
   import PlayerInfo from './PlayerInfo.svelte';
   import { AssignShortcuts } from '../utils/shortcuts';
 
-  const shortcuts = AssignShortcuts(client.moves, client.events, 'mlia');
+  const shortcuts = AssignShortcuts(client.moves, 'mlia');
 
   function SanitizeCtx(ctx) {
     let r = {};
@@ -19,15 +19,12 @@
     return r;
   }
 
-  let playerID = client.playerID;
+  let { playerID, moves, events } = client;
   let ctx = {};
   let G = {};
   client.subscribe((state) => {
-    if (state) {
-      G = state.G;
-      ctx = state.ctx;
-    }
-    playerID = client.playerID;
+    if (state) ({ G, ctx } = state);
+    ({ playerID, moves, events } = client);
   });
 </script>
 
@@ -35,6 +32,7 @@
   .tree {
     --json-tree-font-family: monospace;
     --json-tree-font-size: 14px;
+    --json-tree-null-color: #757575;
   }
 
   label {
@@ -52,19 +50,6 @@
     margin: none;
     margin-bottom: 5px;
   }
-
-  .events {
-    display: flex;
-    flex-direction: column;
-  }
-
-  .events button {
-    width: 100px;
-  }
-
-  .events button:not(:last-child) {
-    margin-bottom: 10px;
-  }
 </style>
 
 <section>
@@ -83,7 +68,7 @@
 
 <section>
   <h3>Moves</h3>
-  {#each Object.entries(client.moves) as [name, fn]}
+  {#each Object.entries(moves) as [name, fn]}
     <li>
       <Move shortcut={shortcuts[name]} {fn} {name} />
     </li>
@@ -94,14 +79,20 @@
   <h3>Events</h3>
 
   <div class="events">
-  {#if client.events.endTurn}
-    <button on:click={() => client.events.endTurn()}>End Turn</button>
+  {#if ctx.activePlayers && events.endStage}
+    <li>
+      <Move name="endStage" shortcut={7} fn={events.endStage} />
+    </li>
   {/if}
-  {#if ctx.phase && client.events.endPhase}
-    <button on:click={() => client.events.endPhase()}>End Phase</button>
+  {#if events.endTurn}
+    <li>
+      <Move name="endTurn" shortcut={8} fn={events.endTurn} />
+    </li>
   {/if}
-  {#if ctx.activePlayers && client.events.endStage}
-    <button on:click={() => client.events.endStage()}>End Stage</button>
+  {#if ctx.phase && events.endPhase}
+    <li>
+      <Move name="endPhase" shortcut={9} fn={events.endPhase} />
+    </li>
   {/if}
   </div>
 </section>
diff --git a/src/client/debug/main/PlayerInfo.svelte b/src/client/debug/main/PlayerInfo.svelte
index 0982645..e6cd89b 100644
--- a/src/client/debug/main/PlayerInfo.svelte
+++ b/src/client/debug/main/PlayerInfo.svelte
@@ -32,6 +32,7 @@
     background: #eee;
     border: 3px solid #fefefe;
     box-sizing: content-box;
+    padding: 0;
   }
 
   .player.current {
@@ -47,12 +48,12 @@
 
 <div class="player-box">
   {#each players as player}
-    <div
+    <button
       class="player"
       class:current={player == ctx.currentPlayer}
       class:active={player == playerID}
       on:click={() => OnClick(player)}>
       {player}
-    </div>
+    </button>
   {/each}
 </div>
diff --git a/src/client/debug/utils/shortcuts.js b/src/client/debug/utils/shortcuts.js
index 7e50b17..e29fe91 100644
--- a/src/client/debug/utils/shortcuts.js
+++ b/src/client/debug/utils/shortcuts.js
@@ -6,17 +6,9 @@
  * https://opensource.org/licenses/MIT.
  */
 
-export function AssignShortcuts(moveNames, eventNames, blacklist) {
+export function AssignShortcuts(moveNames, blacklist) {
   let shortcuts = {};
 
-  const events = {};
-  for (let name in moveNames) {
-    events[name] = name;
-  }
-  for (let name in eventNames) {
-    events[name] = name;
-  }
-
   let taken = {};
   for (let i = 0; i < blacklist.length; i++) {
     const c = blacklist[i];
@@ -26,7 +18,7 @@ export function AssignShortcuts(moveNames, eventNames, blacklist) {
   // Try assigning the first char of each move as the shortcut.
   let t = taken;
   let canUseFirstChar = true;
-  for (let name in events) {
+  for (const name in moveNames) {
     let shortcut = name[0];
     if (t[shortcut]) {
       canUseFirstChar = false;
@@ -44,7 +36,7 @@ export function AssignShortcuts(moveNames, eventNames, blacklist) {
   t = taken;
   let next = 97;
   shortcuts = {};
-  for (let name in events) {
+  for (const name in moveNames) {
     let shortcut = String.fromCharCode(next);
 
     while (t[shortcut]) {


Test patch:
diff --git a/src/client/debug/utils/shortcuts.test.js b/src/client/debug/utils/shortcuts.test.js
index d2a0b82..d294781 100644
--- a/src/client/debug/utils/shortcuts.test.js
+++ b/src/client/debug/utils/shortcuts.test.js
@@ -14,7 +14,7 @@ test('first char is used', () => {
     playCard: () => {},
   };
 
-  const shortcuts = AssignShortcuts(moves, {}, '');
+  const shortcuts = AssignShortcuts(moves, '');
 
   expect(shortcuts).toEqual({
     clickCell: 'c',
@@ -28,7 +28,7 @@ test('a-z if cannot use first char', () => {
     takeToken: () => {},
   };
 
-  const shortcuts = AssignShortcuts(moves, {}, '');
+  const shortcuts = AssignShortcuts(moves, '');
 
   expect(shortcuts).toEqual({
     takeCard: 'a',
@@ -41,7 +41,7 @@ test('a-z if blacklist prevents using first char', () => {
     clickCell: () => {},
   };
 
-  const shortcuts = AssignShortcuts(moves, {}, 'c');
+  const shortcuts = AssignShortcuts(moves, 'c');
 
   expect(shortcuts).toEqual({
     clickCell: 'a',