Mercurial
view mrjunejune/src/public/mjj-composer.js @ 273:e02e2036ef84 default tip
add Layer 2 JRPG component system
Add reusable content and window modals, an isolated component sandbox, shared cyberpunk scroll areas, production-safe cache freshness, and server-rendered JRPG panel state.
Co-authored-by: Copilot <[email protected]>
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Sat, 08 Aug 2026 02:08:08 -0700 |
| parents | 41a49c29a28f |
| children |
line wrap: on
line source
/** * Layer 2 composer composite. * * Dependencies flow downward only: Zenbu Layer 1 primitives and Layer 0 * themes/i18n/API mapping. Application routes and state remain outside. */ /** * 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); }