Mirror of https://github.com/improsocial/impro An extensible Bluesky client for web impro.social
5

Configure Feed

Select the types of activity you want to include in your feed.

Update keyboard gating logic for chat input

Grace Kind (Jul 16, 2026, 2:54 PM -0500) 36491f97 e51b090c

+49 -4
+1 -1
package.json
··· 1 1 { 2 2 "name": "impro", 3 - "version": "0.17.183", 3 + "version": "0.17.184", 4 4 "type": "module", 5 5 "scripts": { 6 6 "start": "rm -rf \"${BUILD_DIR:-build}\" && NODE_ENV=development eleventy --serve",
+2 -2
src/js/components/chat-input.js
··· 2 2 import { html, render } from "/js/lib/lit-html.js"; 3 3 import { sendIconTemplate } from "/js/templates/icons/sendIcon.template.js"; 4 4 import { emojiIconTemplate } from "/js/templates/icons/emojiIcon.template.js"; 5 - import { isMobileViewport, graphemeCount, getByteLength } from "/js/utils.js"; 5 + import { hasKeyboardInput, graphemeCount, getByteLength } from "/js/utils.js"; 6 6 import "/js/components/rich-text-input.js"; 7 7 import "/js/components/emoji-picker-dialog.js"; 8 8 ··· 119 119 handleKeyDown(e) { 120 120 if (e.defaultPrevented) return; 121 121 if (e.key === "Enter" && !e.shiftKey) { 122 - if (isMobileViewport()) return; 122 + if (!hasKeyboardInput()) return; 123 123 e.preventDefault(); 124 124 this.handleSend(); 125 125 }
+2
src/js/utils.js
··· 40 40 window.matchMedia("(max-width: 799px)").matches; 41 41 export const isTouchDevice = () => navigator.maxTouchPoints > 0; 42 42 export const canHover = () => window.matchMedia("(hover: hover)").matches; 43 + export const hasKeyboardInput = () => 44 + window.matchMedia("(hover: hover) and (pointer: fine)").matches; 43 45 export const isSafari = () => 44 46 /^((?!chrome|android).)*safari/i.test(navigator.userAgent); 45 47
+44 -1
tests/unit/specs/components/chat-input.test.js
··· 1 - import { describe, it, beforeEach } from "node:test"; 1 + import { describe, it, beforeEach, afterEach } from "node:test"; 2 2 import assert from "node:assert/strict"; 3 3 4 4 await import("/js/components/chat-input.js"); ··· 427 427 }); 428 428 429 429 describe("ChatInput - keyboard handling", () => { 430 + let originalMatchMedia; 431 + beforeEach(() => { 432 + originalMatchMedia = window.matchMedia; 433 + window.matchMedia = (query) => ({ 434 + matches: query === "(hover: hover) and (pointer: fine)", 435 + media: query, 436 + onchange: null, 437 + addListener: () => {}, 438 + removeListener: () => {}, 439 + addEventListener: () => {}, 440 + removeEventListener: () => {}, 441 + dispatchEvent: () => {}, 442 + }); 443 + }); 444 + afterEach(() => { 445 + window.matchMedia = originalMatchMedia; 446 + }); 447 + 430 448 function enterEvent(options = {}) { 431 449 return new window.KeyboardEvent("keydown", { 432 450 key: "Enter", ··· 448 466 getEditable(element).dispatchEvent(enterEvent()); 449 467 450 468 assert.deepEqual(receivedMessage, "Hello world"); 469 + }); 470 + 471 + it("should not send message on Enter without a keyboard input device", () => { 472 + window.matchMedia = (query) => ({ 473 + matches: false, 474 + media: query, 475 + onchange: null, 476 + addListener: () => {}, 477 + removeListener: () => {}, 478 + addEventListener: () => {}, 479 + removeEventListener: () => {}, 480 + dispatchEvent: () => {}, 481 + }); 482 + 483 + const element = createChatInput(); 484 + getRichTextInput(element).setText("Hello world"); 485 + 486 + let eventFired = false; 487 + element.addEventListener("send", () => { 488 + eventFired = true; 489 + }); 490 + 491 + getEditable(element).dispatchEvent(enterEvent()); 492 + 493 + assert.deepEqual(eventFired, false); 451 494 }); 452 495 453 496 it("should not send message on Shift+Enter", () => {