T3.chat - Autofocus message input

Automatically focuses the message input field when you start writing or pasting.

  1. // ==UserScript==
  2. // @name T3.chat - Autofocus message input
  3. // @namespace Violentmonkey Scripts
  4. // @match https://beta.t3.chat/*
  5. // @match https://t3.chat/*
  6. // @grant none
  7. // @version 2025-05-19_12:27
  8. // @author koza.dev
  9. // @description Automatically focuses the message input field when you start writing or pasting.
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. window.addEventListener('keydown', function(e) {
  15. // Ignore if modifier keys are pressed, or if default is already prevented
  16. if (e.ctrlKey || e.altKey || e.metaKey || e.shiftKey || e.defaultPrevented) {
  17. return
  18. }
  19.  
  20. // We're interested in single, printable characters
  21. if (e.key.length !== 1) {
  22. return
  23. }
  24.  
  25. const chatInput = document.getElementById('chat-input')
  26. if (!chatInput) {
  27. return // Chat input not found
  28. }
  29.  
  30. const active = document.activeElement
  31.  
  32. // If chatInput is ALREADY focused, let the browser handle everything natively.
  33. // This is key for Svelte's reactivity to work as expected.
  34. if (active === chatInput) {
  35. return
  36. }
  37.  
  38. // If another input, textarea, or contenteditable element is focused, do nothing.
  39. if (
  40. active &&
  41. (active.tagName === 'INPUT' ||
  42. active.tagName === 'TEXTAREA' ||
  43. active.isContentEditable)
  44. ) {
  45. return
  46. }
  47.  
  48. // If we're here, a printable key was pressed, chatInput exists,
  49. // it's not focused, and no other editable field is focused.
  50. // So, we'll take over.
  51.  
  52. e.preventDefault() // Prevent typing into the body or triggering shortcuts
  53.  
  54. chatInput.focus()
  55.  
  56. const character = e.key
  57. const currentVal = chatInput.value
  58. const selectionStart = chatInput.selectionStart
  59. const selectionEnd = chatInput.selectionEnd
  60.  
  61. // Insert character, replacing selection if any
  62. chatInput.value =
  63. currentVal.substring(0, selectionStart) +
  64. character +
  65. currentVal.substring(selectionEnd)
  66.  
  67. // Move cursor after the inserted character
  68. const newCursorPos = selectionStart + character.length
  69. chatInput.selectionStart = newCursorPos
  70. chatInput.selectionEnd = newCursorPos
  71.  
  72. // Dispatch a detailed 'input' event that Svelte should recognize
  73. chatInput.dispatchEvent(
  74. new InputEvent('input', {
  75. bubbles: true,
  76. cancelable: true,
  77. data: character,
  78. inputType: 'insertText'
  79. })
  80. )
  81.  
  82. // Dispatch 'change' event for good measure, though 'input' is usually primary
  83. chatInput.dispatchEvent(new Event('change', { bubbles: true }))
  84. })
  85.  
  86. // Handle paste events the same way
  87. window.addEventListener('paste', function(e) {
  88. const chatInput = document.getElementById('chat-input')
  89. if (!chatInput) {
  90. return
  91. }
  92.  
  93. const active = document.activeElement
  94.  
  95. if (active === chatInput) {
  96. return
  97. }
  98.  
  99. if (
  100. active &&
  101. (active.tagName === 'INPUT' ||
  102. active.tagName === 'TEXTAREA' ||
  103. active.isContentEditable)
  104. ) {
  105. return
  106. }
  107.  
  108. e.preventDefault()
  109. chatInput.focus()
  110.  
  111. const clipboardData = e.clipboardData || window.clipboardData
  112. const pasteData = clipboardData.getData('text')
  113.  
  114. const currentVal = chatInput.value
  115. const selectionStart = chatInput.selectionStart
  116. const selectionEnd = chatInput.selectionEnd
  117.  
  118. chatInput.value =
  119. currentVal.substring(0, selectionStart) +
  120. pasteData +
  121. currentVal.substring(selectionEnd)
  122.  
  123. const newCursorPos = selectionStart + pasteData.length
  124. chatInput.selectionStart = newCursorPos
  125. chatInput.selectionEnd = newCursorPos
  126.  
  127. chatInput.dispatchEvent(
  128. new InputEvent('input', {
  129. bubbles: true,
  130. cancelable: true,
  131. data: pasteData,
  132. inputType: 'insertText'
  133. })
  134. )
  135.  
  136. chatInput.dispatchEvent(new Event('change', { bubbles: true }))
  137. })
  138. })()