@instructure/ui-date-input 9.5.2-snapshot-7 → 9.5.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 CHANGED
@@ -3,9 +3,12 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
- ## [9.5.2-snapshot-7](https://github.com/instructure/instructure-ui/compare/v9.5.1...v9.5.2-snapshot-7) (2024-07-31)
6
+ ## [9.5.2](https://github.com/instructure/instructure-ui/compare/v9.5.1...v9.5.2) (2024-08-05)
7
7
 
8
- **Note:** Version bump only for package @instructure/ui-date-input
8
+
9
+ ### Bug Fixes
10
+
11
+ * **ui-calendar,ui-date-input:** fix year picker for non latin based locales; return iso date string in onRequestValidateDate ([d7df0e8](https://github.com/instructure/instructure-ui/commit/d7df0e8d9fc0656e877bd243d9858dc3e5a47198))
9
12
 
10
13
 
11
14
 
@@ -26,7 +26,6 @@ var _IconCalendarMonthLin, _IconArrowOpenEndSoli, _IconArrowOpenStartSo;
26
26
 
27
27
  /** @jsx jsx */
28
28
  import { useState, useEffect, useContext } from 'react';
29
- import moment from 'moment-timezone';
30
29
  import { Calendar } from '@instructure/ui-calendar';
31
30
  import { IconButton } from '@instructure/ui-buttons';
32
31
  import { IconCalendarMonthLine, IconArrowOpenEndSolid, IconArrowOpenStartSolid } from '@instructure/ui-icons';
@@ -36,11 +35,10 @@ import { passthroughProps } from '@instructure/ui-react-utils';
36
35
  import { ApplyLocaleContext, Locale } from '@instructure/ui-i18n';
37
36
  import { jsx } from '@instructure/emotion';
38
37
  import { propTypes } from './props';
39
- function isValidDate(dateString) {
40
- return !isNaN(new Date(dateString).getTime());
41
- }
42
- function isValidMomentDate(dateString, locale, timezone) {
43
- return moment.tz(dateString, [moment.ISO_8601, 'llll', 'LLLL', 'lll', 'LLL', 'll', 'LL', 'l', 'L'], locale, true, timezone).isValid();
38
+ function parseDate(dateString) {
39
+ const date = new Date(dateString);
40
+ // return empty string if not a valid date
41
+ return isNaN(date.getTime()) ? '' : date.toISOString();
44
42
  }
45
43
 
46
44
  /**
@@ -82,6 +80,8 @@ const DateInput2 = ({
82
80
  setShowPopover = _useState6[1];
83
81
  const localeContext = useContext(ApplyLocaleContext);
84
82
  useEffect(() => {
83
+ // when `value` is changed, validation runs again and removes the error message if validation passes
84
+ // but it's NOT adding error message if validation fails for better UX
85
85
  validateInput(true);
86
86
  }, [value]);
87
87
  useEffect(() => {
@@ -89,6 +89,10 @@ const DateInput2 = ({
89
89
  }, [messages]);
90
90
  const handleInputChange = (e, value) => {
91
91
  onChange === null || onChange === void 0 ? void 0 : onChange(e, value);
92
+ // blur event formats the input which should trigger parsing
93
+ if (e.type !== 'blur') {
94
+ setSelectedDate(parseDate(value));
95
+ }
92
96
  };
93
97
  const handleDateSelected = (dateString, _momentDate, e) => {
94
98
  const formattedDate = new Date(dateString).toLocaleDateString(getLocale(), {
@@ -98,23 +102,24 @@ const DateInput2 = ({
98
102
  timeZone: getTimezone()
99
103
  });
100
104
  handleInputChange(e, formattedDate);
105
+ setSelectedDate(dateString);
101
106
  setShowPopover(false);
102
- onRequestValidateDate === null || onRequestValidateDate === void 0 ? void 0 : onRequestValidateDate(formattedDate, true);
107
+ onRequestValidateDate === null || onRequestValidateDate === void 0 ? void 0 : onRequestValidateDate(dateString, true);
103
108
  };
109
+
110
+ // onlyRemoveError is used to remove the error msg immediately when the user inputs a valid date (and don't wait for blur event)
104
111
  const validateInput = (onlyRemoveError = false) => {
105
- // TODO `isValidDate` and `isValidMomentDate` basically have the same functionality but the latter is a bit more strict (e.g.: `33` is only valid in `isValidMomentDate`)
106
- // in the future we should get rid of moment but currently Calendar is using it for validation too so we can only remove it simultaneously
107
- // otherwise DateInput could pass invalid dates to Calendar and break it
108
- if (isValidDate(value || '') && isValidMomentDate(value || '', getLocale(), getTimezone()) || value === '') {
109
- setSelectedDate(value || '');
112
+ // don't validate empty input
113
+ if (!value || parseDate(value) || selectedDate) {
110
114
  setInputMessages(messages || []);
111
115
  return true;
112
116
  }
113
- if (!onlyRemoveError && typeof invalidDateErrorMessage === 'string') {
114
- setInputMessages(messages => [{
117
+ // only show error if there is no user provided validation callback
118
+ if (!onlyRemoveError && typeof invalidDateErrorMessage === 'string' && !onRequestValidateDate) {
119
+ setInputMessages([{
115
120
  type: 'error',
116
121
  text: invalidDateErrorMessage
117
- }, ...messages]);
122
+ }]);
118
123
  }
119
124
  return false;
120
125
  };
@@ -137,8 +142,8 @@ const DateInput2 = ({
137
142
  };
138
143
  const handleBlur = e => {
139
144
  const isInputValid = validateInput(false);
140
- if (isInputValid && value) {
141
- const formattedDate = new Date(value).toLocaleDateString(getLocale(), {
145
+ if (isInputValid && selectedDate) {
146
+ const formattedDate = new Date(selectedDate).toLocaleDateString(getLocale(), {
142
147
  month: 'long',
143
148
  year: 'numeric',
144
149
  day: 'numeric',
@@ -7,7 +7,6 @@ Object.defineProperty(exports, "__esModule", {
7
7
  exports.default = exports.DateInput2 = void 0;
8
8
  var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
9
9
  var _react = require("react");
10
- var _momentTimezone = _interopRequireDefault(require("moment-timezone"));
11
10
  var _Calendar = require("@instructure/ui-calendar/lib/Calendar");
12
11
  var _IconButton = require("@instructure/ui-buttons/lib/IconButton");
13
12
  var _IconCalendarMonthLine = require("@instructure/ui-icons/lib/IconCalendarMonthLine.js");
@@ -45,11 +44,10 @@ var _IconCalendarMonthLin, _IconArrowOpenEndSoli, _IconArrowOpenStartSo;
45
44
  * SOFTWARE.
46
45
  */
47
46
  /** @jsx jsx */
48
- function isValidDate(dateString) {
49
- return !isNaN(new Date(dateString).getTime());
50
- }
51
- function isValidMomentDate(dateString, locale, timezone) {
52
- return _momentTimezone.default.tz(dateString, [_momentTimezone.default.ISO_8601, 'llll', 'LLLL', 'lll', 'LLL', 'll', 'LL', 'l', 'L'], locale, true, timezone).isValid();
47
+ function parseDate(dateString) {
48
+ const date = new Date(dateString);
49
+ // return empty string if not a valid date
50
+ return isNaN(date.getTime()) ? '' : date.toISOString();
53
51
  }
54
52
 
55
53
  /**
@@ -91,6 +89,8 @@ const DateInput2 = ({
91
89
  setShowPopover = _useState6[1];
92
90
  const localeContext = (0, _react.useContext)(_ApplyLocaleContext.ApplyLocaleContext);
93
91
  (0, _react.useEffect)(() => {
92
+ // when `value` is changed, validation runs again and removes the error message if validation passes
93
+ // but it's NOT adding error message if validation fails for better UX
94
94
  validateInput(true);
95
95
  }, [value]);
96
96
  (0, _react.useEffect)(() => {
@@ -98,6 +98,10 @@ const DateInput2 = ({
98
98
  }, [messages]);
99
99
  const handleInputChange = (e, value) => {
100
100
  onChange === null || onChange === void 0 ? void 0 : onChange(e, value);
101
+ // blur event formats the input which should trigger parsing
102
+ if (e.type !== 'blur') {
103
+ setSelectedDate(parseDate(value));
104
+ }
101
105
  };
102
106
  const handleDateSelected = (dateString, _momentDate, e) => {
103
107
  const formattedDate = new Date(dateString).toLocaleDateString(getLocale(), {
@@ -107,23 +111,24 @@ const DateInput2 = ({
107
111
  timeZone: getTimezone()
108
112
  });
109
113
  handleInputChange(e, formattedDate);
114
+ setSelectedDate(dateString);
110
115
  setShowPopover(false);
111
- onRequestValidateDate === null || onRequestValidateDate === void 0 ? void 0 : onRequestValidateDate(formattedDate, true);
116
+ onRequestValidateDate === null || onRequestValidateDate === void 0 ? void 0 : onRequestValidateDate(dateString, true);
112
117
  };
118
+
119
+ // onlyRemoveError is used to remove the error msg immediately when the user inputs a valid date (and don't wait for blur event)
113
120
  const validateInput = (onlyRemoveError = false) => {
114
- // TODO `isValidDate` and `isValidMomentDate` basically have the same functionality but the latter is a bit more strict (e.g.: `33` is only valid in `isValidMomentDate`)
115
- // in the future we should get rid of moment but currently Calendar is using it for validation too so we can only remove it simultaneously
116
- // otherwise DateInput could pass invalid dates to Calendar and break it
117
- if (isValidDate(value || '') && isValidMomentDate(value || '', getLocale(), getTimezone()) || value === '') {
118
- setSelectedDate(value || '');
121
+ // don't validate empty input
122
+ if (!value || parseDate(value) || selectedDate) {
119
123
  setInputMessages(messages || []);
120
124
  return true;
121
125
  }
122
- if (!onlyRemoveError && typeof invalidDateErrorMessage === 'string') {
123
- setInputMessages(messages => [{
126
+ // only show error if there is no user provided validation callback
127
+ if (!onlyRemoveError && typeof invalidDateErrorMessage === 'string' && !onRequestValidateDate) {
128
+ setInputMessages([{
124
129
  type: 'error',
125
130
  text: invalidDateErrorMessage
126
- }, ...messages]);
131
+ }]);
127
132
  }
128
133
  return false;
129
134
  };
@@ -146,8 +151,8 @@ const DateInput2 = ({
146
151
  };
147
152
  const handleBlur = e => {
148
153
  const isInputValid = validateInput(false);
149
- if (isInputValid && value) {
150
- const formattedDate = new Date(value).toLocaleDateString(getLocale(), {
154
+ if (isInputValid && selectedDate) {
155
+ const formattedDate = new Date(selectedDate).toLocaleDateString(getLocale(), {
151
156
  month: 'long',
152
157
  year: 'numeric',
153
158
  day: 'numeric',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instructure/ui-date-input",
3
- "version": "9.5.2-snapshot-7",
3
+ "version": "9.5.2",
4
4
  "description": "A UI component library made by Instructure Inc.",
5
5
  "author": "Instructure, Inc. Engineering and Product Design",
6
6
  "module": "./es/index.js",
@@ -23,11 +23,11 @@
23
23
  },
24
24
  "license": "MIT",
25
25
  "devDependencies": {
26
- "@instructure/ui-axe-check": "9.5.2-snapshot-7",
27
- "@instructure/ui-babel-preset": "9.5.2-snapshot-7",
28
- "@instructure/ui-buttons": "9.5.2-snapshot-7",
29
- "@instructure/ui-scripts": "9.5.2-snapshot-7",
30
- "@instructure/ui-test-utils": "9.5.2-snapshot-7",
26
+ "@instructure/ui-axe-check": "9.5.2",
27
+ "@instructure/ui-babel-preset": "9.5.2",
28
+ "@instructure/ui-buttons": "9.5.2",
29
+ "@instructure/ui-scripts": "9.5.2",
30
+ "@instructure/ui-test-utils": "9.5.2",
31
31
  "@testing-library/jest-dom": "^6.4.6",
32
32
  "@testing-library/react": "^15.0.7",
33
33
  "@testing-library/user-event": "^14.5.2",
@@ -35,20 +35,20 @@
35
35
  },
36
36
  "dependencies": {
37
37
  "@babel/runtime": "^7.24.5",
38
- "@instructure/emotion": "9.5.2-snapshot-7",
39
- "@instructure/shared-types": "9.5.2-snapshot-7",
40
- "@instructure/ui-calendar": "9.5.2-snapshot-7",
41
- "@instructure/ui-form-field": "9.5.2-snapshot-7",
42
- "@instructure/ui-i18n": "9.5.2-snapshot-7",
43
- "@instructure/ui-icons": "9.5.2-snapshot-7",
44
- "@instructure/ui-popover": "9.5.2-snapshot-7",
45
- "@instructure/ui-position": "9.5.2-snapshot-7",
46
- "@instructure/ui-prop-types": "9.5.2-snapshot-7",
47
- "@instructure/ui-react-utils": "9.5.2-snapshot-7",
48
- "@instructure/ui-selectable": "9.5.2-snapshot-7",
49
- "@instructure/ui-testable": "9.5.2-snapshot-7",
50
- "@instructure/ui-text-input": "9.5.2-snapshot-7",
51
- "@instructure/ui-utils": "9.5.2-snapshot-7",
38
+ "@instructure/emotion": "9.5.2",
39
+ "@instructure/shared-types": "9.5.2",
40
+ "@instructure/ui-calendar": "9.5.2",
41
+ "@instructure/ui-form-field": "9.5.2",
42
+ "@instructure/ui-i18n": "9.5.2",
43
+ "@instructure/ui-icons": "9.5.2",
44
+ "@instructure/ui-popover": "9.5.2",
45
+ "@instructure/ui-position": "9.5.2",
46
+ "@instructure/ui-prop-types": "9.5.2",
47
+ "@instructure/ui-react-utils": "9.5.2",
48
+ "@instructure/ui-selectable": "9.5.2",
49
+ "@instructure/ui-testable": "9.5.2",
50
+ "@instructure/ui-text-input": "9.5.2",
51
+ "@instructure/ui-utils": "9.5.2",
52
52
  "moment-timezone": "^0.5.45",
53
53
  "prop-types": "^15.8.1"
54
54
  },
@@ -25,7 +25,6 @@
25
25
  /** @jsx jsx */
26
26
  import { useState, useEffect, useContext } from 'react'
27
27
  import type { SyntheticEvent } from 'react'
28
- import moment from 'moment-timezone'
29
28
  import { Calendar } from '@instructure/ui-calendar'
30
29
  import { IconButton } from '@instructure/ui-buttons'
31
30
  import {
@@ -43,25 +42,12 @@ import { jsx } from '@instructure/emotion'
43
42
  import { propTypes } from './props'
44
43
  import type { DateInput2Props } from './props'
45
44
  import type { FormMessage } from '@instructure/ui-form-field'
45
+ import type { Moment } from '@instructure/ui-i18n'
46
46
 
47
- function isValidDate(dateString: string): boolean {
48
- return !isNaN(new Date(dateString).getTime())
49
- }
50
-
51
- function isValidMomentDate(
52
- dateString: string,
53
- locale: string,
54
- timezone: string
55
- ): boolean {
56
- return moment
57
- .tz(
58
- dateString,
59
- [moment.ISO_8601, 'llll', 'LLLL', 'lll', 'LLL', 'll', 'LL', 'l', 'L'],
60
- locale,
61
- true,
62
- timezone
63
- )
64
- .isValid()
47
+ function parseDate(dateString: string): string {
48
+ const date = new Date(dateString)
49
+ // return empty string if not a valid date
50
+ return isNaN(date.getTime()) ? '' : date.toISOString()
65
51
  }
66
52
 
67
53
  /**
@@ -97,6 +83,8 @@ const DateInput2 = ({
97
83
  const localeContext = useContext(ApplyLocaleContext)
98
84
 
99
85
  useEffect(() => {
86
+ // when `value` is changed, validation runs again and removes the error message if validation passes
87
+ // but it's NOT adding error message if validation fails for better UX
100
88
  validateInput(true)
101
89
  }, [value])
102
90
 
@@ -106,11 +94,15 @@ const DateInput2 = ({
106
94
 
107
95
  const handleInputChange = (e: SyntheticEvent, value: string) => {
108
96
  onChange?.(e, value)
97
+ // blur event formats the input which should trigger parsing
98
+ if (e.type !== 'blur') {
99
+ setSelectedDate(parseDate(value))
100
+ }
109
101
  }
110
102
 
111
103
  const handleDateSelected = (
112
104
  dateString: string,
113
- _momentDate: any, // real type is Moment but used `any` to avoid importing the moment lib
105
+ _momentDate: Moment,
114
106
  e: SyntheticEvent
115
107
  ) => {
116
108
  const formattedDate = new Date(dateString).toLocaleDateString(getLocale(), {
@@ -120,30 +112,29 @@ const DateInput2 = ({
120
112
  timeZone: getTimezone()
121
113
  })
122
114
  handleInputChange(e, formattedDate)
115
+ setSelectedDate(dateString)
123
116
  setShowPopover(false)
124
- onRequestValidateDate?.(formattedDate, true)
117
+ onRequestValidateDate?.(dateString, true)
125
118
  }
126
119
 
120
+ // onlyRemoveError is used to remove the error msg immediately when the user inputs a valid date (and don't wait for blur event)
127
121
  const validateInput = (onlyRemoveError = false): boolean => {
128
- // TODO `isValidDate` and `isValidMomentDate` basically have the same functionality but the latter is a bit more strict (e.g.: `33` is only valid in `isValidMomentDate`)
129
- // in the future we should get rid of moment but currently Calendar is using it for validation too so we can only remove it simultaneously
130
- // otherwise DateInput could pass invalid dates to Calendar and break it
131
- if (
132
- (isValidDate(value || '') &&
133
- isValidMomentDate(value || '', getLocale(), getTimezone())) ||
134
- value === ''
135
- ) {
136
- setSelectedDate(value || '')
122
+ // don't validate empty input
123
+ if (!value || parseDate(value) || selectedDate) {
137
124
  setInputMessages(messages || [])
138
125
  return true
139
126
  }
140
- if (!onlyRemoveError && typeof invalidDateErrorMessage === 'string') {
141
- setInputMessages((messages) => [
127
+ // only show error if there is no user provided validation callback
128
+ if (
129
+ !onlyRemoveError &&
130
+ typeof invalidDateErrorMessage === 'string' &&
131
+ !onRequestValidateDate
132
+ ) {
133
+ setInputMessages([
142
134
  {
143
135
  type: 'error',
144
136
  text: invalidDateErrorMessage
145
- },
146
- ...messages
137
+ }
147
138
  ])
148
139
  }
149
140
 
@@ -171,13 +162,16 @@ const DateInput2 = ({
171
162
 
172
163
  const handleBlur = (e: SyntheticEvent) => {
173
164
  const isInputValid = validateInput(false)
174
- if (isInputValid && value) {
175
- const formattedDate = new Date(value).toLocaleDateString(getLocale(), {
176
- month: 'long',
177
- year: 'numeric',
178
- day: 'numeric',
179
- timeZone: getTimezone()
180
- })
165
+ if (isInputValid && selectedDate) {
166
+ const formattedDate = new Date(selectedDate).toLocaleDateString(
167
+ getLocale(),
168
+ {
169
+ month: 'long',
170
+ year: 'numeric',
171
+ day: 'numeric',
172
+ timeZone: getTimezone()
173
+ }
174
+ )
181
175
  handleInputChange(e, formattedDate)
182
176
  }
183
177
  onRequestValidateDate?.(value, isInputValid)
@@ -23,14 +23,14 @@
23
23
  */
24
24
 
25
25
  import PropTypes from 'prop-types'
26
- import type { SyntheticEvent } from 'react'
26
+ import type { SyntheticEvent, InputHTMLAttributes } from 'react'
27
27
 
28
28
  import { controllable } from '@instructure/ui-prop-types'
29
29
  import { FormPropTypes } from '@instructure/ui-form-field'
30
30
  import type { FormMessage } from '@instructure/ui-form-field'
31
- import type { Renderable, PropValidators } from '@instructure/shared-types'
31
+ import type { OtherHTMLAttributes, Renderable, PropValidators } from '@instructure/shared-types'
32
32
 
33
- type DateInput2Props = {
33
+ type DateInput2OwnProps = {
34
34
  /**
35
35
  * Specifies the input label.
36
36
  */
@@ -159,7 +159,13 @@ type DateInput2Props = {
159
159
  }
160
160
  }
161
161
 
162
- type PropKeys = keyof DateInput2Props
162
+ type PropKeys = keyof DateInput2OwnProps
163
+
164
+ type DateInput2Props = DateInput2OwnProps &
165
+ OtherHTMLAttributes<
166
+ DateInput2OwnProps,
167
+ InputHTMLAttributes<DateInput2OwnProps & Element>
168
+ >
163
169
 
164
170
  const propTypes: PropValidators<PropKeys> = {
165
171
  renderLabel: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired,
package/src/index.ts CHANGED
@@ -25,3 +25,4 @@
25
25
  export { DateInput } from './DateInput'
26
26
  export { DateInput2 } from './DateInput2'
27
27
  export type { DateInputProps } from './DateInput/props'
28
+ export type { DateInput2Props } from './DateInput2/props'