@instructure/ui-date-input 9.6.0 → 9.6.1-snapshot-2
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.
- package/CHANGELOG.md +11 -0
- package/es/DateInput2/index.js +74 -48
- package/es/DateInput2/props.js +0 -2
- package/lib/DateInput2/index.js +74 -48
- package/lib/DateInput2/props.js +0 -2
- package/package.json +20 -20
- package/src/DateInput2/README.md +62 -29
- package/src/DateInput2/index.tsx +74 -53
- package/src/DateInput2/props.ts +8 -19
- package/tsconfig.build.tsbuildinfo +1 -1
- package/types/DateInput2/index.d.ts +4 -4
- package/types/DateInput2/index.d.ts.map +1 -1
- package/types/DateInput2/props.d.ts +6 -15
- package/types/DateInput2/props.d.ts.map +1 -1
package/src/DateInput2/index.tsx
CHANGED
@@ -44,9 +44,34 @@ import type { DateInput2Props } from './props'
|
|
44
44
|
import type { FormMessage } from '@instructure/ui-form-field'
|
45
45
|
import type { Moment } from '@instructure/ui-i18n'
|
46
46
|
|
47
|
-
|
47
|
+
/*
|
48
|
+
* Tries parsing a date in a given timezone, if it's not possible, returns an empty string
|
49
|
+
* If parsing is successful an ISO formatted datetime string is returned in UTC timezone
|
50
|
+
*/
|
51
|
+
function timezoneDateToUtc(dateString: string, timezone: string): string {
|
52
|
+
// Don't try to parse short dateString, they are incomplete
|
53
|
+
if (dateString.length < 10) {
|
54
|
+
return ''
|
55
|
+
}
|
56
|
+
|
57
|
+
// Create a Date object from the input date string
|
48
58
|
const date = new Date(dateString)
|
49
|
-
|
59
|
+
|
60
|
+
// Check if the date is valid
|
61
|
+
if (isNaN(date.getTime())) {
|
62
|
+
return ''
|
63
|
+
}
|
64
|
+
|
65
|
+
// snippet from https://stackoverflow.com/a/57842203
|
66
|
+
// but it might need to be improved:
|
67
|
+
// "This produces incorrect datetimes for several hours surrounding daylight saving times if the
|
68
|
+
// computer running the code is in a zone that doesn't obey the same daylight saving shifts as the target zone."
|
69
|
+
const utcDate = new Date(date.toLocaleString('en-US', { timeZone: 'UTC' }))
|
70
|
+
const tzDate = new Date(date.toLocaleString('en-US', { timeZone: timezone }))
|
71
|
+
const offset = utcDate.getTime() - tzDate.getTime()
|
72
|
+
const newDate = new Date(date.getTime() + offset)
|
73
|
+
|
74
|
+
return newDate.toISOString()
|
50
75
|
}
|
51
76
|
|
52
77
|
function defaultDateFormatter(
|
@@ -66,6 +91,8 @@ function defaultDateFormatter(
|
|
66
91
|
---
|
67
92
|
category: components
|
68
93
|
---
|
94
|
+
|
95
|
+
@module experimental
|
69
96
|
**/
|
70
97
|
const DateInput2 = ({
|
71
98
|
renderLabel,
|
@@ -89,37 +116,48 @@ const DateInput2 = ({
|
|
89
116
|
// margin, TODO enable this prop
|
90
117
|
...rest
|
91
118
|
}: DateInput2Props) => {
|
92
|
-
const
|
119
|
+
const localeContext = useContext(ApplyLocaleContext)
|
120
|
+
|
121
|
+
const getLocale = () => {
|
122
|
+
if (locale) {
|
123
|
+
return locale
|
124
|
+
} else if (localeContext.locale) {
|
125
|
+
return localeContext.locale
|
126
|
+
}
|
127
|
+
// default to the system's locale
|
128
|
+
return Locale.browserLocale()
|
129
|
+
}
|
130
|
+
|
131
|
+
const getTimezone = () => {
|
132
|
+
if (timezone) {
|
133
|
+
return timezone
|
134
|
+
} else if (localeContext.timezone) {
|
135
|
+
return localeContext.timezone
|
136
|
+
}
|
137
|
+
// default to the system's timezone
|
138
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone
|
139
|
+
}
|
140
|
+
|
141
|
+
const [inputValue, setInputValue] = useState<string>(
|
142
|
+
value ? formatDate(value, getLocale(), getTimezone()) : ''
|
143
|
+
)
|
93
144
|
const [inputMessages, setInputMessages] = useState<FormMessage[]>(
|
94
145
|
messages || []
|
95
146
|
)
|
96
147
|
const [showPopover, setShowPopover] = useState<boolean>(false)
|
97
|
-
const localeContext = useContext(ApplyLocaleContext)
|
98
|
-
|
99
|
-
useEffect(() => {
|
100
|
-
// when `value` is changed, validation removes the error message if passes
|
101
|
-
// but it's NOT adding error message if validation fails for better UX
|
102
|
-
validateInput(true)
|
103
|
-
}, [value])
|
104
148
|
|
105
149
|
useEffect(() => {
|
106
150
|
setInputMessages(messages || [])
|
107
151
|
}, [messages])
|
108
152
|
|
109
153
|
useEffect(() => {
|
110
|
-
|
111
|
-
}, [])
|
154
|
+
validateInput(true)
|
155
|
+
}, [inputValue])
|
112
156
|
|
113
|
-
const handleInputChange = (
|
114
|
-
|
115
|
-
newValue
|
116
|
-
|
117
|
-
) => {
|
118
|
-
// blur event formats the input which shouldn't trigger parsing
|
119
|
-
if (e.type !== 'blur') {
|
120
|
-
setSelectedDate(parseDate(newValue))
|
121
|
-
}
|
122
|
-
onChange?.(e, newValue, parsedDate)
|
157
|
+
const handleInputChange = (e: SyntheticEvent, newValue: string) => {
|
158
|
+
setInputValue(newValue)
|
159
|
+
const parsedInput = timezoneDateToUtc(newValue, getTimezone())
|
160
|
+
onChange?.(e, parsedInput, newValue)
|
123
161
|
}
|
124
162
|
|
125
163
|
const handleDateSelected = (
|
@@ -128,17 +166,16 @@ const DateInput2 = ({
|
|
128
166
|
e: SyntheticEvent
|
129
167
|
) => {
|
130
168
|
const formattedDate = formatDate(dateString, getLocale(), getTimezone())
|
131
|
-
|
132
|
-
setSelectedDate(parsedDate)
|
133
|
-
handleInputChange(e, formattedDate, parsedDate)
|
169
|
+
setInputValue(formattedDate)
|
134
170
|
setShowPopover(false)
|
171
|
+
onChange?.(e, dateString, formattedDate)
|
135
172
|
onRequestValidateDate?.(dateString, true)
|
136
173
|
}
|
137
174
|
|
138
175
|
// onlyRemoveError is used to remove the error msg immediately when the user inputs a valid date (and don't wait for blur event)
|
139
176
|
const validateInput = (onlyRemoveError = false): boolean => {
|
140
177
|
// don't validate empty input
|
141
|
-
if (!
|
178
|
+
if (!inputValue || timezoneDateToUtc(inputValue, getTimezone()) || value) {
|
142
179
|
setInputMessages(messages || [])
|
143
180
|
return true
|
144
181
|
}
|
@@ -159,32 +196,16 @@ const DateInput2 = ({
|
|
159
196
|
return false
|
160
197
|
}
|
161
198
|
|
162
|
-
const getLocale = () => {
|
163
|
-
if (locale) {
|
164
|
-
return locale
|
165
|
-
} else if (localeContext.locale) {
|
166
|
-
return localeContext.locale
|
167
|
-
}
|
168
|
-
return Locale.browserLocale()
|
169
|
-
}
|
170
|
-
|
171
|
-
const getTimezone = () => {
|
172
|
-
if (timezone) {
|
173
|
-
return timezone
|
174
|
-
} else if (localeContext.timezone) {
|
175
|
-
return localeContext.timezone
|
176
|
-
}
|
177
|
-
// default to the system's timezone
|
178
|
-
return Intl.DateTimeFormat().resolvedOptions().timeZone
|
179
|
-
}
|
180
|
-
|
181
199
|
const handleBlur = (e: SyntheticEvent) => {
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
200
|
+
if (value) {
|
201
|
+
const formattedDate = formatDate(value, getLocale(), getTimezone())
|
202
|
+
if (formattedDate !== inputValue) {
|
203
|
+
setInputValue(formattedDate)
|
204
|
+
onChange?.(e, value, formattedDate)
|
205
|
+
}
|
186
206
|
}
|
187
|
-
|
207
|
+
validateInput(false)
|
208
|
+
onRequestValidateDate?.(value, !!value)
|
188
209
|
onBlur?.(e)
|
189
210
|
}
|
190
211
|
|
@@ -196,7 +217,7 @@ const DateInput2 = ({
|
|
196
217
|
onChange={handleInputChange}
|
197
218
|
onBlur={handleBlur}
|
198
219
|
isRequired={isRequired}
|
199
|
-
value={
|
220
|
+
value={inputValue}
|
200
221
|
placeholder={placeholder}
|
201
222
|
width={width}
|
202
223
|
size={size}
|
@@ -228,8 +249,8 @@ const DateInput2 = ({
|
|
228
249
|
<Calendar
|
229
250
|
withYearPicker={withYearPicker}
|
230
251
|
onDateSelected={handleDateSelected}
|
231
|
-
selectedDate={
|
232
|
-
visibleMonth={
|
252
|
+
selectedDate={value}
|
253
|
+
visibleMonth={value}
|
233
254
|
locale={getLocale()}
|
234
255
|
timezone={getTimezone()}
|
235
256
|
role="listbox"
|
package/src/DateInput2/props.ts
CHANGED
@@ -45,9 +45,9 @@ type DateInput2OwnProps = {
|
|
45
45
|
nextMonthButton: string
|
46
46
|
}
|
47
47
|
/**
|
48
|
-
* Specifies the input value.
|
48
|
+
* Specifies the input value *before* formatting. The `formatDate` will be applied to it before displaying. Should be a valid, parsable date.
|
49
49
|
*/
|
50
|
-
value?: string
|
50
|
+
value?: string
|
51
51
|
/**
|
52
52
|
* Specifies the input size.
|
53
53
|
*/
|
@@ -62,8 +62,8 @@ type DateInput2OwnProps = {
|
|
62
62
|
*/
|
63
63
|
onChange?: (
|
64
64
|
event: React.SyntheticEvent,
|
65
|
-
|
66
|
-
|
65
|
+
isoDateString: string,
|
66
|
+
formattedValue: string
|
67
67
|
) => void
|
68
68
|
/**
|
69
69
|
* Callback executed when the input fires a blur event.
|
@@ -99,21 +99,12 @@ type DateInput2OwnProps = {
|
|
99
99
|
*/
|
100
100
|
messages?: FormMessage[]
|
101
101
|
/**
|
102
|
-
* Callback fired
|
103
|
-
|
104
|
-
|
105
|
-
/**
|
106
|
-
* Callback fired requesting the calendar be hidden.
|
107
|
-
*/
|
108
|
-
onRequestHideCalendar?: (event: SyntheticEvent) => void
|
109
|
-
/**
|
110
|
-
* Callback fired when the input is blurred. Feedback should be provided
|
111
|
-
* to the user when this function is called if the selected date or input
|
112
|
-
* value is invalid. The component has an internal check whether the date can
|
113
|
-
* be parsed to a valid date.
|
102
|
+
* Callback fired when the input is blurred or a date is selected from the calendar.
|
103
|
+
* Feedback should be provided to the user when this function is called if the selected date or input
|
104
|
+
* value is invalid. The component has an internal check whether the date can be parsed to a valid date.
|
114
105
|
*/
|
115
106
|
onRequestValidateDate?: (
|
116
|
-
|
107
|
+
isoDateString?: string,
|
117
108
|
internalValidationPassed?: boolean
|
118
109
|
) => void | FormMessage[]
|
119
110
|
/**
|
@@ -200,8 +191,6 @@ const propTypes: PropValidators<PropKeys> = {
|
|
200
191
|
isInline: PropTypes.bool,
|
201
192
|
width: PropTypes.string,
|
202
193
|
messages: PropTypes.arrayOf(FormPropTypes.message),
|
203
|
-
onRequestShowCalendar: PropTypes.func,
|
204
|
-
onRequestHideCalendar: PropTypes.func,
|
205
194
|
onRequestValidateDate: PropTypes.func,
|
206
195
|
invalidDateErrorMessage: PropTypes.oneOfType([
|
207
196
|
PropTypes.func,
|