@atlaskit/editor-plugin-date 0.1.0 → 0.2.0

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.
Files changed (76) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/cjs/actions.js +105 -0
  3. package/dist/cjs/commands.js +114 -1
  4. package/dist/cjs/index.js +9 -1
  5. package/dist/cjs/nodeviews/date.js +44 -0
  6. package/dist/cjs/plugin.js +197 -0
  7. package/dist/cjs/pm-plugins/keymap.js +41 -0
  8. package/dist/cjs/pm-plugins/main.js +39 -0
  9. package/dist/cjs/pm-plugins/plugin-key.js +8 -0
  10. package/dist/cjs/pm-plugins/types.js +5 -0
  11. package/dist/cjs/pm-plugins/utils.js +70 -0
  12. package/dist/cjs/ui/DatePicker/date-picker-input.js +253 -0
  13. package/dist/cjs/ui/DatePicker/index.js +170 -0
  14. package/dist/cjs/utils/formatParse.js +88 -0
  15. package/dist/cjs/utils/internal.js +176 -0
  16. package/dist/es2019/actions.js +93 -0
  17. package/dist/es2019/commands.js +113 -1
  18. package/dist/es2019/index.js +1 -1
  19. package/dist/es2019/nodeviews/date.js +47 -0
  20. package/dist/es2019/plugin.js +183 -0
  21. package/dist/es2019/pm-plugins/keymap.js +34 -0
  22. package/dist/es2019/pm-plugins/main.js +35 -0
  23. package/dist/es2019/pm-plugins/plugin-key.js +2 -0
  24. package/dist/es2019/pm-plugins/types.js +1 -0
  25. package/dist/es2019/pm-plugins/utils.js +70 -0
  26. package/dist/es2019/ui/DatePicker/date-picker-input.js +242 -0
  27. package/dist/es2019/ui/DatePicker/index.js +154 -0
  28. package/dist/es2019/utils/formatParse.js +83 -0
  29. package/dist/es2019/utils/internal.js +151 -0
  30. package/dist/esm/actions.js +99 -0
  31. package/dist/esm/commands.js +112 -1
  32. package/dist/esm/index.js +1 -1
  33. package/dist/esm/nodeviews/date.js +37 -0
  34. package/dist/esm/plugin.js +185 -0
  35. package/dist/esm/pm-plugins/keymap.js +34 -0
  36. package/dist/esm/pm-plugins/main.js +34 -0
  37. package/dist/esm/pm-plugins/plugin-key.js +2 -0
  38. package/dist/esm/pm-plugins/types.js +1 -0
  39. package/dist/esm/pm-plugins/utils.js +61 -0
  40. package/dist/esm/ui/DatePicker/date-picker-input.js +247 -0
  41. package/dist/esm/ui/DatePicker/index.js +164 -0
  42. package/dist/esm/utils/formatParse.js +79 -0
  43. package/dist/esm/utils/internal.js +165 -0
  44. package/dist/types/actions.d.ts +49 -0
  45. package/dist/types/commands.d.ts +12 -10
  46. package/dist/types/index.d.ts +1 -1
  47. package/dist/types/nodeviews/date.d.ts +3 -0
  48. package/dist/types/plugin.d.ts +3 -0
  49. package/dist/types/pm-plugins/keymap.d.ts +3 -0
  50. package/dist/types/pm-plugins/main.d.ts +6 -0
  51. package/dist/types/pm-plugins/plugin-key.d.ts +3 -0
  52. package/dist/types/pm-plugins/types.d.ts +12 -0
  53. package/dist/types/pm-plugins/utils.d.ts +5 -0
  54. package/dist/types/types.d.ts +10 -2
  55. package/dist/types/ui/DatePicker/date-picker-input.d.ts +25 -0
  56. package/dist/types/ui/DatePicker/index.d.ts +36 -0
  57. package/dist/types/utils/formatParse.d.ts +27 -0
  58. package/dist/types/utils/internal.d.ts +32 -0
  59. package/dist/types-ts4.5/actions.d.ts +60 -0
  60. package/dist/types-ts4.5/commands.d.ts +14 -10
  61. package/dist/types-ts4.5/index.d.ts +1 -1
  62. package/dist/types-ts4.5/nodeviews/date.d.ts +3 -0
  63. package/dist/types-ts4.5/plugin.d.ts +3 -0
  64. package/dist/types-ts4.5/pm-plugins/keymap.d.ts +3 -0
  65. package/dist/types-ts4.5/pm-plugins/main.d.ts +6 -0
  66. package/dist/types-ts4.5/pm-plugins/plugin-key.d.ts +3 -0
  67. package/dist/types-ts4.5/pm-plugins/types.d.ts +12 -0
  68. package/dist/types-ts4.5/pm-plugins/utils.d.ts +5 -0
  69. package/dist/types-ts4.5/types.d.ts +10 -2
  70. package/dist/types-ts4.5/ui/DatePicker/date-picker-input.d.ts +25 -0
  71. package/dist/types-ts4.5/ui/DatePicker/index.d.ts +36 -0
  72. package/dist/types-ts4.5/utils/formatParse.d.ts +27 -0
  73. package/dist/types-ts4.5/utils/internal.d.ts +32 -0
  74. package/package.json +29 -4
  75. package/report.api.md +5 -2
  76. package/tmp/api-report-tmp.d.ts +5 -2
