Mercurial
diff mrjunejune/src/public/mjj-composer.js @ 272:41a49c29a28f
polish JRPG conversation experience
Integrate desktop conversations into the utility panel, simplify the mobile frame, add modal destinations and a reusable Zenbu composer lab, and preserve explicit conversation resume behavior.
Co-authored-by: Copilot <[email protected]>
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Fri, 07 Aug 2026 16:05:29 -0700 |
| parents | |
| children | e02e2036ef84 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mrjunejune/src/public/mjj-composer.js Fri Aug 07 16:05:29 2026 -0700 @@ -0,0 +1,80 @@ +/** + * Coordinates a native composer form built from Zenbu controls. + * + * @extends {HTMLElement} + */ +export class MjjComposer extends HTMLElement { + connectedCallback() { + this._form = this.querySelector(":scope > form"); + this._textarea = this._form?.querySelector("textarea"); + this._submit = this._form?.querySelector('button[type="submit"]'); + this._cancel = this._form?.querySelector("[data-composer-cancel]"); + this._cancelOwner = this._form?.querySelector("[data-composer-cancel-owner]"); + this._onSubmit = event => { + event.preventDefault(); + const message = this._textarea?.value.trim(); + if (!message || this._submit?.disabled) return; + this.dispatchEvent(new CustomEvent("mjj-composer-submit", { + bubbles: true, + detail: { message }, + })); + this._form.reset(); + }; + this._onKeydown = event => { + if (event.key !== "Enter" || event.shiftKey || event.isComposing) return; + event.preventDefault(); + this._form?.requestSubmit(); + }; + this._onCancel = () => { + this.dispatchEvent(new CustomEvent("mjj-composer-cancel", { + bubbles: true, + })); + }; + this._form?.addEventListener("submit", this._onSubmit); + this._textarea?.addEventListener("keydown", this._onKeydown); + this._cancel?.addEventListener("click", this._onCancel); + } + + disconnectedCallback() { + this._form?.removeEventListener("submit", this._onSubmit); + this._textarea?.removeEventListener("keydown", this._onKeydown); + this._cancel?.removeEventListener("click", this._onCancel); + } + + /** + * Sets the active request state. + * + * @param {boolean} busy + */ + setBusy(busy) { + if (this._textarea) this._textarea.disabled = busy; + if (this._submit) { + this._submit.disabled = busy; + this._submit.setAttribute("aria-busy", String(busy)); + } + if (this._cancelOwner) this._cancelOwner.hidden = !busy; + } + + /** + * Shows or hides the cancellation action. + * + * @param {boolean} cancellable + */ + setCancellable(cancellable) { + if (this._cancelOwner) this._cancelOwner.hidden = !cancellable; + } + + /** + * Enables or disables composer input. + * + * @param {boolean} disabled + */ + setDisabled(disabled) { + if (this._textarea) this._textarea.disabled = disabled; + if (this._submit) this._submit.disabled = disabled; + } +} + +if (!customElements.get("mjj-composer")) { + customElements.define("mjj-composer", MjjComposer); +}