11<template >
2- <div
3- class =" pin-input__container"
4- >
2+ <div class =" pin-input__container" >
53 <input
64 v-for =" (number, index) in length"
75 :id =" `pin-input${number}`"
86 :key =" index"
9- :ref =" `pin- input${ number}` "
7+ :ref =" ( input) => (pinInputRefs[ number] = input) "
108 v-model =" innerValue[number - 1]"
119 :type =" visible ? 'text' : 'password'"
1210 maxlength =" 1"
2119 </div >
2220</template >
2321
24- <script >
25- export default {
26- props: {
27- modelValue: {
28- type: String ,
29- default: ' ' ,
30- },
31- length: {
32- type: Number ,
33- default: 4
34- },
35- /**
36- * Especifica o estado do TextInput. As opções são 'default', 'valid' e 'invalid'.
37- */
38- state: {
39- type: String ,
40- default: ' default' ,
41- },
42- visible: {
43- type: Boolean ,
44- default: false ,
45- },
46- /**
47- * Especifica se o PinInput deve ser versão mobile.
48- */
49- mobile: {
50- type: Boolean ,
51- default: false ,
52- }
53- },
22+ <script setup>
23+ import { ref , defineProps , defineEmits , computed , watch } from ' vue' ;
5424
55- data () {
56- return {
57- innerValue : this . modelValue . split ( ' ' ) ,
58- };
25+ const props = defineProps ( {
26+ modelValue : {
27+ type : String ,
28+ default : ' ' ,
5929 },
60-
61- computed: {
62- computedClass () {
63- let classToUse = ' ' ;
64-
65- switch (this .state ) {
66- case ' valid' :
67- classToUse = ' pin-input--valid' ;
68- break ;
69- case ' invalid' :
70- classToUse = ' pin-input--invalid' ;
71- break ;
72- default :
73- classToUse = ' pin-input' ;
74- }
75-
76- return this .mobile ? ` ${ classToUse} pin-input--mobile` : classToUse;
77- },
30+ /**
31+ * Especifica a quantidade de caracteres no PIN.
32+ */
33+ length: {
34+ type: Number ,
35+ default: 4 ,
7836 },
79-
80- watch: {
81- modelValue (value ) {
82- this .innerValue = value .split (' ' );
83- },
37+ /**
38+ * Especifica o estado do TextInput. As opções são 'default', 'valid' e 'invalid'.
39+ */
40+ state: {
41+ type: String ,
42+ default: ' default' ,
8443 },
85-
86- methods: {
87- handleInput (event , index ) {
88- let stringifiedPin = this .innerValue .join (' ' );
89-
90- if (index < this .length && event .inputType !== ' deleteContentBackward' ) {
91- let nextInput = this .$refs [` pin-input${ index + 1 } ` ][0 ];
92- nextInput .focus ();
93- }
94-
95- if (stringifiedPin .length === this .length ) {
96- this .$emit (' update:modelValue' , stringifiedPin);
97- }
98- },
99-
100- handleBack (index ) {
101- if (index > 1 ) {
102- this .innerValue [index - 1 ] = ' ' ;
103- let previousInput = this .$refs [` pin-input${ index - 1 } ` ][0 ];
104-
105- setTimeout (() => {
106- previousInput .focus ();
107- }, 150 );
108- }
109- },
110-
111- fixCursorPosition (index ) {
112- let input = this .$refs [` pin-input${ index} ` ][0 ];
113- setTimeout (() => {
114- input .setSelectionRange (1 , 1 );
115- }, 3 );
116- },
117-
118- changeInputContent (event , index ) {
119- this .innerValue .splice (index - 1 , 1 , event .key );
120- if (index < this .length ) {
121- this .$refs [` pin-input${ index + 1 } ` ][0 ].focus ();
122- }
123-
124- if (index === this .length ) {
125- this .$emit (' update:modelValue' , this .innerValue .join (' ' ));
126- }
127- }
44+ /**
45+ * Especifica se os caracteres do PIN são visíveis ou não.
46+ */
47+ visible: {
48+ type: Boolean ,
49+ default: false ,
50+ },
51+ /**
52+ * Especifica se o PinInput deve ser versão mobile.
53+ */
54+ mobile: {
55+ type: Boolean ,
56+ default: false ,
12857 },
58+ });
59+
60+ const emits = defineEmits ([' update:modelValue' ]);
61+
62+ const innerValue = ref (props .modelValue .split (' ' ));
63+ const pinInputRefs = ref ([]);
64+
65+ const computedClass = computed (() => {
66+ let classToUse = ' ' ;
67+
68+ switch (props .state ) {
69+ case ' valid' :
70+ classToUse = ' pin-input--valid' ;
71+ break ;
72+ case ' invalid' :
73+ classToUse = ' pin-input--invalid' ;
74+ break ;
75+ default :
76+ classToUse = ' pin-input' ;
77+ }
78+
79+ return props .mobile ? ` ${ classToUse} pin-input--mobile` : classToUse;
80+ });
81+
82+ watch (
83+ () => props .modelValue ,
84+ (value ) => {
85+ innerValue .value = value .split (' ' );
86+ }
87+ );
88+
89+ function handleInput (event , index ) {
90+ let stringifiedPin = innerValue .value .join (' ' );
91+ const length = props .length ;
92+
93+ if (index < length && event .inputType !== ' deleteContentBackward' ) {
94+ let nextInput = pinInputRefs .value [index + 1 ];
95+ nextInput .focus ();
96+ }
97+
98+ if (stringifiedPin .length === length) {
99+ emits (' update:modelValue' , stringifiedPin);
100+ }
101+ }
102+
103+ function handleBack (index ) {
104+ if (index > 1 ) {
105+ innerValue .value [index - 1 ] = ' ' ;
106+ let previousInput = pinInputRefs .value [index - 1 ];
107+
108+ setTimeout (() => {
109+ previousInput .focus ();
110+ }, 150 );
111+ }
112+ }
113+
114+ function handleForth (index ) {
115+ if (index < props .length ) {
116+ return pinInputRefs .value [index + 1 ].focus ();
117+ }
118+
119+ pinInputRefs .value [index].blur ();
120+ }
121+
122+ function fixCursorPosition (index ) {
123+ let input = pinInputRefs .value [index];
124+
125+ setTimeout (() => {
126+ input .setSelectionRange (1 , 1 );
127+ }, 3 );
128+ }
129+
130+ function changeInputContent (event , index ) {
131+ if (event .key === ' Enter' ) {
132+ handleForth (index);
133+
134+ return ;
135+ }
136+
137+ const length = props .length ;
138+
139+ innerValue .value .splice (index - 1 , 1 , event .key );
140+ if (index < length) {
141+ pinInputRefs .value [index + 1 ].focus ();
142+ }
143+
144+ if (index === length && innerValue .value .join (' ' ).length === length) {
145+ emits (' update:modelValue' , innerValue .value .join (' ' ));
146+ }
129147}
130148 </script >
131149
132150<style lang="scss" scoped>
133151@use ' ../assets/sass/tokens/index' as tokens ;
134- .pin-input {
135- height : 40px ;
136- box-sizing : border-box ;
137- width : 36px ;
138- border-radius : tokens .$border-radius-extra-small ;
139- border : 1px solid tokens .$n-100 ;
140- text-align : center ;
141- font-size : 1.5em ;
142- transition : tokens .$interaction ;
143-
144- & --mobile {
145- @extend .pin-input ;
146- height : 48px ;
147- width : 42px ;
148- }
152+ .pin-input {
153+ height : 40px ;
154+ box-sizing : border-box ;
155+ width : 36px ;
156+ border-radius : tokens .$border-radius-extra-small ;
157+ border : 1px solid tokens .$n-100 ;
158+ text-align : center ;
159+ font-size : 1.5em ;
160+ transition : tokens .$interaction ;
161+
162+ & --mobile {
163+ @extend .pin-input ;
164+ height : 48px ;
165+ width : 42px ;
166+ }
149167
150- & __container {
151- display : flex ;
152- gap : 8px ;
153- }
168+ & __container {
169+ display : flex ;
170+ gap : 8px ;
171+ }
154172
155- & :focus-visible {
156- outline-color : tokens .$bn-300 ;
157- color : tokens .$bn-300 ;
158- transition : tokens .$interaction ;
159- }
173+ & :focus-visible {
174+ outline-color : tokens .$bn-300 ;
175+ color : tokens .$bn-300 ;
176+ transition : tokens .$interaction ;
177+ }
160178
161- & --valid {
162- @extend .pin-input ;
163- border : 1px solid tokens .$gp-500 ;
179+ & --valid {
180+ @extend .pin-input ;
181+ border : 1px solid tokens .$gp-500 ;
164182
165- & :focus-visible {
166- outline-color : tokens .$gp-500 ;
167- }
183+ & :focus-visible {
184+ outline-color : tokens .$gp-500 ;
168185 }
186+ }
169187
170- & --invalid {
171- @extend .pin-input ;
172- border : 1px solid tokens .$rc-500 ;
188+ & --invalid {
189+ @extend .pin-input ;
190+ border : 1px solid tokens .$rc-500 ;
173191
174- & :focus-visible {
175- outline-color : tokens .$rc-500 ;
176- }
192+ & :focus-visible {
193+ outline-color : tokens .$rc-500 ;
177194 }
178195 }
179- </style >
196+ }
197+ </style >
0 commit comments