@@ -3,31 +3,71 @@ import { produce } from "immer";
3
3
4
4
export const SEPARATOR = "." ;
5
5
6
+ interface Status {
7
+ isFocused : boolean ;
8
+ isTouched : boolean ;
9
+ }
10
+
6
11
interface Field {
7
12
id : string ;
8
13
name : string ;
9
14
10
- defaultValue ? : string ;
11
- initialValue ? : string ;
15
+ defaultValue : string ;
16
+ initialValue : string ;
12
17
currentValue : string ;
13
18
14
- isFocused : boolean ;
15
- isTouched : boolean ;
19
+ status : Status ;
16
20
}
17
21
18
22
type Fields = {
19
23
[ key : string ] : Field | undefined ;
20
24
} ;
21
25
22
- export class GroupStoreMutable extends TinyEmitter < Callback > {
23
- protected fields : Fields = { } ;
26
+ interface GroupState {
27
+ fields : Fields ;
28
+ status : Status ;
29
+ }
30
+
31
+ const defaultStatus = ( ) : Status => ( { isFocused : false , isTouched : false } ) ;
32
+
33
+ const calculateGroupStatus = ( state : GroupState ) : Status => {
34
+ let isFocused = false ;
35
+ let isTouched = false ;
24
36
25
- constructor ( ) {
26
- super ( ) ;
37
+ for ( const key of Object . keys ( state . fields ) ) {
38
+ const field = state . fields [ key ] ;
39
+ if ( field == null ) {
40
+ continue ;
41
+ }
42
+
43
+ if ( field . status . isFocused ) {
44
+ isFocused = true ;
45
+ }
46
+ if ( field . status . isTouched ) {
47
+ isTouched = true ;
48
+ }
49
+
50
+ // If group is both focused and touched already, nothing is going to change anymore
51
+ if ( isFocused && isTouched ) {
52
+ // Thus, break.
53
+ break ;
54
+ }
27
55
}
28
56
57
+ return {
58
+ isFocused : isFocused ,
59
+ isTouched : isTouched
60
+ } ;
61
+ } ;
62
+
63
+ export class GroupStoreMutable extends TinyEmitter < Callback > {
64
+ protected state : GroupState = {
65
+ fields : { } ,
66
+ status : defaultStatus ( )
67
+ } ;
68
+
29
69
public getField ( fieldId : string ) : Field | undefined {
30
- return this . fields [ fieldId ] ;
70
+ return this . state . fields [ fieldId ] ;
31
71
}
32
72
33
73
public generateFieldId ( name : string , groupId : string ) : string {
@@ -38,56 +78,63 @@ export class GroupStoreMutable extends TinyEmitter<Callback> {
38
78
name : string ,
39
79
groupId : string ,
40
80
defaultValue : string ,
41
- initialValue : string
81
+ initialValue ? : string
42
82
) : void {
43
83
const id = this . generateFieldId ( name , groupId ) ;
44
84
45
- if ( this . fields [ id ] != null ) {
85
+ if ( this . state . fields [ id ] != null ) {
46
86
throw new Error ( `Field with an id '${ id } ' is already registered.` ) ;
47
87
}
48
88
89
+ if ( initialValue == null ) {
90
+ initialValue = defaultValue ;
91
+ }
92
+
49
93
const newField : Field = {
50
94
id : id ,
51
95
name : name ,
52
96
defaultValue : defaultValue ,
53
97
initialValue : initialValue ,
54
- currentValue : initialValue || defaultValue ,
55
- isFocused : false ,
56
- isTouched : false
98
+ currentValue : initialValue ,
99
+ status : {
100
+ isFocused : false ,
101
+ isTouched : false
102
+ }
57
103
} ;
58
104
59
- this . fields = produce ( this . fields , fields => {
60
- fields [ id ] = newField ;
105
+ this . state = produce ( this . state , state => {
106
+ state . fields [ id ] = newField ;
61
107
} ) ;
62
108
this . emit ( ) ;
63
109
}
64
110
65
111
public unregisterField ( id : string ) : void {
66
- if ( this . fields [ id ] == null ) {
112
+ if ( this . state . fields [ id ] == null ) {
67
113
return ;
68
114
}
69
115
70
- this . fields = produce ( this . fields , fields => {
71
- fields [ id ] = undefined ;
116
+ this . state = produce ( this . state , state => {
117
+ state . fields [ id ] = undefined ;
72
118
} ) ;
73
119
this . emit ( ) ;
74
120
}
75
121
76
122
public updateValue ( fieldId : string , value : string ) : void {
77
- if ( this . fields [ fieldId ] == null ) {
123
+ console . log ( `Value updated ${ fieldId } ` ) ;
124
+ if ( this . state . fields [ fieldId ] == null ) {
78
125
throw new Error (
79
126
`Cannot update non-existent field value. (field id '${ fieldId } ').`
80
127
) ;
81
128
}
82
- this . fields = produce ( this . fields , fields => {
83
- const field = fields [ fieldId ] ;
129
+ this . state = produce ( this . state , state => {
130
+ const field = state . fields [ fieldId ] ;
84
131
if ( field == null ) {
85
132
return ;
86
133
}
87
134
88
135
// Value equality check should be abstracted for particular component and value type
89
136
if ( field . currentValue !== value ) {
90
- field . isTouched = true ;
137
+ field . status . isTouched = true ;
91
138
}
92
139
field . currentValue = value ;
93
140
} ) ;
@@ -96,41 +143,69 @@ export class GroupStoreMutable extends TinyEmitter<Callback> {
96
143
}
97
144
98
145
public focus ( fieldId : string ) : void {
146
+ console . log ( `Focus ${ fieldId } ` ) ;
99
147
this . setFocused ( fieldId , true ) ;
100
148
this . emit ( ) ;
101
149
}
102
150
103
151
public blur ( fieldId : string ) : void {
152
+ console . log ( `Blur ${ fieldId } ` ) ;
104
153
this . setFocused ( fieldId , false ) ;
105
154
this . emit ( ) ;
106
155
}
107
156
108
157
private setFocused ( fieldId : string , isFocused : boolean ) : void {
109
- console . log ( `Setting focus for field '${ fieldId } ' to ${ isFocused } ` ) ;
110
- if ( this . fields [ fieldId ] == null ) {
158
+ if ( this . state . fields [ fieldId ] == null ) {
111
159
throw new Error (
112
160
`Cannot update non-existent field value. (field id '${ fieldId } ').`
113
161
) ;
114
162
}
115
- this . fields = produce ( this . fields , fields => {
116
- const field = fields [ fieldId ] ;
117
- if ( field == null ) {
163
+
164
+ this . state = produce ( this . state , state => {
165
+ const field = state . fields [ fieldId ] ;
166
+ if ( field == null || field . status . isFocused === isFocused ) {
118
167
return ;
119
168
}
120
- field . isFocused = isFocused ;
169
+
170
+ field . status . isFocused = isFocused ;
121
171
122
172
// If the field is not touched yet and got focused, make it touched.
123
- if ( ! field . isTouched && isFocused ) {
124
- field . isTouched = true ;
173
+ if ( ! field . status . isTouched && isFocused ) {
174
+ field . status . isTouched = true ;
175
+ }
176
+
177
+ // Recalculate group status, because field status has changed
178
+ state . status = calculateGroupStatus ( state ) ;
179
+ } ) ;
180
+ }
181
+
182
+ public reset ( ) : void {
183
+ console . log ( `Reset state` ) ;
184
+ this . state = produce ( this . state , state => {
185
+ // Reset fields
186
+ const fields = state . fields ;
187
+ for ( const key of Object . keys ( fields ) ) {
188
+ const field = fields [ key ] ;
189
+ if ( field == null ) {
190
+ continue ;
191
+ }
192
+
193
+ field . currentValue = field . initialValue ;
194
+ field . status = defaultStatus ( ) ;
125
195
}
196
+
197
+ // Reset group status
198
+ state . status = defaultStatus ( ) ;
126
199
} ) ;
200
+ this . emit ( ) ;
127
201
}
128
202
129
203
public toObject ( ) : unknown {
130
204
const result : { [ key : string ] : unknown } = { } ;
131
- for ( const key in this . fields ) {
132
- if ( this . fields . hasOwnProperty ( key ) ) {
133
- const field = this . fields [ key ] ;
205
+ const fields = this . state . fields ;
206
+ for ( const key in fields ) {
207
+ if ( fields . hasOwnProperty ( key ) ) {
208
+ const field = fields [ key ] ;
134
209
if ( field == null ) {
135
210
continue ;
136
211
}
0 commit comments