Merge branch 'chat-input' into 'master'

MAINT: Rewrite the chat input auto expansion logic and allow the chat reply DOM to take advantage of this

See merge request 
This commit is contained in:
BondageProjects 2025-04-05 19:01:24 +00:00
commit 6d8edae80d
4 changed files with 104 additions and 71 deletions
BondageClub
CSS
Screens/Online/ChatRoom
Scripts

View file

@ -344,5 +344,5 @@ select:invalid:not(:disabled):not(:read-only) {
}
.hidden {
display: none;
}
display: none !important;
}

View file

@ -58,19 +58,18 @@
#chat-room-div {
--button-size: min(8vh, 4vw);
}
/* Do NOT use min-/max-content for grid rows, as this will cause serious lag related to layour recomputing once the chat log starts to fill up */
display: grid;
grid-template-rows: 89.6% 10%;
gap: 0.4%;
#chat-room-div > * {
margin-bottom: max(3px, min(0.4vh, 0.2vw));
}
#TextAreaChatLog {
background-color: white;
border: 1px solid black;
word-wrap: break-word;
padding: 0 !important;
overflow-y: scroll;
height: max-content;
}
#chat-room-bot {
@ -102,25 +101,30 @@
border-left: min(0.2vh, 0.1vw) inset black;
justify-self: center;
}
#chat-room-reply-indicator-text {
color: var(--base-font-color);
text-overflow: ellipsis;
text-wrap: nowrap;
overflow: hidden;
flex: 1;
line-height: 1.6em;
border: min(0.2vh, 0.1vw) solid black;
border-right: none;
user-select: none
user-select: none;
border-right: black solid max(3px, min(0.4vh, 0.2vw));
padding-left: 4px;
}
#chat-room-reply-indicator:not(.hidden) {
background-color: var(--base-color, #eee);
position: absolute;
transform: translateY(-105%);
width: 100%;
#chat-room-reply-indicator {
background-color: #eee;
display: flex;
height: 1.6em;
}
#chat-room-reply-indicator-close {
aspect-ratio: 1;
}
#chat-room-reply-indicator-close::before {
content: "❌";
}
#InputChat {
grid-area: chat-input;
min-height: var(--button-size);
@ -132,15 +136,7 @@
border: unset;
outline: unset;
}
#chat-room-reply-indicator-close::before {
content: "❌";
}
#chat-room-reply-indicator-close {
background-color: var(--base-color, #eee);
border: min(0.2vh, 0.1vw) solid black;
cursor: pointer;
aspect-ratio: 1;
}
#InputChat:focus {
scrollbar-width: auto;
}
@ -317,8 +313,10 @@
--base-font-color: #eee;
}
#TextAreaChatLog[data-colortheme="dark"]~#chat-room-bot,
#TextAreaChatLog[data-colortheme="dark2"]~#chat-room-bot {
#TextAreaChatLog[data-colortheme="dark"] ~ #chat-room-bot,
#TextAreaChatLog[data-colortheme="dark2"] ~ #chat-room-bot,
#TextAreaChatLog[data-colortheme="dark"] ~ #chat-room-reply-indicator,
#TextAreaChatLog[data-colortheme="dark2"] ~ #chat-room-reply-indicator {
--button-color: #eee;
--base-color: #111;
--base-font-color: #eee;
@ -329,7 +327,7 @@
#TextAreaChatLog[data-colortheme="dark"]~#chat-room-buttons-div,
#TextAreaChatLog[data-colortheme="dark2"]~#chat-room-buttons-div {
border-left: min(0.2vh, 0.1vw) inset rgba(0, 0, 0, 0.25);
}

View file

