feat: release design system engine, studio and component contracts

This commit is contained in:
Clintchiz committed 2026-10-19 13:09:03 +05:30
1 parent ff1065fb27
commit 3943b946d6
693 files changed
+175546 -3894

No files matched your search

+45 -5
View File
@@ -415,6 +415,10 @@
const challengeInput = form?.querySelector("[name=challengeId]");
const radios = Array.from(form?.querySelectorAll("input[type=radio][name=method]") || []);
const status = form?.querySelector("[data-auth-mfa-status]");
const resendGroup = form?.querySelector("[data-auth-mfa-resend-group]");
const resendButton = form?.querySelector("button[data-auth-mfa-resend]");
const resendDeadlines = new Map();
let resendTimer;
const saved = readMfaState();
const allowed = Array.isArray(saved?.requires?.mfa) ? saved.requires.mfa : [];
const challenges = new Map();
@@ -457,6 +461,19 @@
};
const method = () => radios.find((radio) => radio.checked && !radio.disabled)?.value;
const isOtp = (value) => ["email-otp", "sms-otp"].includes(value);
const updateResend = () => {
window.clearTimeout(resendTimer);
const selected = method();
if (resendGroup) resendGroup.hidden = !isOtp(selected) || expired;
if (!resendButton || !isOtp(selected) || expired || disposed) return;
const seconds = Math.max(
0,
Math.ceil(((resendDeadlines.get(selected) || 0) - Date.now()) / 1000),
);
resendButton.disabled = pending.has(selected) || seconds > 0;
resendButton.textContent = seconds > 0 ? "Resend code in " + seconds + "s" : "Resend code";
if (seconds > 0) resendTimer = window.setTimeout(updateResend, 1000);
};
for (const radio of radios) {
radio.disabled = allowed.length > 0 && !allowed.includes(radio.value);
const label = radio.closest("label");
@@ -468,8 +485,10 @@
for (const radio of radios) radio.checked = radio === initial;
restoreToken();
const sendCode = async (selected) => {
const sendCode = async (selected, resend = false) => {
if (disposed || expired || !isOtp(selected)) return;
if (resend && (pending.has(selected) || (resendDeadlines.get(selected) || 0) > Date.now()))
return;
restoreToken();
if (expired) return;
if (!tokenInput?.value) {
@@ -481,12 +500,13 @@
const previous = challenges.get(selected);
if (previous?.expiresAt && Number(previous.expiresAt) <= Date.now())
challenges.delete(selected);
if (!pending.has(selected) && !challenges.has(selected)) {
if (!pending.has(selected) && (resend || !challenges.has(selected))) {
pending.set(
selected,
json(element.dataset.mfaOtpAction || "/api/auth/mfa/otp", {
mfaToken: tokenInput.value,
method: selected,
resend,
}),
);
}
@@ -495,16 +515,27 @@
setMessage(status, "Sending a one-time code…");
}
try {
const result = challenges.get(selected) || (await pending.get(selected));
const result = (!resend && challenges.get(selected)) || (await pending.get(selected));
if (disposed || expired) return;
challenges.set(selected, result);
if (!resendDeadlines.has(selected) || resend)
resendDeadlines.set(selected, Date.now() + (Number(result.retryAfter) || 60) * 1000);
if (method() !== selected) return;
if (challengeInput) challengeInput.value = String(result.id || "");
if (resend) {
form?.querySelectorAll("[data-auth-mfa-pin] input, [name=code]").forEach((input) => {
input.value = "";
input.dispatchEvent(new window.Event("input", { bubbles: true }));
});
form?.querySelectorAll("[data-error]").forEach((error) => {
error.textContent = "";
});
}
setMessage(
status,
selected === "email-otp"
? "A code has been sent to your email."
: "A code has been sent to your mobile number.",
? "An email code has been requested. Check your Inbox and Spam folder."
: "An SMS code has been requested. It may take a moment to arrive.",
);
} catch (error) {
if (!disposed && !expired && method() === selected)
@@ -516,6 +547,7 @@
} finally {
pending.delete(selected);
if (!disposed && !expired && method() === selected) setFormBusy(form, false);
updateResend();
}
};
const updateMethod = () => {
@@ -528,6 +560,7 @@
const error = form?.querySelector("[data-error=code]");
if (error) error.textContent = "";
setMessage(status, "");
updateResend();
for (const [selector, active] of [
["[data-auth-mfa-pin]", selected !== "recovery-code"],
["[data-auth-mfa-recovery]", selected === "recovery-code"],
@@ -546,6 +579,10 @@
const onMethodChange = (event) => {
if (radios.includes(event.target)) updateMethod();
};
const onResend = (event) => {
event.preventDefault();
void sendCode(method(), true);
};
const onSubmit = (event) => {
restoreToken();
const code = form?.querySelector("[name=code]");
@@ -573,11 +610,14 @@
// Capture native change before the shared Radio emits its component output.
form?.addEventListener("change", onMethodChange, true);
form?.addEventListener("submit", onSubmit, true);
resendButton?.addEventListener("click", onResend);
element.addEventListener("wrn:success", onSuccess);
element.addEventListener("wrn:error", onError);
flowCleanups.set(element, () => {
disposed = true;
window.clearTimeout(expiryTimer);
window.clearTimeout(resendTimer);
resendButton?.removeEventListener("click", onResend);
form?.removeEventListener("change", onMethodChange, true);
form?.removeEventListener("submit", onSubmit, true);
element.removeEventListener("wrn:success", onSuccess);