@@ -0,0 +1,242 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ /** @jsx jsx */
3
+ import React from 'react';
4
+ import { css, jsx } from '@emotion/react';
5
+ import { defineMessages, injectIntl } from 'react-intl-next';
6
+ import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
7
+ import { ErrorMessage } from '@atlaskit/form';
8
+ import TextField from '@atlaskit/textfield';
9
+ import { formatDateType, parseDateType } from '../../utils/formatParse';
10
+ import { adjustDate, findDateSegmentByPosition, isDatePossiblyValid } from '../../utils/internal';
11
+
12
+ // eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage
13
+ const dateTextFieldWrapper = css`
14
+ padding: 22px;
15
+ padding-bottom: ${"var(--ds-space-150, 12px)"};
16
+ `;
17
+ const messages = defineMessages({
18
+ invalidDateError: {
19
+ id: 'fabric.editor.invalidDateError',
20
+ defaultMessage: 'Enter a valid date',
21
+ description: 'Error message when the date typed in is invalid, requesting they inputs a new date'
22
+ }
23
+ });
24
+
25
+ // eslint-disable-next-line @repo/internal/react/no-class-components
26
+ class DatePickerInput extends React.Component {
27
+ constructor(props) {
28
+ super(props);
29
+ /**
30
+ * Focus the input textfield
31
+ */
32
+ _defineProperty(this, "focusInput", () => {
33
+ if (!this.inputRef) {
34
+ return;
35
+ }
36
+ // Defer to prevent editor scrolling to top (See FS-3227, also ED-2992)
37
+ this.autofocusTimeout = setTimeout(() => {
38
+ var _this$inputRef;
39
+ (_this$inputRef = this.inputRef) === null || _this$inputRef === void 0 ? void 0 : _this$inputRef.focus();
40
+ });
41
+ });
42
+ /**
43
+ * Select all the input text
44
+ */
45
+ _defineProperty(this, "selectInput", () => {
46
+ if (!this.inputRef) {
47
+ return;
48
+ }
49
+ // Defer to prevent editor scrolling to top (See FS-3227, also ED-2992)
50
+ this.autoSelectAllTimeout = setTimeout(() => {
51
+ var _this$inputRef2;
52
+ (_this$inputRef2 = this.inputRef) === null || _this$inputRef2 === void 0 ? void 0 : _this$inputRef2.select();
53
+ });
54
+ });
55
+ _defineProperty(this, "handleInputRef", ref => {
56
+ const {
57
+ autoFocus,
58
+ autoSelectAll
59
+ } = this.props;
60
+ if (ref) {
61
+ this.inputRef = ref;
62
+ }
63
+ if (ref && autoFocus) {
64
+ this.focusInput();
65
+ }
66
+ if (autoSelectAll) {
67
+ this.selectInput();
68
+ }
69
+ });
70
+ _defineProperty(this, "handleChange", evt => {
71
+ const textFieldValue = evt.target.value;
72
+ const {
73
+ locale,
74
+ dispatchAnalyticsEvent
75
+ } = this.props;
76
+ const newDate = parseDateType(textFieldValue, locale);
77
+ if (newDate !== undefined && newDate !== null) {
78
+ this.setState({
79
+ inputText: textFieldValue
80
+ });
81
+ this.props.onNewDate(newDate);
82
+ if (dispatchAnalyticsEvent) {
83
+ dispatchAnalyticsEvent({
84
+ eventType: EVENT_TYPE.TRACK,
85
+ action: ACTION.TYPING_FINISHED,
86
+ actionSubject: ACTION_SUBJECT.DATE
87
+ });
88
+ }
89
+ } else {
90
+ // if invalid, just update state text (to rerender textfield)
91
+ this.setState({
92
+ inputText: textFieldValue
93
+ });
94
+ }
95
+ });
96
+ _defineProperty(this, "handleKeyPress", event => {
97
+ const {
98
+ locale,
99
+ dispatchAnalyticsEvent
100
+ } = this.props;
101
+ const textFieldValue = event.target.value;
102
+
103
+ // Fire event on every keypress (textfield not necessarily empty)
104
+ if (dispatchAnalyticsEvent && event.key !== 'Enter' && event.key !== 'Backspace') {
105
+ dispatchAnalyticsEvent({
106
+ eventType: EVENT_TYPE.TRACK,
107
+ action: ACTION.TYPING_STARTED,
108
+ actionSubject: ACTION_SUBJECT.DATE
109
+ });
110
+ }
111
+ if (event.key !== 'Enter') {
112
+ return;
113
+ }
114
+ if (textFieldValue === '') {
115
+ this.props.onEmptySubmit();
116
+ return;
117
+ }
118
+ const newDate = parseDateType(textFieldValue, locale);
119
+ this.props.onSubmitDate(newDate);
120
+ });
121
+ // arrow keys are only triggered by onKeyDown, not onKeyPress
122
+ _defineProperty(this, "handleKeyDown", event => {
123
+ var _this$inputRef3;
124
+ const dateString = event.target.value;
125
+ const {
126
+ locale
127
+ } = this.props;
128
+ let adjustment;
129
+ if (event.key === 'ArrowUp') {
130
+ adjustment = 1;
131
+ } else if (event.key === 'ArrowDown') {
132
+ adjustment = -1;
133
+ }
134
+ if (adjustment === undefined) {
135
+ return;
136
+ }
137
+ const {
138
+ dispatchAnalyticsEvent
139
+ } = this.props;
140
+ const cursorPos = (_this$inputRef3 = this.inputRef) === null || _this$inputRef3 === void 0 ? void 0 : _this$inputRef3.selectionStart;
141
+ if (cursorPos === null || cursorPos === undefined) {
142
+ return;
143
+ }
144
+ const activeSegment = findDateSegmentByPosition(cursorPos, dateString, locale);
145
+ if (activeSegment === undefined) {
146
+ return;
147
+ }
148
+ let dateSegment;
149
+ switch (activeSegment) {
150
+ case 'day':
151
+ dateSegment = ACTION_SUBJECT_ID.DATE_DAY;
152
+ break;
153
+ case 'month':
154
+ dateSegment = ACTION_SUBJECT_ID.DATE_MONTH;
155
+ break;
156
+ default:
157
+ dateSegment = ACTION_SUBJECT_ID.DATE_YEAR;
158
+ }
159
+ if (dispatchAnalyticsEvent) {
160
+ dispatchAnalyticsEvent({
161
+ eventType: EVENT_TYPE.TRACK,
162
+ action: adjustment > 0 ? ACTION.INCREMENTED : ACTION.DECREMENTED,
163
+ actionSubject: ACTION_SUBJECT.DATE_SEGMENT,
164
+ attributes: {
165
+ dateSegment
166
+ }
167
+ });
168
+ }
169
+ const oldDateType = parseDateType(dateString, locale);
170
+ if (oldDateType === undefined || oldDateType === null) {
171
+ return;
172
+ }
173
+ const newDateType = adjustDate(oldDateType, activeSegment, adjustment);
174
+ this.setState({
175
+ inputText: formatDateType(newDateType, locale)
176
+ });
177
+ this.props.onNewDate(newDateType);
178
+ this.setInputSelectionPos = Math.min(cursorPos, dateString.length);
179
+ event.preventDefault();
180
+ });
181
+ const {
182
+ date
183
+ } = props;
184
+ this.setInputSelectionPos = undefined;
185
+ const inputText = formatDateType(date, this.props.locale);
186
+ this.state = {
187
+ inputText
188
+ };
189
+ }
190
+ render() {
191
+ const {
192
+ locale,
193
+ intl: {
194
+ formatMessage
195
+ }
196
+ } = this.props;
197
+ const {
198
+ inputText
199
+ } = this.state;
200
+ const possiblyValid = isDatePossiblyValid(inputText);
201
+ const attemptedDateParse = parseDateType(inputText, locale);
202
+
203
+ // Don't display an error for an empty input.
204
+ const displayError = (attemptedDateParse === null || !possiblyValid) && inputText !== '';
205
+ return jsx("div", {
206
+ css: dateTextFieldWrapper
207
+ }, jsx(TextField, {
208
+ name: "datetextfield",
209
+ value: inputText,
210
+ ref: this.handleInputRef,
211
+ onChange: this.handleChange,
212
+ onKeyPress: this.handleKeyPress,
213
+ onKeyDown: this.handleKeyDown,
214
+ spellCheck: false,
215
+ autoComplete: "off",
216
+ isInvalid: displayError
217
+ }), displayError && jsx(ErrorMessage, null, formatMessage(messages.invalidDateError)));
218
+ }
219
+ componentDidUpdate() {
220
+ const setInputSelectionPos = this.setInputSelectionPos;
221
+ if (this.inputRef && setInputSelectionPos !== undefined) {
222
+ this.inputRef.setSelectionRange(setInputSelectionPos, setInputSelectionPos);
223
+ this.setInputSelectionPos = undefined;
224
+ }
225
+ if (this.inputRef && this.props.autoFocus) {
226
+ // TODO: Check if input already has focus
227
+ this.focusInput();
228
+ }
229
+
230
+ // Don't select all text here - would seleect text on each keystroke
231
+ }
232
+
233
+ componentWillUnmount() {
234
+ if (this.autofocusTimeout !== undefined) {
235
+ clearTimeout(this.autofocusTimeout);
236
+ }
237
+ if (this.autoSelectAllTimeout !== undefined) {
238
+ clearTimeout(this.autoSelectAllTimeout);
239
+ }
240
+ }
241
+ }
242
+ export default injectIntl(DatePickerInput);
@@ -0,0 +1,154 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ /** @jsx jsx */
3
+ import React from 'react';
4
+ import { css, jsx } from '@emotion/react';
5
+ import ReactDOM from 'react-dom';
6
+ import { injectIntl } from 'react-intl-next';
7
+ import Calendar from '@atlaskit/calendar';
8
+ import { INPUT_METHOD } from '@atlaskit/editor-common/analytics';
9
+ import { Popup, withOuterListeners } from '@atlaskit/editor-common/ui';
10
+ import { timestampToIsoFormat, timestampToUTCDate } from '@atlaskit/editor-common/utils';
11
+ import { akEditorFloatingDialogZIndex } from '@atlaskit/editor-shared-styles';
12
+ import { N0, N60A } from '@atlaskit/theme/colors';
13
+ import { borderRadius } from '@atlaskit/theme/constants';
14
+ const PopupWithListeners = withOuterListeners(Popup);
15
+ import DatePickerInput from './date-picker-input';
16
+
17
+ // eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage
18
+ const popupContentWrapper = css`
19
+ padding: ${"var(--ds-space-025, 2px)"};
20
+ border-radius: ${borderRadius()}px;
21
+ box-shadow: ${`var(--ds-shadow-overlay, ${`0 4px 8px -2px ${N60A}, 0 0 1px ${N60A}`})`};
22
+ background-color: ${`var(--ds-surface-overlay, ${N0})`};
23
+ `;
24
+ // eslint-disable-next-line @repo/internal/react/no-class-components
25
+ class DatePicker extends React.Component {
26
+ constructor(props) {
27
+ super(props);
28
+ _defineProperty(this, "handleNewDate", date => {
29
+ this.props.onTextChanged(date);
30
+ this.setState({
31
+ latestValidDate: date
32
+ });
33
+ });
34
+ _defineProperty(this, "handleKeyboardSubmitDate", date => {
35
+ this.props.onSelect(date, INPUT_METHOD.KEYBOARD);
36
+ });
37
+ _defineProperty(this, "handleEmptySubmitDate", () => {
38
+ this.props.onDelete();
39
+ });
40
+ _defineProperty(this, "handleOnChange", ({
41
+ day,
42
+ month,
43
+ year
44
+ }) => {
45
+ const date = {
46
+ day,
47
+ month,
48
+ year
49
+ };
50
+ this.setState({
51
+ latestValidDate: date
52
+ });
53
+ });
54
+ _defineProperty(this, "closeDatePickerWithAnalytics", () => {
55
+ this.props.closeDatePickerWithAnalytics({
56
+ date: this.state.latestValidDate
57
+ });
58
+ });
59
+ _defineProperty(this, "handleRef", ref => {
60
+ const elm = ref && ReactDOM.findDOMNode(ref);
61
+ if (elm) {
62
+ elm.focus();
63
+ }
64
+ });
65
+ const timestamp = props.element.getAttribute('timestamp');
66
+ if (timestamp) {
67
+ // Warning: The 'Date' return type of timestampToUTCDate() is not a JS date, it's more similar
68
+ // to the DateType type
69
+ const {
70
+ day,
71
+ month,
72
+ year
73
+ } = timestampToUTCDate(timestamp);
74
+ const date = {
75
+ day,
76
+ month,
77
+ year
78
+ };
79
+ this.state = {
80
+ selected: [timestampToIsoFormat(timestamp)],
81
+ date,
82
+ latestValidDate: date
83
+ };
84
+ }
85
+ }
86
+ render() {
87
+ const {
88
+ element,
89
+ onSelect,
90
+ mountTo,
91
+ boundariesElement,
92
+ scrollableElement,
93
+ intl,
94
+ dispatchAnalyticsEvent,
95
+ isNew,
96
+ autoFocus,
97
+ weekStartDay
98
+ } = this.props;
99
+ const timestamp = element.getAttribute('timestamp');
100
+ if (this.state === null) {
101
+ // Without this, you can blow up the page by slowing down cpu, opening date, typing after date
102
+ // then clicking on date lozenge and typing quickly before it opens
103
+ return null;
104
+ }
105
+ const {
106
+ date,
107
+ selected,
108
+ latestValidDate
109
+ } = this.state;
110
+ const {
111
+ day,
112
+ month,
113
+ year
114
+ } = latestValidDate;
115
+ if (!timestamp) {
116
+ return null;
117
+ }
118
+ return jsx(PopupWithListeners, {
119
+ target: element,
120
+ offset: [0, 8],
121
+ fitHeight: 327,
122
+ fitWidth: 340,
123
+ handleClickOutside: this.closeDatePickerWithAnalytics,
124
+ handleEscapeKeydown: this.closeDatePickerWithAnalytics,
125
+ zIndex: akEditorFloatingDialogZIndex,
126
+ mountTo: mountTo,
127
+ boundariesElement: boundariesElement,
128
+ scrollableElement: scrollableElement,
129
+ ariaLabel: null
130
+ }, jsx("div", {
131
+ css: popupContentWrapper
132
+ }, jsx(DatePickerInput, {
133
+ date: date,
134
+ onNewDate: this.handleNewDate,
135
+ onSubmitDate: this.handleKeyboardSubmitDate,
136
+ onEmptySubmit: this.handleEmptySubmitDate,
137
+ locale: intl.locale,
138
+ dispatchAnalyticsEvent: dispatchAnalyticsEvent,
139
+ autoFocus: autoFocus,
140
+ autoSelectAll: isNew
141
+ }), jsx(Calendar, {
142
+ onChange: this.handleOnChange,
143
+ onSelect: date => onSelect(date, INPUT_METHOD.PICKER),
144
+ day: day,
145
+ month: month,
146
+ year: year,
147
+ selected: selected,
148
+ ref: this.handleRef,
149
+ weekStartDay: weekStartDay,
150
+ testId: 'datepicker'
151
+ })));
152
+ }
153
+ }
154
+ export default injectIntl(DatePicker);
@@ -0,0 +1,83 @@
1
+ import { createLocalizationProvider } from '@atlaskit/locale';
2
+ /**
3
+ * Attempt to parse a string representing a date in a particular locale to a date object
4
+ * @param dateString The string representing the date in the given locale, eg '02/12/2000'
5
+ * @param l10n The localisation provider created by createLocalizationProvider
6
+ * @returns Editor DateType when can parse, null when can't parse or invalid
7
+ */
8
+ export function parseDateType(dateString, locale) {
9
+ try {
10
+ const l10n = createLocalizationProvider(locale);
11
+ const date = l10n.parseDate(dateString);
12
+
13
+ // If date is invalid
14
+ if (isNaN(date.getTime())) {
15
+ return null;
16
+ }
17
+ const year = date.getFullYear();
18
+ if (year < 1000 || year > 9999) {
19
+ return null;
20
+ }
21
+ const dateObj = {
22
+ day: date.getDate(),
23
+ month: date.getMonth() + 1,
24
+ year
25
+ };
26
+ return dateObj;
27
+ } catch (e) {
28
+ return null;
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Convert an EditorDateType to a date string string formatted for a particular locale
34
+ * @param date The date object
35
+ * @param locale The locale code string (eg. "en-AU")
36
+ * @returns Date string, eg "25/5/20"
37
+ */
38
+ export function formatDateType(date, locale) {
39
+ const {
40
+ day,
41
+ month,
42
+ year
43
+ } = date;
44
+ const l10n = createLocalizationProvider(locale);
45
+
46
+ // The JS Date api represents month as a number between 0-11 :)
47
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date
48
+ const dateObj = new Date(year, month - 1, day);
49
+ return l10n.formatDate(dateObj);
50
+ }
51
+
52
+ /**
53
+ * Convert an Editor DateType to a JavaScript Date object
54
+ * @param date Editor DateType
55
+ * @returns JavaScript Date object
56
+ */
57
+ export function dateTypeToDate(date) {
58
+ const {
59
+ day,
60
+ month,
61
+ year
62
+ } = date;
63
+ // The JS Date api represents month as a number between 0-11 :)
64
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date
65
+ const dateObj = new Date(year, month - 1, day);
66
+ return dateObj;
67
+ }
68
+
69
+ /**
70
+ * Convert a JavaScript Date to an editor DateType
71
+ * @param date JavaScript Date object
72
+ * @returns Editor DateType
73
+ */
74
+ export function dateToDateType(date) {
75
+ const dateObj = {
76
+ day: date.getDate(),
77
+ // The JS Date api represents month as a number between 0-11 :)
78
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date
79
+ month: date.getMonth() + 1,
80
+ year: date.getFullYear()
81
+ };
82
+ return dateObj;
83
+ }
@@ -0,0 +1,151 @@
1
+ import addDays from 'date-fns/addDays';
2
+ import addMonths from 'date-fns/addMonths';
3
+ import addYears from 'date-fns/addYears';
4
+ import { dateToDateType, dateTypeToDate, formatDateType } from './formatParse';
5
+ function isDigit(c) {
6
+ if (c === undefined) {
7
+ return false;
8
+ }
9
+ return c >= '0' && c <= '9';
10
+ }
11
+
12
+ /**
13
+ * Check if cursor is in first segment of a date.
14
+ * @param cursorPos Cursor pos, with 0 referring to the left of first char
15
+ * @param date Date string in any locale
16
+ */
17
+ function isCursorInFirstDateSegment(cursorPos, date) {
18
+ let posCounter = cursorPos - 1;
19
+ let isAdjacent = true;
20
+ // The date without any non-digit characters on the end
21
+ const strippedDate = date.replace(/[^0-9]+$/g, '');
22
+ while (posCounter >= 0 && isAdjacent) {
23
+ const c = strippedDate[posCounter];
24
+ if (!isDigit(c)) {
25
+ isAdjacent = false;
26
+ }
27
+ posCounter -= 1;
28
+ }
29
+ return isAdjacent;
30
+ }
31
+
32
+ /**
33
+ * Check if cursor is in last segment of a date.
34
+ * @param cursorPos Cursor pos, with 0 referring to the left of first char
35
+ * @param date Date string in any locale
36
+ */
37
+ function isCursorInLastDateSegment(cursorPos, date) {
38
+ let posCounter = cursorPos;
39
+ let isAdjacent = true;
40
+ // The date without any non-digit characters on the end
41
+ const strippedDate = date.replace(/[^0-9]+$/g, '');
42
+ while (posCounter < strippedDate.length && isAdjacent) {
43
+ const c = strippedDate[posCounter];
44
+ if (!isDigit(c)) {
45
+ isAdjacent = false;
46
+ }
47
+ posCounter += 1;
48
+ }
49
+ return isAdjacent;
50
+ }
51
+
52
+ /**
53
+ * Inconclusively check if a date string is valid - a value of false means it is definitely
54
+ * invalid, a value of true means it might be valid.
55
+ * @param date Date string to be parsed
56
+ */
57
+ export function isDatePossiblyValid(date) {
58
+ for (const c of date) {
59
+ const isNumber = c >= '0' && c <= '9';
60
+ const isValidPunctuation = '. ,/'.indexOf(c) !== -1;
61
+ if (!(isNumber || isValidPunctuation)) {
62
+ return false;
63
+ }
64
+ }
65
+ return true;
66
+ }
67
+
68
+ /**
69
+ * Find the segment of a date a position refers to. Eg: pos 2 in 29/03/2020 is in
70
+ * the day segment.
71
+ * @param position Cursor position, with 0 referring to the left of the first char
72
+ * @param date The localised date string
73
+ * @param locale The language to interpret the date string in
74
+ */
75
+ export function findDateSegmentByPosition(position, date, locale) {
76
+ if (position > date.length) {
77
+ return undefined;
78
+ }
79
+ const placeholder = getLocaleDatePlaceholder(locale);
80
+ if (!placeholder) {
81
+ return undefined;
82
+ }
83
+
84
+ // The placeholder without any non-digit characters on the end
85
+ const strippedPlaceholder = placeholder.replace(/[^ymd]+$/g, '');
86
+ const keyToSegment = {
87
+ d: 'day',
88
+ m: 'month',
89
+ y: 'year'
90
+ };
91
+ const firstSegment = keyToSegment[strippedPlaceholder[0]];
92
+ const lastSegment = keyToSegment[strippedPlaceholder[strippedPlaceholder.length - 1]];
93
+ const allPossibleSegments = ['day', 'month', 'year'];
94
+ const middleSegment = allPossibleSegments.filter(s => s !== firstSegment && s !== lastSegment)[0];
95
+ if (isCursorInFirstDateSegment(position, date)) {
96
+ return firstSegment;
97
+ }
98
+ if (isCursorInLastDateSegment(position, date)) {
99
+ return lastSegment;
100
+ }
101
+ return middleSegment;
102
+ }
103
+
104
+ /**
105
+ * Generate a placeholder date string for a given locale
106
+ * eg: locale 'hu-HU' -> 'yyyy. mm. dd.'
107
+ * @param locale A locale string supported by Intl.DateTimeFormat
108
+ * @returns A placeholder string. d=1 or 2 digit day, dd=zero padded
109
+ * day, same for month but letter m, yyyy=year
110
+ */
111
+ export function getLocaleDatePlaceholder(locale) {
112
+ const uniqueDateType = {
113
+ day: 7,
114
+ month: 1,
115
+ year: 1992
116
+ };
117
+ const localisedDateString = formatDateType(uniqueDateType, locale);
118
+ const shortDateFormat = localisedDateString.replace(/\d+/g, str => {
119
+ if (!str) {
120
+ return '';
121
+ }
122
+ var num = parseInt(str);
123
+ switch (num % 100) {
124
+ case 92:
125
+ return str.replace(/.{1}/g, 'y');
126
+ case 1:
127
+ return str.length === 1 ? 'm' : 'mm';
128
+ case 7:
129
+ return str.length === 1 ? 'd' : 'dd';
130
+ }
131
+ return '';
132
+ });
133
+ return shortDateFormat;
134
+ }
135
+
136
+ /**
137
+ * Adjust date segment up or down. Eg. If day is the active segment and adjustment is -1,
138
+ * reduce the day by one.
139
+ * @param date Valid datetype
140
+ * @param activeSegment which part of the date is selected/being adjusted
141
+ * @param adjustment how many units the segment is being adjusted (can be pos or neg, usually 1 or -1)
142
+ */
143
+ export function adjustDate(date, activeSegment, adjustment) {
144
+ const originalDate = dateTypeToDate(date);
145
+ const newDate = activeSegment === 'day' ? addDays(originalDate, adjustment) : activeSegment === 'month' ? addMonths(originalDate, adjustment) : addYears(originalDate, adjustment);
146
+ return dateToDateType(newDate);
147
+ }
148
+ export function isToday(date) {
149
+ const today = new Date();
150
+ return date !== undefined && today.getDate() === date.day && date.month === today.getMonth() + 1 && date.year === today.getFullYear();
151
+ }