show-keys.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. // https://github.com/siddharthkp/show-keys/blob/main/index.js
  2. const prettyMap = {
  3. ArrowUp: "↑",
  4. ArrowRight: "→",
  5. ArrowDown: "↓",
  6. ArrowLeft: "←",
  7. Shift: "⇧",
  8. Meta: "⌘",
  9. Alt: "⌥",
  10. Control: "^",
  11. Escape: "esc",
  12. Backspace: "⌫",
  13. Enter: "⏎",
  14. 32: "space",
  15. CapsLock: "caps lock",
  16. Tab: "tab",
  17. }
  18. let keys = []
  19. let appearedAt = null
  20. const handler = (event) => {
  21. if (
  22. window.SHOW_KEYS_SKIP_INPUTS &&
  23. ["INPUT", "TEXTAREA"].includes(event.target.tagName)
  24. ) {
  25. return
  26. }
  27. const key =
  28. prettyMap[event.key] || prettyMap[event.which] || event.key.toUpperCase()
  29. const modifiers = {
  30. Meta: event.metaKey,
  31. Shift: event.shiftKey,
  32. Alt: event.altKey,
  33. Control: event.ctrlKey,
  34. }
  35. const newKeys = []
  36. Object.keys(modifiers)
  37. .filter((modifier) => modifiers[modifier])
  38. .forEach((modifier) => newKeys.push(prettyMap[modifier]))
  39. if (!Object.keys(modifiers).includes(event.key)) newKeys.push(key)
  40. const dismissAfterTimeout = () => {
  41. // TODO: Should probably clear this timeout
  42. window.setTimeout(() => {
  43. if (new Date() - appearedAt < 1000) dismissAfterTimeout()
  44. else {
  45. keys = []
  46. render()
  47. }
  48. }, 1000)
  49. }
  50. keys = newKeys
  51. appearedAt = new Date()
  52. render()
  53. dismissAfterTimeout()
  54. }
  55. const css = `
  56. [data-keys] {
  57. display: flex;
  58. background: rgba(0, 0, 0, 0.75);
  59. border-radius: 8px;
  60. position: fixed;
  61. bottom: 64px;
  62. left: 32px;
  63. padding: 8px 8px 12px;
  64. font-size: 24px;
  65. font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
  66. Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
  67. animation: keys-zoom-in 50ms;
  68. z-index: 99999;
  69. }
  70. [data-keys][data-children="0"] {
  71. opacity: 0;
  72. }
  73. [data-keys] [data-key] + [data-key] {
  74. margin-left: 8px;
  75. }
  76. [data-keys] [data-key] {
  77. height: 46px;
  78. min-width: 32px;
  79. padding: 16px;
  80. display: flex;
  81. justify-content: center;
  82. align-items: center;
  83. color: #2e2e2e;
  84. background: linear-gradient(#fff, #dadada);
  85. border-radius: 8px;
  86. border-top: 1px solid #f5f5f5;
  87. box-shadow: inset 0 0 25px #e8e8e8, 0 1px 0 #c3c3c3, 0 4px 0 #c9c9c9;
  88. text-shadow: 0px 1px 0px #f5f5f5;
  89. }
  90. @keyframes keys-zoom-in {
  91. from {
  92. transform: scale(0.9);
  93. }
  94. 100% {
  95. }
  96. }
  97. `
  98. const insertCSS = () => {
  99. const cssExists = document.head.querySelector("#keyscss")
  100. if (!cssExists) {
  101. const cssContainer = document.createElement("style")
  102. cssContainer.id = "keyscss"
  103. document.head.append(cssContainer)
  104. cssContainer.append(css)
  105. }
  106. }
  107. const ensureContainer = () => {
  108. let container = document.querySelector("[data-keys]")
  109. if (!container) {
  110. container = document.createElement("div")
  111. container.setAttribute("data-keys", "")
  112. document.body.append(container)
  113. return container
  114. } else {
  115. return container
  116. }
  117. }
  118. const render = () => {
  119. const container = ensureContainer()
  120. if (keys.length === 0) container.outerHTML = ``
  121. else {
  122. container.outerHTML = `
  123. <div data-keys>
  124. ${keys.map((key) => `<div data-key>${key}</div>`)}
  125. </div>
  126. `
  127. }
  128. }
  129. if (typeof window !== "undefined") {
  130. window.addEventListener("keydown", handler)
  131. insertCSS()
  132. }