import { html, LitElement } from "lit";
import { property, state } from "lit/decorators.js";
import type { LoomPairingQuestion } from "../lib/loom-pairing-login.ts";
import {
activateModalDialog,
pairingDialogStyles,
TAP_THROUGH_GUARD_MS,
} from "./pairing-dialog.ts";
/**
* The dialog a `#pair=` link raises: whether to sign in as the identity a Loom
* holds, or why the link did not sign anyone in.
*
* For a Loom on this computer the question is asked before the code is
* redeemed, so it names the Loom but not the incoming DID, which is not known
* until then. For a Loom elsewhere it is asked after, and names both. It
* dispatches `loom-pairing-result` once, with `detail.accepted`; a failure
* report always answers `false`.
*/
export class XLoomPairingView extends LitElement {
static override styles = pairingDialogStyles;
/** What to ask the person. */
@property({ attribute: false })
accessor question: LoomPairingQuestion | null = null;
/** Set instead of `question` to report why pairing failed. */
@property({ attribute: false })
accessor failure: string | null = null;
@state()
private accessor guarded = true;
#answered = false;
// `setTimeout` is typed as Node's `Timeout` under this config, not `number`.
#guardTimer: ReturnType | undefined;
/** The answer step of this dialog, which a test drives directly. */
get accessForTestingOnly(): { finish(accepted: boolean): void } {
return { finish: (accepted) => this.#finish(accepted) };
}
override firstUpdated() {
// Before anything that could throw, so the accept button cannot stay
// disabled for good.
this.#guardTimer = setTimeout(() => {
this.guarded = false;
}, TAP_THROUGH_GUARD_MS);
activateModalDialog(
this.renderRoot.querySelector("dialog"),
() => this.#finish(false),
);
(this.renderRoot.querySelector("[data-autofocus]") as HTMLElement | null)
?.focus?.();
}
override disconnectedCallback() {
clearTimeout(this.#guardTimer);
super.disconnectedCallback();
}
/** Answers the dialog once, dispatching the result the host listens for. */
#finish(accepted: boolean) {
if (this.#answered) return;
if (accepted && this.guarded) return;
this.#answered = true;
this.dispatchEvent(
new CustomEvent("loom-pairing-result", { detail: { accepted } }),
);
}
override render() {
if (this.failure !== null) {
return html`
`;
}
if (this.question === null) return html``;
const { loomUrl, currentDid, incomingDid } = this.question;
const replacing = currentDid !== null;
return html`
`;
}
}
globalThis.customElements.define("x-loom-pairing-view", XLoomPairingView);
declare global {
interface HTMLElementTagNameMap {
"x-loom-pairing-view": XLoomPairingView;
}
}