1const { onMounted, nextTick, reactive } = Vue; 2 3const Login2faInput = { 4 name: 'TwoFactorAuthInput', 5 emits: ['completeCode', 'newInteraction'], 6 props: { 7 modalErrorMessage: { 8 type: String, 9 required: true 10 }, 11 modalId: { 12 type: String, 13 required: true 14 }, 15 callInProgress: { 16 type: Boolean, 17 default: false 18 } 19 }, 20 setup(props, { emit }) { 21 const values = reactive(['', '', '', '', '', '']); 22 const inputs = new Array(6); 23 24 onMounted(() => { 25 const modalEl = document.getElementById(props.modalId); 26 if (modalEl) { 27 modalEl.addEventListener('shown.bs.modal', () => { 28 nextTick(() => { 29 inputs[0]?.focus(); 30 }); 31 }); 32 } 33 }); 34 35 const checkComplete = () => { 36 if (values.every((v) => v !== '')) { 37 const code = values.join(''); 38 emit('completeCode', code); 39 } 40 }; 41 42 const onInput = (event, index) => { 43 const input = event.target; 44 let value = input.value; 45 46 if (value.length > 1) { 47 value = value.slice(-1); 48 } 49 50 values[index] = value; 51 input.value = value; 52 53 if (value !== '' && index < inputs.length - 1) { 54 nextTick(() => { 55 inputs[index + 1]?.focus(); 56 }); 57 } 58 emit('newInteraction'); 59 checkComplete(); 60 }; 61 62 const onKeyDown = (event, index) => { 63 switch (event.key) { 64 case 'Backspace': 65 event.preventDefault(); 66 if (values[index] === '') { 67 if (index > 0) { 68 values[index - 1] = ''; 69 nextTick(() => { 70 inputs[index - 1]?.focus(); 71 }); 72 } 73 } else { 74 values[index] = ''; 75 } 76 emit('newInteraction'); 77 break; 78 79 case 'ArrowLeft': 80 if (index > 0) { 81 event.preventDefault(); 82 nextTick(() => { 83 inputs[index - 1]?.focus(); 84 }); 85 } 86 break; 87 88 case 'ArrowRight': 89 if (index < inputs.length - 1) { 90 event.preventDefault(); 91 nextTick(() => { 92 inputs[index + 1]?.focus(); 93 }); 94 } 95 break; 96 97 default: 98 break; 99 } 100 }; 101 102 const onPaste = (event) => { 103 const pasteData = event.clipboardData.getData('Text').slice(0, 6); 104 if (pasteData.length > 0) { 105 event.preventDefault(); 106 const chars = pasteData.split(''); 107 for (let i = 0; i < chars.length && i < values.length; i++) { 108 values[i] = chars[i]; 109 } 110 if (chars.length < 6) { 111 nextTick(() => { 112 inputs[chars.length]?.focus(); 113 }); 114 } else { 115 inputs[inputs.length - 1]?.focus(); 116 } 117 emit('newInteraction'); 118 checkComplete(); 119 } 120 }; 121 122 const onFocus = (event) => { 123 event.target.select(); 124 }; 125 126 const setInputRef = (el, index) => { 127 inputs[index] = el; 128 }; 129 130 return { 131 values, 132 onInput, 133 onKeyDown, 134 onPaste, 135 onFocus, 136 setInputRef, 137 modalInputMessage: ACC.login2faModalInputMessage, 138 props 139 }; 140 }, 141 template: ` 142 <div class="two-factor-input-container"> 143 <div class="two-factor-input" :class="{ 'call-in-progress': props.callInProgress }"> 144 <input 145 v-for="(value, index) in values" 146 :key="index" 147 :ref="(el) => setInputRef(el, index)" 148 v-model="values[index]" 149 @focus="onFocus"
150 @input="onInput($event, index)" 151 @keydown="onKeyDown($event, index)" 152 @paste="onPaste" 153 :class="props.modalErrorMessage ? 'input-box input-box-danger':'input-box'" 154 type="text" 155 inputmode="numeric" 156 autocomplete="one-time-code" 157 /> 158 <div v-if="props.callInProgress" class="call-in-progress-overlay"> 159 <div class="spinner"></div> 160 </div> 161 </div> 162 <p v-if="props.modalErrorMessage" class="two-factor-error-input-message">{{ props.modalErrorMessage }}</p> 163 <p class="two-factor-input-message">{{ modalInputMessage }}</p> 164 </div> 165 ` 166}; 167 168export { Login2faInput };
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.