@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.
- package/CHANGELOG.md +10 -0
- package/dist/cjs/actions.js +105 -0
- package/dist/cjs/commands.js +114 -1
- package/dist/cjs/index.js +9 -1
- package/dist/cjs/nodeviews/date.js +44 -0
- package/dist/cjs/plugin.js +197 -0
- package/dist/cjs/pm-plugins/keymap.js +41 -0
- package/dist/cjs/pm-plugins/main.js +39 -0
- package/dist/cjs/pm-plugins/plugin-key.js +8 -0
- package/dist/cjs/pm-plugins/types.js +5 -0
- package/dist/cjs/pm-plugins/utils.js +70 -0
- package/dist/cjs/ui/DatePicker/date-picker-input.js +253 -0
- package/dist/cjs/ui/DatePicker/index.js +170 -0
- package/dist/cjs/utils/formatParse.js +88 -0
- package/dist/cjs/utils/internal.js +176 -0
- package/dist/es2019/actions.js +93 -0
- package/dist/es2019/commands.js +113 -1
- package/dist/es2019/index.js +1 -1
- package/dist/es2019/nodeviews/date.js +47 -0
- package/dist/es2019/plugin.js +183 -0
- package/dist/es2019/pm-plugins/keymap.js +34 -0
- package/dist/es2019/pm-plugins/main.js +35 -0
- package/dist/es2019/pm-plugins/plugin-key.js +2 -0
- package/dist/es2019/pm-plugins/types.js +1 -0
- package/dist/es2019/pm-plugins/utils.js +70 -0
- package/dist/es2019/ui/DatePicker/date-picker-input.js +242 -0
- package/dist/es2019/ui/DatePicker/index.js +154 -0
- package/dist/es2019/utils/formatParse.js +83 -0
- package/dist/es2019/utils/internal.js +151 -0
- package/dist/esm/actions.js +99 -0
- package/dist/esm/commands.js +112 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/nodeviews/date.js +37 -0
- package/dist/esm/plugin.js +185 -0
- package/dist/esm/pm-plugins/keymap.js +34 -0
- package/dist/esm/pm-plugins/main.js +34 -0
- package/dist/esm/pm-plugins/plugin-key.js +2 -0
- package/dist/esm/pm-plugins/types.js +1 -0
- package/dist/esm/pm-plugins/utils.js +61 -0
- package/dist/esm/ui/DatePicker/date-picker-input.js +247 -0
- package/dist/esm/ui/DatePicker/index.js +164 -0
- package/dist/esm/utils/formatParse.js +79 -0
- package/dist/esm/utils/internal.js +165 -0
- package/dist/types/actions.d.ts +49 -0
- package/dist/types/commands.d.ts +12 -10
- package/dist/types/index.d.ts +1 -1
- package/dist/types/nodeviews/date.d.ts +3 -0
- package/dist/types/plugin.d.ts +3 -0
- package/dist/types/pm-plugins/keymap.d.ts +3 -0
- package/dist/types/pm-plugins/main.d.ts +6 -0
- package/dist/types/pm-plugins/plugin-key.d.ts +3 -0
- package/dist/types/pm-plugins/types.d.ts +12 -0
- package/dist/types/pm-plugins/utils.d.ts +5 -0
- package/dist/types/types.d.ts +10 -2
- package/dist/types/ui/DatePicker/date-picker-input.d.ts +25 -0
- package/dist/types/ui/DatePicker/index.d.ts +36 -0
- package/dist/types/utils/formatParse.d.ts +27 -0
- package/dist/types/utils/internal.d.ts +32 -0
- package/dist/types-ts4.5/actions.d.ts +60 -0
- package/dist/types-ts4.5/commands.d.ts +14 -10
- package/dist/types-ts4.5/index.d.ts +1 -1
- package/dist/types-ts4.5/nodeviews/date.d.ts +3 -0
- package/dist/types-ts4.5/plugin.d.ts +3 -0
- package/dist/types-ts4.5/pm-plugins/keymap.d.ts +3 -0
- package/dist/types-ts4.5/pm-plugins/main.d.ts +6 -0
- package/dist/types-ts4.5/pm-plugins/plugin-key.d.ts +3 -0
- package/dist/types-ts4.5/pm-plugins/types.d.ts +12 -0
- package/dist/types-ts4.5/pm-plugins/utils.d.ts +5 -0
- package/dist/types-ts4.5/types.d.ts +10 -2
- package/dist/types-ts4.5/ui/DatePicker/date-picker-input.d.ts +25 -0
- package/dist/types-ts4.5/ui/DatePicker/index.d.ts +36 -0
- package/dist/types-ts4.5/utils/formatParse.d.ts +27 -0
- package/dist/types-ts4.5/utils/internal.d.ts +32 -0
- package/package.json +29 -4
- package/report.api.md +5 -2
- 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
|
+
}
|