@labyrinthe/otp-widget 0.0.0-stage → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,70 @@
1
+ "use strict";var LabyrintheOtp=(()=>{var g=Object.defineProperty;var D=Object.getOwnPropertyDescriptor;var H=Object.getOwnPropertyNames;var I=Object.prototype.hasOwnProperty;var B=(o,i)=>{for(var e in i)g(o,e,{get:i[e],enumerable:!0})},N=(o,i,e,t)=>{if(i&&typeof i=="object"||typeof i=="function")for(let n of H(i))!I.call(o,n)&&n!==e&&g(o,n,{get:()=>i[n],enumerable:!(t=D(i,n))||t.enumerable});return o};var P=o=>N(g({},"__esModule",{value:!0}),o);var $={};B($,{DEFAULT_COUNTRY_CODE:()=>u,EVENT_ERROR:()=>x,EVENT_SENT:()=>v,EVENT_VERIFIED:()=>y,LOCALES:()=>T,TAG_NAME:()=>m,VERSION:()=>R,defineLabyrintheOtp:()=>W,maskPhone:()=>O,normalizePhone:()=>h,resolveTexts:()=>c});var E={title:"V\xE9rification du num\xE9ro",phoneLabel:"Num\xE9ro de t\xE9l\xE9phone",countryCodeLabel:"Indicatif pays",phonePlaceholder:"81 234 5678",phoneHint:"Vous allez recevoir un code par SMS.",send:"Recevoir le code",sending:"Envoi\u2026",codeLabel:"Code re\xE7u par SMS",codeSentTo:"Code envoy\xE9 au {phone}.",cellLabel:"Caract\xE8re {index} sur {total}",verify:"V\xE9rifier",verifying:"V\xE9rification\u2026",changePhone:"Modifier le num\xE9ro",resend:"Renvoyer le code",resendIn:"Renvoyer le code dans {time}",resendAvailable:"Vous pouvez demander un nouveau code.",attemptsLeftOne:"Il vous reste 1 tentative.",attemptsLeftOther:"Il vous reste {count} tentatives.",success:"Num\xE9ro v\xE9rifi\xE9.",testMode:"Mode test : le code est {code}",retryIn:"R\xE9essayez dans {time}.",errors:{invalid_phone:"Ce num\xE9ro de t\xE9l\xE9phone n\u2019est pas valide.",invalid_code:"Code incorrect.",expired:"Ce code a expir\xE9. Demandez-en un nouveau.",already_used:"Ce code a d\xE9j\xE0 \xE9t\xE9 utilis\xE9. Demandez-en un nouveau.",max_attempts_reached:"Trop d\u2019essais incorrects. Demandez un nouveau code.",resend_too_soon:"Patientez {time} avant de demander un nouveau code.",rate_limited:"Trop de demandes. R\xE9essayez dans quelques minutes.",no_pending_verification:"Votre demande a expir\xE9. Demandez un nouveau code.",forbidden:"Demande refus\xE9e.",invalid_request:"Demande invalide.",service_unavailable:"Le service de v\xE9rification est momentan\xE9ment indisponible.",account_suspended:"La v\xE9rification par SMS n\u2019est pas disponible sur ce site pour le moment.",send_failed:"L\u2019envoi du code n\u2019a pas pu \xEAtre confirm\xE9. Si vous ne le recevez pas d\u2019ici une minute, demandez un nouveau code.",server_error:"Une erreur est survenue. R\xE9essayez.",network_error:"Connexion impossible. V\xE9rifiez votre r\xE9seau et r\xE9essayez.",incomplete_code:"Saisissez les {count} caract\xE8res du code."}},C={title:"Phone verification",phoneLabel:"Phone number",countryCodeLabel:"Country code",phonePlaceholder:"81 234 5678",phoneHint:"We will send you a code by SMS.",send:"Send code",sending:"Sending\u2026",codeLabel:"Code received by SMS",codeSentTo:"Code sent to {phone}.",cellLabel:"Character {index} of {total}",verify:"Verify",verifying:"Verifying\u2026",changePhone:"Change number",resend:"Resend code",resendIn:"Resend code in {time}",resendAvailable:"You can request a new code.",attemptsLeftOne:"1 attempt left.",attemptsLeftOther:"{count} attempts left.",success:"Phone number verified.",testMode:"Test mode: the code is {code}",retryIn:"Try again in {time}.",errors:{invalid_phone:"This phone number is not valid.",invalid_code:"Wrong code.",expired:"This code has expired. Request a new one.",already_used:"This code was already used. Request a new one.",max_attempts_reached:"Too many wrong attempts. Request a new code.",resend_too_soon:"Please wait {time} before requesting a new code.",rate_limited:"Too many requests. Try again in a few minutes.",no_pending_verification:"Your request has expired. Request a new code.",forbidden:"Request refused.",invalid_request:"Invalid request.",service_unavailable:"The verification service is temporarily unavailable.",account_suspended:"SMS verification is not available on this site at the moment.",send_failed:"We could not confirm that the code was sent. If it does not arrive within a minute, ask for a new code.",server_error:"Something went wrong. Please try again.",network_error:"Cannot connect. Check your network and try again.",incomplete_code:"Enter the {count} characters of the code."}},T={fr:E,en:C};function c(o,i){let e=o?.toLowerCase().startsWith("en")===!0?C:E;if(i==null)return e;let{errors:t,...n}=i;return{...e,..._(n),errors:{...e.errors,..._(t??{})}}}function l(o,i){return o.replace(/\{(\w+)\}/g,(e,t)=>{let n=i[t];return n===void 0?e:String(n)})}function b(o){let i=Math.max(0,Math.ceil(o)),e=t=>String(t).padStart(2,"0");return i>=3600?`${String(Math.floor(i/3600))}:${e(Math.floor(i/60)%60)}:${e(i%60)}`:`${String(Math.floor(i/60))}:${e(i%60)}`}function _(o){return Object.fromEntries(Object.entries(o).filter(([,i])=>typeof i=="string"))}var u="243";function h(o,i="243"){if(!f(i))return null;let e=o.trim();if(e==="")return null;let t=e.startsWith("+")||e.startsWith("00"),n=e.replace(/\D/g,"");return t?n=e.startsWith("00")?n.slice(2):n:n.startsWith(i)&&n.length>10||n.length>=11&&!n.startsWith("0")||(n=i+n.replace(/^0+/,"")),n.length>=8&&n.length<=15?n:null}function f(o){return/^[1-9]\d{0,2}$/.test(o)}function O(o){let i=o.replace(/\D/g,"");return i.length<=6?"\u2022".repeat(i.length):`+${i.slice(0,3)} ${i.slice(3,5)}${"\u2022".repeat(i.length-7)}${i.slice(-2)}`}var L=["invalid_code","expired","already_used","max_attempts_reached"];var S=`
2
+ :host{--lab-otp-primary:#008374;--lab-otp-primary-contrast:#fff;--lab-otp-accent:#F85A40;--lab-otp-error:#c2381f;
3
+ --lab-otp-success:#008374;--lab-otp-bg:#fff;--lab-otp-fg:#19201e;--lab-otp-muted:#5b6763;--lab-otp-border:#d5dcd9;
4
+ --lab-otp-field:#f8faf9;--lab-otp-radius:10px;--lab-otp-font:Inter,ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
5
+ --lab-otp-cell-size:44px;--lab-otp-gap:8px;
6
+ display:block;max-width:420px;color:var(--lab-otp-fg);font:14px/1.45 var(--lab-otp-font);color-scheme:light}
7
+ :host([hidden]){display:none}
8
+ :host([theme=dark]){--lab-otp-primary:#2bb3a3;--lab-otp-primary-contrast:#06201c;--lab-otp-accent:#ff7a63;--lab-otp-error:#ff8a75;
9
+ --lab-otp-success:#2bb3a3;--lab-otp-bg:#19201e;--lab-otp-fg:#eef2f0;--lab-otp-muted:#a3b0ab;--lab-otp-border:#38433f;--lab-otp-field:#222b28;color-scheme:dark}
10
+ @media (prefers-color-scheme:dark){:host(:not([theme=light])){--lab-otp-primary:#2bb3a3;--lab-otp-primary-contrast:#06201c;
11
+ --lab-otp-accent:#ff7a63;--lab-otp-error:#ff8a75;--lab-otp-success:#2bb3a3;--lab-otp-bg:#19201e;--lab-otp-fg:#eef2f0;
12
+ --lab-otp-muted:#a3b0ab;--lab-otp-border:#38433f;--lab-otp-field:#222b28;color-scheme:dark}}
13
+ *{box-sizing:border-box}
14
+ .root{background:var(--lab-otp-bg);border:1px solid var(--lab-otp-border);border-radius:var(--lab-otp-radius);padding:20px}
15
+ h2{margin:0 0 12px;font-size:16px;font-weight:600}
16
+ form{display:flex;flex-direction:column;gap:10px;margin:0}
17
+ [hidden]{display:none!important}
18
+ label,.label{font-weight:500}
19
+ .hint,.muted{margin:0;color:var(--lab-otp-muted);font-size:13px}
20
+ .phone{display:flex;gap:var(--lab-otp-gap)}
21
+ input{font:inherit;color:inherit;background:var(--lab-otp-field);border:1px solid var(--lab-otp-border);
22
+ border-radius:calc(var(--lab-otp-radius) - 2px);padding:10px 12px;min-width:0;width:100%}
23
+ input:focus-visible,button:focus-visible{outline:2px solid var(--lab-otp-primary);outline-offset:2px}
24
+ input[aria-invalid=true]{border-color:var(--lab-otp-error)}
25
+ .cc{flex:0 0 5.5em;display:flex;align-items:center;gap:2px;background:var(--lab-otp-field);border:1px solid var(--lab-otp-border);
26
+ border-radius:calc(var(--lab-otp-radius) - 2px);padding-left:10px}
27
+ .cc input{border:0;padding-left:0;background:transparent}
28
+ .cc input:focus-visible{outline-offset:-2px}
29
+ .cells{display:flex;gap:var(--lab-otp-gap);flex-wrap:nowrap}
30
+ .cells input{width:var(--lab-otp-cell-size);height:calc(var(--lab-otp-cell-size) + 8px);padding:0;text-align:center;
31
+ font-size:20px;font-weight:600;font-variant-numeric:tabular-nums;flex:0 1 var(--lab-otp-cell-size)}
32
+ button{font:inherit;font-weight:600;border-radius:calc(var(--lab-otp-radius) - 2px);cursor:pointer;padding:10px 14px;
33
+ border:1px solid transparent}
34
+ button[disabled]{cursor:not-allowed;opacity:.6}
35
+ .primary{background:var(--lab-otp-primary);color:var(--lab-otp-primary-contrast)}
36
+ .primary:hover:not([disabled]){filter:brightness(1.08)}
37
+ .link{background:none;border:0;padding:4px 0;color:var(--lab-otp-primary);text-decoration:underline;text-underline-offset:2px;
38
+ align-self:flex-start;font-weight:500}
39
+ .row{display:flex;flex-wrap:wrap;justify-content:space-between;gap:4px 12px;align-items:center}
40
+ .error{margin:0;color:var(--lab-otp-error);font-weight:500}
41
+ .error:empty{display:none}
42
+ .test{margin:0;padding:6px 10px;border-radius:6px;border:1px dashed var(--lab-otp-accent);font-size:13px}
43
+ .success{display:flex;gap:10px;align-items:center;color:var(--lab-otp-success);font-weight:600}
44
+ .success svg{flex:none}
45
+ .sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
46
+ @media (max-width:360px){.root{padding:14px}:host{--lab-otp-cell-size:36px;--lab-otp-gap:6px}}
47
+ @media (prefers-reduced-motion:no-preference){button,input{transition:border-color .15s,filter .15s}}
48
+ `;var m="labyrinthe-otp",v="labyrinthe-otp:sent",y="labyrinthe-otp:verified",x="labyrinthe-otp:error",F=["invalid_phone","resend_too_soon","rate_limited","no_pending_verification","forbidden","invalid_request","service_unavailable","account_suspended","send_failed","server_error"],w=["expired","already_used","max_attempts_reached","no_pending_verification"],M="\u200B",z='<svg width="24" height="24" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="11" fill="none" stroke="currentColor" stroke-width="2"/><path d="M7 12.5l3.2 3.2L17 9" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>',V=`<style>${S}</style>
49
+ <div class="root" part="root">
50
+ <h2 id="t" part="title"></h2>
51
+ <form id="pf" part="phone-form" novalidate>
52
+ <label for="pn" id="pl"></label>
53
+ <div class="phone"><span class="cc" part="country-code"><span aria-hidden="true">+</span><input id="cc" inputmode="numeric" autocomplete="tel-country-code" maxlength="3" spellcheck="false"></span>
54
+ <input id="pn" type="tel" inputmode="tel" autocomplete="tel-national" aria-describedby="ph er" part="phone-input"></div>
55
+ <p class="hint" id="ph"></p>
56
+ <button class="primary" type="submit" id="sb" part="button send-button"></button>
57
+ </form>
58
+ <form id="cf" part="code-form" novalidate hidden>
59
+ <div class="row"><p class="muted" id="st"></p><button type="button" class="link" id="chg" part="link change-button"></button></div>
60
+ <span class="label" id="cl"></span>
61
+ <div class="cells" role="group" id="cells" aria-labelledby="cl" aria-describedby="at er" part="cells"></div>
62
+ <p class="muted" id="at"></p>
63
+ <p class="test" id="tc" hidden part="test-code"></p>
64
+ <button class="primary" type="submit" id="vb" part="button verify-button"></button>
65
+ <button type="button" class="link" id="rs" part="link resend-button"></button>
66
+ </form>
67
+ <div class="success" id="ok" hidden tabindex="-1" part="success">${z}<span id="okt"></span></div>
68
+ <p class="error" id="er" role="alert" part="error"></p>
69
+ <p class="sr" id="lv" aria-live="polite"></p>
70
+ </div>`;function A(){class o extends HTMLElement{static observedAttributes=["locale","country-code","phone","code-length","code-type","heading"];#C;#e;#T=null;#r=c("fr");#O={};#t="phone";#L="";#g="";#n=!1;#d=6;#u="numeric";#l=null;#s=!1;#S=0;#b;#w;#c="";#o=null;#v={};#M={};#h=null;constructor(){super(),this.#C=this.attachShadow({mode:"open",delegatesFocus:!0}),this.#C.innerHTML=V;let e=t=>this.#C.getElementById(t);this.#e={title:e("t"),phoneForm:e("pf"),phoneLabel:e("pl"),countryCode:e("cc"),phone:e("pn"),phoneHint:e("ph"),sendButton:e("sb"),codeForm:e("cf"),sentTo:e("st"),changeButton:e("chg"),codeLabel:e("cl"),cells:e("cells"),attempts:e("at"),testCode:e("tc"),verifyButton:e("vb"),resendButton:e("rs"),success:e("ok"),successText:e("okt"),error:e("er"),live:e("lv")},this.#e.phoneForm.addEventListener("submit",t=>{t.preventDefault(),this.#G()}),this.#e.codeForm.addEventListener("submit",t=>{t.preventDefault(),this.#N()}),this.#e.changeButton.addEventListener("click",()=>{this.#F()}),this.#e.resendButton.addEventListener("click",()=>{!this.#n&&this.#x()===0&&this.#B(this.#L)}),this.#e.phone.addEventListener("input",()=>{this.#e.phone.removeAttribute("aria-invalid")}),this.#e.cells.addEventListener("input",t=>{this.#Q(t)}),this.#e.cells.addEventListener("keydown",t=>{this.#X(t)}),this.#e.cells.addEventListener("paste",t=>{this.#ee(t)}),this.#e.cells.addEventListener("focusin",t=>{let n=t.target;n instanceof HTMLInputElement&&n.select()})}connectedCallback(){this.#e.countryCode.value===""&&(this.#e.countryCode.value=this.#j());let e=this.getAttribute("phone");e!==null&&this.#e.phone.value===""&&(this.#e.phone.value=e),this.#I(),this.#i(),this.#t==="code"&&this.#A()}disconnectedCallback(){this.#f(),this.#_()}attributeChangedCallback(e,t,n){t!==n&&(e==="country-code"&&n!==null&&f(n.replace(/^\+/,""))?this.#e.countryCode.value=n.replace(/^\+/,""):e==="phone"&&n!==null&&this.#t==="phone"?this.#e.phone.value=n:(e==="code-length"||e==="code-type")&&this.#t==="phone"&&this.#I(),this.#i())}get step(){return this.#t}get texts(){return this.#T}set texts(e){this.#T=e,this.#i()}get requestHeaders(){return{...this.#O}}set requestHeaders(e){this.#O={...e??{}}}reset(){this.#s=!1,this.#l=null,this.#V(),this.#F(),this.#e.phone.value=this.getAttribute("phone")??""}focus(e){(this.#t==="phone"?this.#e.phone:this.#t==="code"?this.#y()??this.#e.verifyButton:this.#e.success).focus(e)}async#G(){if(this.#n)return;let e=this.#e.countryCode.value.replace(/\D/g,"")||this.#j(),t=h(this.#e.phone.value,e);if(t===null){this.#e.phone.setAttribute("aria-invalid","true"),this.#p("invalid_phone",null),this.#e.phone.focus();return}await this.#B(t)}async#B(e){this.#E(!0),this.#m();let t=this.getAttribute("locale")?.toLowerCase().startsWith("en")===!0?"en":"fr";this.#h?.phone!==e&&(this.#h={phone:e,id:q()});let n=await this.#Y(this.getAttribute("send-url")??"/api/otp/send",{phone:`+${e}`,locale:t,send_id:this.#h.id});if(this.#E(!1),n===null)return;let r=n.body;if(n.status>=200&&n.status<300&&r?.sent===!0){this.#h=null,this.#K(e,r);return}let{code:s,retryAfterSeconds:a,maintenance:d}=k(n);s==="send_failed"&&(this.#h=null),s==="resend_too_soon"&&a!==void 0&&(this.#S=Date.now()+a*1e3,this.#A()),s==="invalid_phone"&&this.#t==="phone"&&this.#e.phone.setAttribute("aria-invalid","true"),this.#p(s,n.status,a,void 0,d)}#K(e,t){this.#L=e,this.#g=typeof t.phone_masked=="string"?t.phone_masked:`+${e}`;let n=t.code_length;typeof n=="number"&&Number.isInteger(n)&&n>=1&&n<=20&&(this.#d=n),(t.code_type==="numeric"||t.code_type==="alphanumeric")&&(this.#u=t.code_type);let r=typeof t.resend_after_seconds=="number"?Math.max(0,t.resend_after_seconds):60,s=typeof t.max_attempts=="number"?t.max_attempts:null;this.#S=Date.now()+r*1e3,this.#l=s,this.#s=!1,this.#c="";let a=typeof t.test_code=="string"&&t.test_code!==""?t.test_code:void 0;this.#e.testCode.hidden=a===void 0,this.#e.testCode.textContent=a===void 0?"":l(this.#r.testMode,{code:a}),this.#t="code",this.#J(),this.#W(),this.#i(),this.#A(),this.#te(),this.#D(l(this.#r.codeSentTo,{phone:this.#g})),this.#y()?.focus();let d={phone:e,phoneMasked:this.#g,expiresAt:new Date(typeof t.expires_at=="string"?t.expires_at:Date.now()),maxAttempts:s??0,resendAfterSeconds:r,...a===void 0?{}:{testCode:a}};this.#H(v,d)}async#N(){if(this.#n||this.#s)return;let e=this.#z();if(e.length<this.#d){this.#p("incomplete_code",null),this.#y()?.focus();return}this.#E(!0),this.#m();let t=await this.#Y(this.getAttribute("verify-url")??"/api/otp/verify",{code:e});if(this.#E(!1),t===null){this.#c="";return}let n=t.body;if(t.status>=200&&t.status<300&&n?.verified===!0){this.#Z(n.data);return}if(t.status>=200&&t.status<300&&n?.verified===!1){let d=L.includes(n.reason)?n.reason:"invalid_code";this.#l=typeof n.attempts_left=="number"?n.attempts_left:null,this.#P(d,t.status);return}let{code:r,retryAfterSeconds:s,maintenance:a}=k(t);if(w.includes(r)){this.#P(r,t.status);return}this.#c="",this.#p(r,t.status,s,void 0,a)}#P(e,t){this.#V(),this.#c="",w.includes(e)&&(this.#s=!0,this.#l=null),this.#i(),this.#p(e,t,void 0,this.#l),this.#s?(this.#e.resendButton.disabled?this.#e.changeButton:this.#e.resendButton).focus():this.#y()?.focus()}#Z(e){this.#t="success",this.#f(),this.#_(),this.#m(),this.#W(),this.#i(),this.#e.success.focus(),this.#D(this.#r.success);let t={phone:this.#L,data:e??null};this.#H(y,t)}#F(){this.#t="phone",this.#f(),this.#_(),this.#m(),this.#I(),this.#W(),this.#i(),this.#e.phone.focus()}#J(){let e=this.#e.cells;e.textContent="";for(let t=0;t<this.#d;t++){let n=document.createElement("input");n.type="text",n.inputMode=this.#u==="numeric"?"numeric":"text",n.autocomplete=t===0?"one-time-code":"off",n.spellcheck=!1,n.setAttribute("autocapitalize",this.#u==="numeric"?"off":"characters"),n.setAttribute("enterkeyhint","done"),n.setAttribute("part","cell"),n.dataset.index=String(t),e.append(n)}}#a(){return Array.from(this.#e.cells.querySelectorAll("input"))}#z(){return this.#a().map(e=>e.value).join("")}#y(){let e=this.#a();return e.find(t=>t.value==="")??e[e.length-1]}#V(){for(let e of this.#a())e.value=""}#q(e){return this.#u==="numeric"?e.replace(/\D/g,""):e.toUpperCase().replace(/[^A-Z0-9]/g,"")}#k(e,t){let n=this.#a(),r=this.#q(t),s=r.length>=n.length?0:e;for(let d=0;d<r.length&&s+d<n.length;d++){let p=n[s+d];p!==void 0&&(p.value=r.charAt(d))}(n.find((d,p)=>p>=s&&d.value==="")??n[Math.min(s+r.length,n.length-1)])?.focus(),this.#$()}#Q(e){let t=e.target;if(!(t instanceof HTMLInputElement))return;let n=Number(t.dataset.index),r=this.#q(t.value);if(r.length<=1){t.value=r,r!==""&&this.#a()[n+1]?.focus(),this.#$();return}t.value="",this.#k(n,r)}#X(e){let t=e.target;if(!(t instanceof HTMLInputElement))return;let n=this.#a(),r=Number(t.dataset.index),s;e.key==="Backspace"&&t.value===""&&r>0?(s=n[r-1],s!==void 0&&(s.value="")):e.key==="ArrowLeft"?s=n[r-1]:e.key==="ArrowRight"?s=n[r+1]:e.key==="Home"?s=n[0]:e.key==="End"&&(s=n[n.length-1]),s!==void 0&&(e.preventDefault(),s.focus())}#ee(e){let t=e.target,n=e.clipboardData?.getData("text")??"";!(t instanceof HTMLInputElement)||n===""||(e.preventDefault(),this.#k(Number(t.dataset.index),n))}#$(){(this.#o==="incomplete_code"||this.#o==="invalid_code")&&this.#m();let e=this.#z();e.length===this.#d&&!this.hasAttribute("no-auto-submit")&&e!==this.#c&&!this.#n&&!this.#s&&(this.#c=e,this.#N())}#x(){return Math.max(0,Math.ceil((this.#S-Date.now())/1e3))}#A(){this.#f(),this.#R(),this.#x()>0&&(this.#b=setInterval(()=>{this.#R(),this.#x()===0&&(this.#f(),this.#t==="code"&&this.#D(this.#r.resendAvailable))},1e3))}#f(){this.#b!==void 0&&(clearInterval(this.#b),this.#b=void 0)}#R(){let e=this.#x(),t=this.#e.resendButton;t.disabled=e>0||this.#n,t.textContent=e>0?l(this.#r.resendIn,{time:b(e)}):this.#r.resend,this.#t==="phone"&&(this.#e.sendButton.disabled=this.#n||this.#o==="resend_too_soon"&&e>0)}#te(){this.#_();let e=navigator.credentials;if(this.hasAttribute("no-web-otp")||!("OTPCredential"in window)||e===void 0)return;let t=new AbortController;this.#w=t,e.get({otp:{transport:["sms"]},signal:t.signal}).then(n=>{let r=n?.code;typeof r=="string"&&!t.signal.aborted&&this.#t==="code"&&this.#k(0,r)}).catch(()=>{})}#_(){this.#w?.abort(),this.#w=void 0}#W(){this.#e.phoneForm.hidden=this.#t!=="phone",this.#e.codeForm.hidden=this.#t!=="code",this.#e.success.hidden=this.#t!=="success"}#i(){let e=this.#r=c(this.getAttribute("locale"),this.#T),t=this.#e;t.title.textContent=this.getAttribute("heading")??e.title,t.title.hidden=this.getAttribute("heading")==="",t.phoneLabel.textContent=e.phoneLabel,t.countryCode.setAttribute("aria-label",e.countryCodeLabel),t.phone.placeholder=e.phonePlaceholder,t.phoneHint.textContent=e.phoneHint,t.sendButton.textContent=this.#n&&this.#t==="phone"?e.sending:e.send,t.sentTo.textContent=l(e.codeSentTo,{phone:this.#g}),t.changeButton.textContent=e.changePhone,t.codeLabel.textContent=e.codeLabel,t.verifyButton.textContent=this.#n&&this.#t==="code"?e.verifying:e.verify,t.successText.textContent=e.success;let n=this.#a();n.forEach((s,a)=>{s.setAttribute("aria-label",l(e.cellLabel,{index:a+1,total:n.length})),s.disabled=this.#s}),t.verifyButton.disabled=this.#n||this.#s;let r=this.#l;if(t.attempts.textContent=r===null||this.#o!=="invalid_code"?"":l(r===1?e.attemptsLeftOne:e.attemptsLeftOther,{count:r}),this.#o!==null){let s=this.#U(this.#o);t.error.textContent.replace(M,"")!==s&&(t.error.textContent=s)}this.#R()}#E(e){this.#n=e,(this.#t==="phone"?this.#e.phoneForm:this.#e.codeForm).setAttribute("aria-busy",String(e)),this.#e.sendButton.disabled=e,this.#i()}#U(e){let t=this.#r,n={count:this.#d,...this.#v};if(e!=="service_unavailable")return l(t.errors[e],n);let r=this.#v.time===void 0?"":` ${l(t.retryIn,n)}`;return(this.#M.adminMessage??l(t.errors[e],n))+r}#p(e,t,n,r,s={}){this.#o=e,this.#v=n===void 0?{}:{time:b(n)},this.#M=s;let a=this.#U(e);this.#e.error.textContent=this.#e.error.textContent===a?a+M:a,this.#i();let d={code:e,message:a,status:t,...n===void 0?{}:{retryAfterSeconds:n},...r===void 0?{}:{attemptsLeft:r},...s};this.#H(x,d)}#m(){this.#o=null,this.#v={},this.#M={},this.#e.error.textContent="",this.#e.attempts.textContent=""}#D(e){this.#e.live.textContent=e}#H(e,t){this.dispatchEvent(new CustomEvent(e,{detail:t,bubbles:!0,composed:!0}))}#j(){let e=(this.getAttribute("country-code")??"").replace(/^\+/,"");return f(e)?e:"243"}#I(){let e=Number(this.getAttribute("code-length")??"6");this.#d=Number.isInteger(e)&&e>=1&&e<=20?e:6,this.#u=this.getAttribute("code-type")==="alphanumeric"?"alphanumeric":"numeric"}async#Y(e,t){try{let n=await fetch(e,{method:"POST",headers:{...this.#O,"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(t),credentials:this.getAttribute("credentials")==="include"?"include":"same-origin"}),r=null;try{r=await n.json()}catch{r=null}return{status:n.status,body:r}}catch{return this.#p("network_error",null),null}}}return o}function q(){return Array.from(crypto.getRandomValues(new Uint8Array(16)),o=>o.toString(16).padStart(2,"0")).join("")}function k(o){let i=o.body?.error,e=i?.retry_after_seconds,t=typeof e=="number"&&e>=0?Math.ceil(e):void 0,n;typeof i?.code=="string"&&F.includes(i.code)?n=i.code:o.status===429?n="rate_limited":o.status===403?n="forbidden":o.status===503?n="service_unavailable":n="server_error";let r={};if(n==="service_unavailable"){typeof i?.admin_message=="string"&&i.admin_message.trim()!==""&&(r.adminMessage=i.admin_message.trim());let s=typeof i?.until=="string"?new Date(i.until):null;s!==null&&!Number.isNaN(s.getTime())&&(r.until=s)}return t===void 0?{code:n,maintenance:r}:{code:n,retryAfterSeconds:t,maintenance:r}}var R="1.2.0";function W(o=m){if(typeof customElements>"u"||typeof HTMLElement>"u")return;let i=customElements.get(o);if(i!==void 0)return i;let e=A();return customElements.define(o,e),e}W();return P($);})();
package/dist/react.cjs ADDED
@@ -0,0 +1,81 @@
1
+ 'use client';
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/react.tsx
22
+ var react_exports = {};
23
+ __export(react_exports, {
24
+ LabyrintheOtp: () => LabyrintheOtp
25
+ });
26
+ module.exports = __toCommonJS(react_exports);
27
+ var import_react = require("react");
28
+ var import_otp_widget = require("@labyrinthe/otp-widget");
29
+ var LabyrintheOtp = (0, import_react.forwardRef)(function LabyrintheOtp2(props, ref) {
30
+ const element = (0, import_react.useRef)(null);
31
+ const callbacks = (0, import_react.useRef)(props);
32
+ callbacks.current = props;
33
+ (0, import_react.useImperativeHandle)(ref, () => element.current, []);
34
+ (0, import_react.useEffect)(() => {
35
+ (0, import_otp_widget.defineLabyrintheOtp)();
36
+ const node = element.current;
37
+ if (node === null) {
38
+ return void 0;
39
+ }
40
+ const onSent = (event) => callbacks.current.onSent?.(event.detail);
41
+ const onVerified = (event) => callbacks.current.onVerified?.(event.detail);
42
+ const onError = (event) => callbacks.current.onError?.(event.detail);
43
+ node.addEventListener(import_otp_widget.EVENT_SENT, onSent);
44
+ node.addEventListener(import_otp_widget.EVENT_VERIFIED, onVerified);
45
+ node.addEventListener(import_otp_widget.EVENT_ERROR, onError);
46
+ return () => {
47
+ node.removeEventListener(import_otp_widget.EVENT_SENT, onSent);
48
+ node.removeEventListener(import_otp_widget.EVENT_VERIFIED, onVerified);
49
+ node.removeEventListener(import_otp_widget.EVENT_ERROR, onError);
50
+ };
51
+ }, []);
52
+ const { texts, requestHeaders } = props;
53
+ (0, import_react.useEffect)(() => {
54
+ if (element.current !== null) {
55
+ element.current.texts = texts ?? null;
56
+ }
57
+ }, [texts]);
58
+ (0, import_react.useEffect)(() => {
59
+ if (element.current !== null) {
60
+ element.current.requestHeaders = requestHeaders ?? {};
61
+ }
62
+ }, [requestHeaders]);
63
+ return (0, import_react.createElement)("labyrinthe-otp", {
64
+ ref: element,
65
+ id: props.id,
66
+ className: props.className,
67
+ style: props.style,
68
+ "send-url": props.sendUrl,
69
+ "verify-url": props.verifyUrl,
70
+ locale: props.locale,
71
+ "country-code": props.countryCode,
72
+ phone: props.phone,
73
+ "code-length": props.codeLength === void 0 ? void 0 : String(props.codeLength),
74
+ "code-type": props.codeType,
75
+ theme: props.theme,
76
+ heading: props.heading,
77
+ credentials: props.credentials,
78
+ "no-auto-submit": props.autoSubmit === false ? "" : void 0,
79
+ "no-web-otp": props.webOtp === false ? "" : void 0
80
+ });
81
+ });
@@ -0,0 +1,43 @@
1
+ import * as react from 'react';
2
+ import { CSSProperties } from 'react';
3
+ import { OtpWidgetLocale, OtpWidgetTextsOverride, OtpSentDetail, OtpVerifiedDetail, OtpErrorDetail, LabyrintheOtpElement } from '@labyrinthe/otp-widget';
4
+ export { LabyrintheOtpElement, OtpErrorDetail, OtpSentDetail, OtpVerifiedDetail, OtpWidgetLocale, OtpWidgetTextsOverride } from '@labyrinthe/otp-widget';
5
+
6
+ interface LabyrintheOtpProps {
7
+ /** Your endpoint that sends the code (default "/api/otp/send"). */
8
+ sendUrl?: string;
9
+ /** Your endpoint that verifies the code (default "/api/otp/verify"). */
10
+ verifyUrl?: string;
11
+ /** "fr" (default) or "en". */
12
+ locale?: OtpWidgetLocale;
13
+ /** Default country code, without "+" (default "243", DR Congo). */
14
+ countryCode?: string;
15
+ /** Pre-filled phone number. */
16
+ phone?: string;
17
+ /** Number of code boxes before the server says otherwise (default 6). */
18
+ codeLength?: number;
19
+ codeType?: 'numeric' | 'alphanumeric';
20
+ /** "light", "dark" or automatic (default). */
21
+ theme?: 'light' | 'dark';
22
+ /** Title shown above the form; "" hides it. */
23
+ heading?: string;
24
+ /** Text overrides, merged with the locale defaults. */
25
+ texts?: OtpWidgetTextsOverride;
26
+ /** Extra headers sent to your endpoints (e.g. a CSRF token). */
27
+ requestHeaders?: Record<string, string>;
28
+ /** "include" when your endpoints are on another origin (default "same-origin"). */
29
+ credentials?: 'same-origin' | 'include';
30
+ /** Submit as soon as every box is filled (default true). */
31
+ autoSubmit?: boolean;
32
+ /** Web OTP API (Android Chrome) when available (default true). */
33
+ webOtp?: boolean;
34
+ className?: string;
35
+ style?: CSSProperties;
36
+ id?: string;
37
+ onSent?: (detail: OtpSentDetail) => void;
38
+ onVerified?: (detail: OtpVerifiedDetail) => void;
39
+ onError?: (detail: OtpErrorDetail) => void;
40
+ }
41
+ declare const LabyrintheOtp: react.ForwardRefExoticComponent<LabyrintheOtpProps & react.RefAttributes<LabyrintheOtpElement | null>>;
42
+
43
+ export { LabyrintheOtp, type LabyrintheOtpProps };
@@ -0,0 +1,43 @@
1
+ import * as react from 'react';
2
+ import { CSSProperties } from 'react';
3
+ import { OtpWidgetLocale, OtpWidgetTextsOverride, OtpSentDetail, OtpVerifiedDetail, OtpErrorDetail, LabyrintheOtpElement } from '@labyrinthe/otp-widget';
4
+ export { LabyrintheOtpElement, OtpErrorDetail, OtpSentDetail, OtpVerifiedDetail, OtpWidgetLocale, OtpWidgetTextsOverride } from '@labyrinthe/otp-widget';
5
+
6
+ interface LabyrintheOtpProps {
7
+ /** Your endpoint that sends the code (default "/api/otp/send"). */
8
+ sendUrl?: string;
9
+ /** Your endpoint that verifies the code (default "/api/otp/verify"). */
10
+ verifyUrl?: string;
11
+ /** "fr" (default) or "en". */
12
+ locale?: OtpWidgetLocale;
13
+ /** Default country code, without "+" (default "243", DR Congo). */
14
+ countryCode?: string;
15
+ /** Pre-filled phone number. */
16
+ phone?: string;
17
+ /** Number of code boxes before the server says otherwise (default 6). */
18
+ codeLength?: number;
19
+ codeType?: 'numeric' | 'alphanumeric';
20
+ /** "light", "dark" or automatic (default). */
21
+ theme?: 'light' | 'dark';
22
+ /** Title shown above the form; "" hides it. */
23
+ heading?: string;
24
+ /** Text overrides, merged with the locale defaults. */
25
+ texts?: OtpWidgetTextsOverride;
26
+ /** Extra headers sent to your endpoints (e.g. a CSRF token). */
27
+ requestHeaders?: Record<string, string>;
28
+ /** "include" when your endpoints are on another origin (default "same-origin"). */
29
+ credentials?: 'same-origin' | 'include';
30
+ /** Submit as soon as every box is filled (default true). */
31
+ autoSubmit?: boolean;
32
+ /** Web OTP API (Android Chrome) when available (default true). */
33
+ webOtp?: boolean;
34
+ className?: string;
35
+ style?: CSSProperties;
36
+ id?: string;
37
+ onSent?: (detail: OtpSentDetail) => void;
38
+ onVerified?: (detail: OtpVerifiedDetail) => void;
39
+ onError?: (detail: OtpErrorDetail) => void;
40
+ }
41
+ declare const LabyrintheOtp: react.ForwardRefExoticComponent<LabyrintheOtpProps & react.RefAttributes<LabyrintheOtpElement | null>>;
42
+
43
+ export { LabyrintheOtp, type LabyrintheOtpProps };
package/dist/react.js ADDED
@@ -0,0 +1,66 @@
1
+ 'use client';
2
+
3
+ // src/react.tsx
4
+ import { createElement, forwardRef, useEffect, useImperativeHandle, useRef } from "react";
5
+ import {
6
+ EVENT_ERROR,
7
+ EVENT_SENT,
8
+ EVENT_VERIFIED,
9
+ defineLabyrintheOtp
10
+ } from "@labyrinthe/otp-widget";
11
+ var LabyrintheOtp = forwardRef(function LabyrintheOtp2(props, ref) {
12
+ const element = useRef(null);
13
+ const callbacks = useRef(props);
14
+ callbacks.current = props;
15
+ useImperativeHandle(ref, () => element.current, []);
16
+ useEffect(() => {
17
+ defineLabyrintheOtp();
18
+ const node = element.current;
19
+ if (node === null) {
20
+ return void 0;
21
+ }
22
+ const onSent = (event) => callbacks.current.onSent?.(event.detail);
23
+ const onVerified = (event) => callbacks.current.onVerified?.(event.detail);
24
+ const onError = (event) => callbacks.current.onError?.(event.detail);
25
+ node.addEventListener(EVENT_SENT, onSent);
26
+ node.addEventListener(EVENT_VERIFIED, onVerified);
27
+ node.addEventListener(EVENT_ERROR, onError);
28
+ return () => {
29
+ node.removeEventListener(EVENT_SENT, onSent);
30
+ node.removeEventListener(EVENT_VERIFIED, onVerified);
31
+ node.removeEventListener(EVENT_ERROR, onError);
32
+ };
33
+ }, []);
34
+ const { texts, requestHeaders } = props;
35
+ useEffect(() => {
36
+ if (element.current !== null) {
37
+ element.current.texts = texts ?? null;
38
+ }
39
+ }, [texts]);
40
+ useEffect(() => {
41
+ if (element.current !== null) {
42
+ element.current.requestHeaders = requestHeaders ?? {};
43
+ }
44
+ }, [requestHeaders]);
45
+ return createElement("labyrinthe-otp", {
46
+ ref: element,
47
+ id: props.id,
48
+ className: props.className,
49
+ style: props.style,
50
+ "send-url": props.sendUrl,
51
+ "verify-url": props.verifyUrl,
52
+ locale: props.locale,
53
+ "country-code": props.countryCode,
54
+ phone: props.phone,
55
+ "code-length": props.codeLength === void 0 ? void 0 : String(props.codeLength),
56
+ "code-type": props.codeType,
57
+ theme: props.theme,
58
+ heading: props.heading,
59
+ credentials: props.credentials,
60
+ "no-auto-submit": props.autoSubmit === false ? "" : void 0,
61
+ "no-web-otp": props.webOtp === false ? "" : void 0
62
+ });
63
+ });
64
+ export {
65
+ LabyrintheOtp
66
+ };