1
- import React , { Component , MouseEvent } from "react" ;
1
+ import React , { MouseEvent , useEffect , useReducer } from "react" ;
2
2
3
3
import {
4
- shouldRender ,
5
4
parseDateString ,
6
5
toDateString ,
7
6
pad ,
@@ -17,23 +16,64 @@ function rangeOptions(start: number, stop: number) {
17
16
return options ;
18
17
}
19
18
20
- function readyForChange ( state : any ) {
21
- return Object . keys ( state ) . every ( ( key ) => state [ key ] !== - 1 ) ;
19
+ function readyForChange ( state : DateObject ) {
20
+ return Object . values ( state ) . every ( ( value ) => value !== - 1 ) ;
22
21
}
23
22
24
- function DateElement ( props : any ) {
25
- const {
26
- type,
27
- range,
28
- value,
29
- select,
30
- rootId,
31
- disabled,
32
- readonly,
33
- autofocus,
34
- registry,
35
- onBlur,
36
- } = props ;
23
+ function dateElementProps (
24
+ state : DateObject ,
25
+ time : boolean ,
26
+ yearsRange : [ number , number ] = [ 1900 , new Date ( ) . getFullYear ( ) + 2 ]
27
+ ) {
28
+ const { year, month, day, hour, minute, second } = state ;
29
+ const data = [
30
+ {
31
+ type : "year" ,
32
+ range : yearsRange ,
33
+ value : year ,
34
+ } ,
35
+ { type : "month" , range : [ 1 , 12 ] , value : month } ,
36
+ { type : "day" , range : [ 1 , 31 ] , value : day } ,
37
+ ] as { type : string ; range : [ number , number ] ; value : number | undefined } [ ] ;
38
+ if ( time ) {
39
+ data . push (
40
+ { type : "hour" , range : [ 0 , 23 ] , value : hour } ,
41
+ { type : "minute" , range : [ 0 , 59 ] , value : minute } ,
42
+ { type : "second" , range : [ 0 , 59 ] , value : second }
43
+ ) ;
44
+ }
45
+ return data ;
46
+ }
47
+
48
+ type DateElementProps < T , F > = Pick <
49
+ WidgetProps < T , F > ,
50
+ | "value"
51
+ | "disabled"
52
+ | "readonly"
53
+ | "autofocus"
54
+ | "registry"
55
+ | "onBlur"
56
+ | "onFocus"
57
+ > & {
58
+ rootId : string ;
59
+ select : ( property : keyof DateObject , value : any ) => void ;
60
+ type : string ;
61
+ range : [ number , number ] ;
62
+ } ;
63
+
64
+ function DateElement < T , F > ( {
65
+ type,
66
+ range,
67
+ value,
68
+ select,
69
+ rootId,
70
+ disabled,
71
+ readonly,
72
+ autofocus,
73
+ registry,
74
+ onBlur,
75
+ onFocus,
76
+ } : DateElementProps < T , F > ) {
37
77
const id = rootId + "_" + type ;
38
78
const { SelectWidget } = registry . widgets ;
39
79
return (
@@ -47,152 +87,118 @@ function DateElement(props: any) {
47
87
disabled = { disabled }
48
88
readonly = { readonly }
49
89
autofocus = { autofocus }
50
- onChange = { ( value : any ) => select ( type , value ) }
90
+ onChange = { ( value : any ) => select ( type as keyof DateObject , value ) }
51
91
onBlur = { onBlur }
92
+ onFocus = { onFocus }
93
+ registry = { registry }
94
+ label = ""
52
95
/>
53
96
) ;
54
97
}
55
98
56
- /** The `AltDateWidget` is an alternative widget for rendering date properties. */
57
- class AltDateWidget < T = any , F = any > extends Component <
58
- WidgetProps < T , F > ,
59
- DateObject
60
- > {
61
- static defaultProps = {
62
- time : false ,
63
- disabled : false ,
64
- readonly : false ,
65
- autofocus : false ,
66
- options : {
67
- yearsRange : [ 1900 , new Date ( ) . getFullYear ( ) + 2 ] ,
99
+ /** The `AltDateWidget` is an alternative widget for rendering date properties.
100
+ * @param props - The `WidgetProps` for this component
101
+ */
102
+ function AltDateWidget < T = any , F = any > ( {
103
+ time = false ,
104
+ disabled = false ,
105
+ readonly = false ,
106
+ autofocus = false ,
107
+ options,
108
+ id,
109
+ registry,
110
+ onBlur,
111
+ onFocus,
112
+ onChange,
113
+ value,
114
+ } : WidgetProps < T , F > ) {
115
+ const [ state , setState ] = useReducer (
116
+ ( state : DateObject , action : Partial < DateObject > ) => {
117
+ return { ...state , ...action } ;
68
118
} ,
69
- } ;
70
-
71
- /**
72
- *
73
- * @param props - The `WidgetProps` for this component
74
- */
75
- constructor ( props : WidgetProps < T , F > ) {
76
- super ( props ) ;
77
- this . state = parseDateString ( props . value , props . time ) ;
78
- }
119
+ parseDateString ( value , time )
120
+ ) ;
79
121
80
- componentDidUpdate ( prevProps : WidgetProps < T , F > ) {
81
- if (
82
- prevProps . value &&
83
- prevProps . value !== parseDateString ( this . props . value , this . props . time )
84
- ) {
85
- this . setState ( parseDateString ( this . props . value , this . props . time ) ) ;
122
+ useEffect ( ( ) => {
123
+ if ( value && value !== toDateString ( state , time ) ) {
124
+ setState ( parseDateString ( value , time ) ) ;
86
125
}
87
- }
126
+ } , [ value ] ) ;
88
127
89
- shouldComponentUpdate (
90
- nextProps : WidgetProps < T , F > ,
91
- nextState : DateObject
92
- ) : boolean {
93
- return shouldRender ( this , nextProps , nextState ) ;
94
- }
128
+ useEffect ( ( ) => {
129
+ if ( readyForChange ( state ) ) {
130
+ // Only propagate to parent state if we have a complete date{time}
131
+ onChange ( toDateString ( state , time ) ) ;
132
+ }
133
+ } , [ state , time ] ) ;
95
134
96
- onChange = ( property : keyof DateObject , value : any ) => {
97
- this . setState (
98
- { [ property ] : typeof value === "undefined" ? - 1 : value } as Pick <
99
- DateObject ,
100
- keyof DateObject
101
- > ,
102
- ( ) => {
103
- // Only propagate to parent state if we have a complete date{time}
104
- if ( readyForChange ( this . state ) ) {
105
- this . props . onChange ( toDateString ( this . state , this . props . time ) ) ;
106
- }
107
- }
108
- ) ;
135
+ const handleChange = ( property : keyof DateObject , value : string ) => {
136
+ setState ( { [ property ] : value } ) ;
109
137
} ;
110
138
111
- setNow = ( event : MouseEvent < HTMLAnchorElement > ) => {
139
+ const handleSetNow = ( event : MouseEvent < HTMLAnchorElement > ) => {
112
140
event . preventDefault ( ) ;
113
- const { time, disabled, readonly, onChange } = this . props ;
114
141
if ( disabled || readonly ) {
115
142
return ;
116
143
}
117
144
const nowDateObj = parseDateString ( new Date ( ) . toJSON ( ) , time ) ;
118
- this . setState ( nowDateObj , ( ) => onChange ( toDateString ( this . state , time ) ) ) ;
145
+ setState ( nowDateObj ) ;
119
146
} ;
120
147
121
- clear = ( event : MouseEvent < HTMLAnchorElement > ) => {
148
+ const handleClear = ( event : MouseEvent < HTMLAnchorElement > ) => {
122
149
event . preventDefault ( ) ;
123
- const { time, disabled, readonly, onChange } = this . props ;
124
150
if ( disabled || readonly ) {
125
151
return ;
126
152
}
127
- this . setState ( parseDateString ( "" , time ) , ( ) => onChange ( undefined ) ) ;
153
+ setState ( parseDateString ( "" , time ) ) ;
154
+ onChange ( undefined ) ;
128
155
} ;
129
156
130
- get dateElementProps ( ) {
131
- const { time, options } = this . props ;
132
- const { year, month, day, hour, minute, second } = this . state ;
133
- const data = [
134
- {
135
- type : "year" ,
136
- range : options . yearsRange ,
137
- value : year ,
138
- } ,
139
- { type : "month" , range : [ 1 , 12 ] , value : month } ,
140
- { type : "day" , range : [ 1 , 31 ] , value : day } ,
141
- ] as { type : string ; range : [ number , number ] ; value : number | undefined } [ ] ;
142
- if ( time ) {
143
- data . push (
144
- { type : "hour" , range : [ 0 , 23 ] , value : hour } ,
145
- { type : "minute" , range : [ 0 , 59 ] , value : minute } ,
146
- { type : "second" , range : [ 0 , 59 ] , value : second }
147
- ) ;
148
- }
149
- return data ;
150
- }
151
-
152
- render ( ) {
153
- const { id, disabled, readonly, autofocus, registry, onBlur, options } =
154
- this . props ;
155
- return (
156
- < ul className = "list-inline" >
157
- { this . dateElementProps . map ( ( elemProps , i ) => (
158
- < li key = { i } >
159
- < DateElement
160
- rootId = { id }
161
- select = { this . onChange }
162
- { ...elemProps }
163
- disabled = { disabled }
164
- readonly = { readonly }
165
- registry = { registry }
166
- onBlur = { onBlur }
167
- autofocus = { autofocus && i === 0 }
168
- />
169
- </ li >
170
- ) ) }
171
- { ( options . hideNowButton !== "undefined"
172
- ? ! options . hideNowButton
173
- : true ) && (
174
- < li >
175
- < a href = "#" className = "btn btn-info btn-now" onClick = { this . setNow } >
176
- Now
177
- </ a >
178
- </ li >
179
- ) }
180
- { ( options . hideClearButton !== "undefined"
181
- ? ! options . hideClearButton
182
- : true ) && (
183
- < li >
184
- < a
185
- href = "#"
186
- className = "btn btn-warning btn-clear"
187
- onClick = { this . clear }
188
- >
189
- Clear
190
- </ a >
191
- </ li >
192
- ) }
193
- </ ul >
194
- ) ;
195
- }
157
+ return (
158
+ < ul className = "list-inline" >
159
+ { dateElementProps (
160
+ state ,
161
+ time ,
162
+ options . yearsRange as [ number , number ] | undefined
163
+ ) . map ( ( elemProps , i ) => (
164
+ < li key = { i } >
165
+ < DateElement
166
+ rootId = { id }
167
+ select = { handleChange }
168
+ { ...elemProps }
169
+ disabled = { disabled }
170
+ readonly = { readonly }
171
+ registry = { registry }
172
+ onBlur = { onBlur }
173
+ onFocus = { onFocus }
174
+ autofocus = { autofocus && i === 0 }
175
+ />
176
+ </ li >
177
+ ) ) }
178
+ { ( options . hideNowButton !== "undefined"
179
+ ? ! options . hideNowButton
180
+ : true ) && (
181
+ < li >
182
+ < a href = "#" className = "btn btn-info btn-now" onClick = { handleSetNow } >
183
+ Now
184
+ </ a >
185
+ </ li >
186
+ ) }
187
+ { ( options . hideClearButton !== "undefined"
188
+ ? ! options . hideClearButton
189
+ : true ) && (
190
+ < li >
191
+ < a
192
+ href = "#"
193
+ className = "btn btn-warning btn-clear"
194
+ onClick = { handleClear }
195
+ >
196
+ Clear
197
+ </ a >
198
+ </ li >
199
+ ) }
200
+ </ ul >
201
+ ) ;
196
202
}
197
203
198
204
export default AltDateWidget ;
0 commit comments