@ -92,6 +92,12 @@ var ChatRoomChatLengthLabelRect = /** @type {never} */([1764, 970, 120, 82]);
*/
var ChatRoomDivRect = [1005, 60, 988, 940];
/**
* The last approximate height (_i.e._ {@link HTMLElement.clientHeight} as opposed to {@link DOMRect.height}) of the `InputChat` element.
* @type {number}
*/
let ChatRoomDivInputPrevHeight = NaN;
var ChatRoomChatHidden = false;
/**
* The chatroom characters that were drawn in the last frame.
@ -1153,23 +1159,23 @@ function ChatRoomCreateElement() {
attributes: { id: "TextAreaChatLog", role: "log" },
classList: ["scroll-box"],
},
{
tag: "div",
attributes: {id: "chat-room-reply-indicator"},
classList: ["hidden"],
children: [
{ tag: "span", attributes: { id: "chat-room-reply-indicator-text" }, children: [TextGet("ChatRoomReply")] },
ElementButton.Create(
"chat-room-reply-indicator-close",
ChatRoomMessageReplyStop,
{ noStyling: true },
),
],
},
{
tag: "div",
attributes: { id: "chat-room-bot" },
children: [
{
tag: "div",
attributes: {id: "chat-room-reply-indicator"},
classList: ["hidden"],
children: [
{ tag: "span", attributes: { id: "chat-room-reply-indicator-text" }, children: [TextGet("ChatRoomReply")] },
ElementButton.Create(
"chat-room-reply-indicator-close",
ChatRoomMessageReplyStop,
{ noStyling: true },
),
],
},
{
tag: "textarea",
attributes: {
@ -1181,7 +1187,8 @@ function ChatRoomCreateElement() {
},
eventListeners: {
input: ChatRoomChatInputChangeHandler,
keyup: (key) => {ChatRoomStatusUpdateTalk(key); ChatRoomStopReplyOnEscape(key);},
keyup: ChatRoomStatusUpdateTalk,
keydown: ChatRoomStopReplyOnEscape,
},
},
{
@ -1358,6 +1365,7 @@ function ChatRoomClearAllElements() {
} else {
ElementRemove("chat-room-div");
window.removeEventListener("resize", ChatRoomResizeManager.ChatRoomResizeEvent);
ChatRoomDivInputPrevHeight = NaN;
}
}
@ -1807,7 +1815,7 @@ function ChatRoomResize(load) {
const chatInput = document.getElementById("InputChat");
if (chatInput) {
ElementPositionFix("chat-room-div", ChatRoomFontSize, ...ChatRoomDivRect);
chatInput.dispatchEvent(new Event("input"));
chatInput.dispatchEvent(new InputEvent("input"));
}
}
@ -2069,9 +2077,7 @@ function ChatRoomStatusUpdateTalk(Key) {
*/
function ChatRoomChatInputChangeHandler(event) {
const label = document.getElementById("InputChatLength");
const chatLog = document.getElementById("TextAreaChatLog");
const parent = document.getElementById("chat-room-div");
if (!label || !chatLog || !parent) return;
if (!label) return;
const length = this.value.length;
if (length <= ServerChatMessageMaxLength - 100) {
@ -2088,24 +2094,45 @@ function ChatRoomChatInputChangeHandler(event) {
this.style.height = "100%";
this.style.height = `${this.scrollHeight}px`;
// Check if the text log + input completely fill the parent element (within a gap-accounting error marigin)
// and then resize the text log + input accordingly
const chatLogHeight = chatLog.getBoundingClientRect().height;
const parentHeight = parent.getBoundingClientRect().height;
const inputHeight = this.getBoundingClientRect().height;
const ratio = (inputHeight + chatLogHeight) / parentHeight;
const needsUpdate = ratio > 1.005 || ratio < 0.995;
if (needsUpdate) {
const percentage = 100 * (inputHeight / parentHeight);
if (percentage >= 9 && percentage <= 35) {
const isScrolledToEnd = ElementIsScrolledToEnd("TextAreaChatLog");
const inputHeight = this.clientHeight;
if (inputHeight === ChatRoomDivInputPrevHeight) {
return;
}
ChatRoomInputResize(this);
}
// At ~34% the maximum resize height has been reached and there is no point in trying to expand it any further
parent.style.gridTemplateRows = `${100 - 0.4 - percentage}% ${percentage}%`;
if (isScrolledToEnd) {
ElementScrollToEnd("TextAreaChatLog");
}
/**
* Manually adjust the size of `TextAreaChatLog` based on the (auto-expanded) `chatInput` height.
*
* Why manually? Because doing it automatically can get veeeery slow as the chat log grows in size.
* @param {HTMLElement} chatInput
*/
function ChatRoomInputResize(chatInput) {
const chatLog = document.getElementById("TextAreaChatLog");
const parent = document.getElementById("chat-room-div");
if (!chatLog || !parent) {
return;
}
ChatRoomDivInputPrevHeight = chatInput.clientHeight;
const isScrolledToEnd = ElementIsScrolledToEnd("TextAreaChatLog");
let height = parent.getBoundingClientRect().height;
let nGaps = 0;
for (const i of parent.children) {
if (!ElementCheckVisibility(i, { checkVisibilityCSS: false })) {
continue;
}
nGaps++;
if (i !== chatLog) {
height -= i.getBoundingClientRect().height;
}
}
const gap = globalThis.getComputedStyle(chatLog).marginBottom;
chatLog.style.height = `calc(${Math.max(0, height)}px - (${nGaps} * ${gap}))`;
if (isScrolledToEnd) {
ElementScrollToEnd("TextAreaChatLog");
}
}
@ -3083,7 +3110,7 @@ function ChatRoomSendChat() {
ChatRoomSendEmote(msg);
}
inputChat.value = "";
inputChat.dispatchEvent(new Event("input"));
inputChat.dispatchEvent(new InputEvent("input"));
return;
}
@ -3111,7 +3138,7 @@ function ChatRoomSendChat() {
if (clearChat) {
inputChat.value = "";
inputChat.dispatchEvent(new Event("input"));
inputChat.dispatchEvent(new InputEvent("input"));
}
}
@ -4393,10 +4420,12 @@ function ChatRoomMessageGetType(msgId) {
* Closes the reply.
*/
function ChatRoomMessageReplyStop() {
const chatInput = /** @type {null | HTMLTextAreaElement} */(document.getElementById("InputChat"));
const replyIndicator = document.getElementById("chat-room-reply-indicator");
chatInput.removeAttribute("reply-id");
replyIndicator.classList.add("hidden");
const chatInput = document.getElementById("InputChat");
document.getElementById("chat-room-reply-indicator")?.classList.add("hidden");
if (chatInput) {
chatInput.removeAttribute("reply-id");
ChatRoomInputResize(chatInput);
}
}
/**
@ -4405,8 +4434,12 @@ function ChatRoomMessageReplyStop() {
*/
function ChatRoomMessageSetReply(msgId) {
const chatInput = /** @type {null | HTMLTextAreaElement} */(document.getElementById("InputChat"));
chatInput.setAttribute("reply-id", msgId);
const replyMessage = ChatRoomMessageGetById(msgId);
if (!chatInput || !replyMessage) {
return;
}
chatInput.setAttribute("reply-id", msgId);
const type = ChatRoomMessageGetType(msgId);
const isWhisper = type === "Whisper";
if (isWhisper) {
@ -4418,6 +4451,7 @@ function ChatRoomMessageSetReply(msgId) {
const replyName = ChatRoomMessageGetReplyName(msgId, isWhisper);
replyIndicatorText.textContent = `${TextGet("ChatRoomReply")}: ${replyName && `${replyName}` || "a message"}`;
replyIndicator.classList.remove("hidden");
ChatRoomInputResize(chatInput);
chatInput.focus();
}

View file

@ -17,6 +17,7 @@ function ElementValue(ID, Value) {
return e.value.trim();
e.value = Value;
e.dispatchEvent(new InputEvent("input"));
return "";
}