@os-design/core 1.0.280 → 1.0.281
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/dist/Avatar/utils/nameToInitials.d.ts.map +1 -1
- package/dist/Avatar/utils/strToHue.d.ts.map +1 -1
- package/dist/Button/index.d.ts.map +1 -1
- package/dist/Button/index.js +1 -1
- package/dist/Button/utils/useButtonColors.d.ts.map +1 -1
- package/dist/ButtonLink/index.d.ts.map +1 -1
- package/dist/ButtonLink/index.js +0 -5
- package/dist/Checkbox/index.d.ts.map +1 -1
- package/dist/Checkbox/index.js +1 -6
- package/dist/DateCalendar/Calendar.d.ts +25 -0
- package/dist/DateCalendar/Calendar.d.ts.map +1 -0
- package/dist/DateCalendar/Calendar.js +271 -0
- package/dist/DateCalendar/MonthPicker.d.ts +12 -0
- package/dist/DateCalendar/MonthPicker.d.ts.map +1 -0
- package/dist/DateCalendar/MonthPicker.js +159 -0
- package/dist/DateCalendar/index.d.ts +41 -0
- package/dist/DateCalendar/index.d.ts.map +1 -0
- package/dist/DateCalendar/index.js +77 -0
- package/dist/DateCalendar/locale.d.ts +6 -0
- package/dist/DateCalendar/locale.d.ts.map +1 -0
- package/dist/DateCalendar/locale.js +4 -0
- package/dist/DateCalendar/utils/calendarDays.d.ts +10 -0
- package/dist/DateCalendar/utils/calendarDays.d.ts.map +1 -0
- package/dist/DateCalendar/utils/calendarDays.js +46 -0
- package/dist/DateCalendar/utils/dayOfWeek.d.ts +8 -0
- package/dist/DateCalendar/utils/dayOfWeek.d.ts.map +1 -0
- package/dist/DateCalendar/utils/dayOfWeek.js +6 -0
- package/dist/DateCalendar/utils/daysInMonth.d.ts +7 -0
- package/dist/DateCalendar/utils/daysInMonth.d.ts.map +1 -0
- package/dist/DateCalendar/utils/daysInMonth.js +14 -0
- package/dist/DateCalendar/utils/month.d.ts +14 -0
- package/dist/DateCalendar/utils/month.d.ts.map +1 -0
- package/dist/DateCalendar/utils/month.js +24 -0
- package/dist/DateCalendar/utils/shift.d.ts +3 -0
- package/dist/DateCalendar/utils/shift.d.ts.map +1 -0
- package/dist/DateCalendar/utils/shift.js +12 -0
- package/dist/DatePicker/index.d.ts +68 -62
- package/dist/DatePicker/index.d.ts.map +1 -1
- package/dist/DatePicker/index.js +359 -265
- package/dist/DatePicker/utils/createTimes.d.ts +7 -0
- package/dist/DatePicker/utils/createTimes.d.ts.map +1 -0
- package/dist/DatePicker/utils/createTimes.js +15 -0
- package/dist/GlobalStyles/resetStyles.d.ts.map +1 -1
- package/dist/GlobalStyles/typographyStyles.d.ts.map +1 -1
- package/dist/Input/index.d.ts +15 -0
- package/dist/Input/index.d.ts.map +1 -1
- package/dist/Input/index.js +5 -5
- package/dist/Input/utils/getFocusableElements.d.ts.map +1 -1
- package/dist/InputDateUnstyled/index.d.ts +94 -0
- package/dist/InputDateUnstyled/index.d.ts.map +1 -0
- package/dist/InputDateUnstyled/index.js +406 -0
- package/dist/InputDateUnstyled/utils/convertHours.d.ts +4 -0
- package/dist/InputDateUnstyled/utils/convertHours.d.ts.map +1 -0
- package/dist/InputDateUnstyled/utils/convertHours.js +12 -0
- package/dist/InputDateUnstyled/utils/convertToFullYear.d.ts +3 -0
- package/dist/InputDateUnstyled/utils/convertToFullYear.d.ts.map +1 -0
- package/dist/InputDateUnstyled/utils/convertToFullYear.js +10 -0
- package/dist/InputDateUnstyled/utils/dateToString.d.ts +3 -0
- package/dist/InputDateUnstyled/utils/dateToString.d.ts.map +1 -0
- package/dist/InputDateUnstyled/utils/dateToString.js +10 -0
- package/dist/InputDateUnstyled/utils/daysInMonth.d.ts +7 -0
- package/dist/InputDateUnstyled/utils/daysInMonth.d.ts.map +1 -0
- package/dist/InputDateUnstyled/utils/daysInMonth.js +14 -0
- package/dist/InputDateUnstyled/utils/ensureCaretVisible.d.ts +3 -0
- package/dist/InputDateUnstyled/utils/ensureCaretVisible.d.ts.map +1 -0
- package/dist/InputDateUnstyled/utils/ensureCaretVisible.js +32 -0
- package/dist/InputDateUnstyled/utils/eraseSelectedTokens.d.ts +10 -0
- package/dist/InputDateUnstyled/utils/eraseSelectedTokens.d.ts.map +1 -0
- package/dist/InputDateUnstyled/utils/eraseSelectedTokens.js +29 -0
- package/dist/InputDateUnstyled/utils/replaceSubstring.d.ts +3 -0
- package/dist/InputDateUnstyled/utils/replaceSubstring.d.ts.map +1 -0
- package/dist/InputDateUnstyled/utils/replaceSubstring.js +9 -0
- package/dist/InputDateUnstyled/utils/same.d.ts +6 -0
- package/dist/InputDateUnstyled/utils/same.d.ts.map +1 -0
- package/dist/InputDateUnstyled/utils/same.js +3 -0
- package/dist/InputDateUnstyled/utils/stringToDay.d.ts +12 -0
- package/dist/InputDateUnstyled/utils/stringToDay.d.ts.map +1 -0
- package/dist/InputDateUnstyled/utils/stringToDay.js +55 -0
- package/dist/InputDateUnstyled/utils/stringToTime.d.ts +7 -0
- package/dist/InputDateUnstyled/utils/stringToTime.d.ts.map +1 -0
- package/dist/InputDateUnstyled/utils/stringToTime.js +40 -0
- package/dist/InputDateUnstyled/utils/token.d.ts +9 -0
- package/dist/InputDateUnstyled/utils/token.d.ts.map +1 -0
- package/dist/InputDateUnstyled/utils/token.js +66 -0
- package/dist/Link/index.d.ts.map +1 -1
- package/dist/Link/index.js +3 -8
- package/dist/LinkButton/index.d.ts.map +1 -1
- package/dist/LinkButton/index.js +0 -5
- package/dist/List/utils/bodyPointerEvents.d.ts.map +1 -1
- package/dist/List/utils/frameTimeout.d.ts.map +1 -1
- package/dist/List/utils/useRWLoadNext.d.ts.map +1 -1
- package/dist/LogoLink/index.d.ts.map +1 -1
- package/dist/LogoLink/index.js +1 -6
- package/dist/Menu/utils/useFocusWithArrows.d.ts.map +1 -1
- package/dist/Modal/index.d.ts +5 -0
- package/dist/Modal/index.d.ts.map +1 -1
- package/dist/Modal/index.js +53 -48
- package/dist/Navigation/utils/useScrollFlags.d.ts.map +1 -1
- package/dist/NavigationItem/index.d.ts.map +1 -1
- package/dist/NavigationItem/index.js +1 -6
- package/dist/Popover/utils/usePopoverPosition.d.ts.map +1 -1
- package/dist/ScrollButton/utils/useContainerPosition.d.ts.map +1 -1
- package/dist/ScrollButton/utils/useVisibility.d.ts.map +1 -1
- package/dist/Select/index.d.ts.map +1 -1
- package/dist/Select/index.js +2 -3
- package/dist/Switch/index.d.ts.map +1 -1
- package/dist/Switch/index.js +1 -7
- package/dist/TagLink/index.d.ts.map +1 -1
- package/dist/TagLink/index.js +1 -6
- package/dist/TimeGrid/index.d.ts +63 -0
- package/dist/TimeGrid/index.d.ts.map +1 -0
- package/dist/TimeGrid/index.js +111 -0
- package/dist/TimeGrid/utils/convertHours.d.ts +4 -0
- package/dist/TimeGrid/utils/convertHours.d.ts.map +1 -0
- package/dist/TimeGrid/utils/convertHours.js +12 -0
- package/dist/TimeGrid/utils/createTimes.d.ts +7 -0
- package/dist/TimeGrid/utils/createTimes.d.ts.map +1 -0
- package/dist/TimeGrid/utils/createTimes.js +15 -0
- package/dist/TimeGrid/utils/timeToString.d.ts +4 -0
- package/dist/TimeGrid/utils/timeToString.d.ts.map +1 -0
- package/dist/TimeGrid/utils/timeToString.js +12 -0
- package/dist/TimeGridSkeleton/index.d.ts +18 -0
- package/dist/TimeGridSkeleton/index.d.ts.map +1 -0
- package/dist/TimeGridSkeleton/index.js +33 -0
- package/dist/TimeList/index.d.ts +45 -0
- package/dist/TimeList/index.d.ts.map +1 -0
- package/dist/TimeList/index.js +80 -0
- package/dist/TimeList/utils/convertHours.d.ts +4 -0
- package/dist/TimeList/utils/convertHours.d.ts.map +1 -0
- package/dist/TimeList/utils/convertHours.js +12 -0
- package/dist/TimeList/utils/createTimes.d.ts +7 -0
- package/dist/TimeList/utils/createTimes.d.ts.map +1 -0
- package/dist/TimeList/utils/createTimes.js +15 -0
- package/dist/TimeList/utils/timeToString.d.ts +4 -0
- package/dist/TimeList/utils/timeToString.d.ts.map +1 -0
- package/dist/TimeList/utils/timeToString.js +12 -0
- package/dist/TimeListSkeleton/index.d.ts +13 -0
- package/dist/TimeListSkeleton/index.d.ts.map +1 -0
- package/dist/TimeListSkeleton/index.js +30 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -0
- package/dist/message/styles.d.ts.map +1 -1
- package/package.json +8 -8
- package/src/Button/index.tsx +1 -1
- package/src/ButtonLink/index.tsx +0 -5
- package/src/Checkbox/index.tsx +1 -6
- package/src/DateCalendar/Calendar.tsx +400 -0
- package/src/DateCalendar/MonthPicker.tsx +212 -0
- package/src/DateCalendar/index.tsx +135 -0
- package/src/DateCalendar/locale.ts +22 -0
- package/src/DateCalendar/utils/calendarDays.ts +61 -0
- package/src/DateCalendar/utils/dayOfWeek.ts +14 -0
- package/src/DateCalendar/utils/daysInMonth.ts +22 -0
- package/src/DateCalendar/utils/month.ts +30 -0
- package/src/DateCalendar/utils/shift.ts +14 -0
- package/src/DatePicker/index.tsx +506 -417
- package/src/DatePicker/utils/createTimes.ts +20 -0
- package/src/Input/index.tsx +11 -8
- package/src/InputDateUnstyled/index.tsx +533 -0
- package/src/InputDateUnstyled/utils/convertHours.ts +15 -0
- package/src/InputDateUnstyled/utils/convertToFullYear.ts +11 -0
- package/src/InputDateUnstyled/utils/dateToString.ts +21 -0
- package/src/InputDateUnstyled/utils/daysInMonth.ts +22 -0
- package/src/InputDateUnstyled/utils/ensureCaretVisible.ts +37 -0
- package/src/InputDateUnstyled/utils/eraseSelectedTokens.ts +38 -0
- package/src/InputDateUnstyled/utils/replaceSubstring.ts +10 -0
- package/src/InputDateUnstyled/utils/same.ts +15 -0
- package/src/InputDateUnstyled/utils/stringToDay.ts +69 -0
- package/src/InputDateUnstyled/utils/stringToTime.ts +48 -0
- package/src/InputDateUnstyled/utils/token.ts +102 -0
- package/src/Link/index.tsx +5 -25
- package/src/LinkButton/index.tsx +2 -15
- package/src/LogoLink/index.tsx +2 -6
- package/src/Modal/index.tsx +71 -60
- package/src/NavigationItem/index.tsx +2 -16
- package/src/Select/index.tsx +2 -3
- package/src/Switch/index.tsx +1 -11
- package/src/TagLink/index.tsx +3 -11
- package/src/TimeGrid/index.tsx +189 -0
- package/src/TimeGrid/utils/convertHours.ts +15 -0
- package/src/TimeGrid/utils/createTimes.ts +20 -0
- package/src/TimeGrid/utils/timeToString.ts +17 -0
- package/src/TimeGridSkeleton/index.tsx +50 -0
- package/src/TimeList/index.tsx +135 -0
- package/src/TimeList/utils/convertHours.ts +15 -0
- package/src/TimeList/utils/createTimes.ts +20 -0
- package/src/TimeList/utils/timeToString.ts +17 -0
- package/src/TimeListSkeleton/index.tsx +44 -0
- package/src/index.ts +12 -0
- package/dist/DatePicker/DatePickerCalendar.d.ts +0 -11
- package/dist/DatePicker/DatePickerCalendar.d.ts.map +0 -1
- package/dist/DatePicker/DatePickerCalendar.js +0 -178
- package/dist/TimePicker/index.d.ts +0 -29
- package/dist/TimePicker/index.d.ts.map +0 -1
- package/dist/TimePicker/index.js +0 -100
- package/src/DatePicker/DatePickerCalendar.tsx +0 -230
- package/src/TimePicker/index.tsx +0 -144
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface Time {
|
|
2
|
+
hour: number;
|
|
3
|
+
minute: number;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const createTimes = (from: Time, to: Time, stepMinutes: number) => {
|
|
7
|
+
const min = from.hour * 60 + from.minute;
|
|
8
|
+
const max = to.hour * 60 + to.minute;
|
|
9
|
+
const times: Time[] = [];
|
|
10
|
+
|
|
11
|
+
for (let i = min; i <= max - stepMinutes; i += stepMinutes) {
|
|
12
|
+
const hour = Math.floor(i / 60);
|
|
13
|
+
const minute = i % 60;
|
|
14
|
+
times.push({ hour, minute });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return times;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default createTimes;
|
package/src/Input/index.tsx
CHANGED
|
@@ -194,10 +194,13 @@ export const StyledInput = styled(
|
|
|
194
194
|
${notHasRightStyles};
|
|
195
195
|
`;
|
|
196
196
|
|
|
197
|
-
interface
|
|
198
|
-
hasPadding
|
|
197
|
+
interface InputAddonProps {
|
|
198
|
+
hasPadding?: boolean;
|
|
199
199
|
}
|
|
200
|
-
const
|
|
200
|
+
const InputAddon = styled(
|
|
201
|
+
'span',
|
|
202
|
+
omitEmotionProps('hasPadding')
|
|
203
|
+
)<InputAddonProps>`
|
|
201
204
|
display: flex;
|
|
202
205
|
align-items: center;
|
|
203
206
|
user-select: none;
|
|
@@ -208,7 +211,7 @@ const Addon = styled('span', omitEmotionProps('hasPadding'))<AddonProps>`
|
|
|
208
211
|
}
|
|
209
212
|
`;
|
|
210
213
|
|
|
211
|
-
const
|
|
214
|
+
export const InputLeftAddon = styled(InputAddon)`
|
|
212
215
|
padding-right: ${(p) => p.theme.inputAddonPaddingHorizontal}em;
|
|
213
216
|
${(p) =>
|
|
214
217
|
p.hasPadding &&
|
|
@@ -217,7 +220,7 @@ const LeftAddon = styled(Addon)`
|
|
|
217
220
|
`}
|
|
218
221
|
`;
|
|
219
222
|
|
|
220
|
-
const
|
|
223
|
+
export const InputRightAddon = styled(InputAddon)`
|
|
221
224
|
padding-left: ${(p) => p.theme.inputAddonPaddingHorizontal}em;
|
|
222
225
|
${(p) =>
|
|
223
226
|
p.hasPadding &&
|
|
@@ -309,7 +312,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
|
309
312
|
>
|
|
310
313
|
{left && (
|
|
311
314
|
<ThemeOverrider overrides={{ buttonPaddingHorizontal: 0.8 }}>
|
|
312
|
-
<
|
|
315
|
+
<InputLeftAddon hasPadding={leftHasPadding}>{left}</InputLeftAddon>
|
|
313
316
|
</ThemeOverrider>
|
|
314
317
|
)}
|
|
315
318
|
|
|
@@ -326,9 +329,9 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
|
326
329
|
|
|
327
330
|
{rightValue && (
|
|
328
331
|
<ThemeOverrider overrides={{ buttonPaddingHorizontal: 0.8 }}>
|
|
329
|
-
<
|
|
332
|
+
<InputRightAddon hasPadding={rightHasPaddingValue}>
|
|
330
333
|
{rightValue}
|
|
331
|
-
</
|
|
334
|
+
</InputRightAddon>
|
|
332
335
|
</ThemeOverrider>
|
|
333
336
|
)}
|
|
334
337
|
</InputContainer>
|
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
import {
|
|
2
|
+
forwardRef,
|
|
3
|
+
useCallback,
|
|
4
|
+
useEffect,
|
|
5
|
+
useMemo,
|
|
6
|
+
useRef,
|
|
7
|
+
type FocusEvent,
|
|
8
|
+
type KeyboardEvent,
|
|
9
|
+
} from 'react';
|
|
10
|
+
import { nextToken, prevToken } from './utils/token.js';
|
|
11
|
+
import replaceSubstring from './utils/replaceSubstring.js';
|
|
12
|
+
import dateToString from './utils/dateToString.js';
|
|
13
|
+
import { useEvent, useForwardedRef, useForwardedState } from '@os-design/utils';
|
|
14
|
+
import eraseSelectedTokens from './utils/eraseSelectedTokens.js';
|
|
15
|
+
import stringToDay, { type Day, type OnDayError } from './utils/stringToDay.js';
|
|
16
|
+
import stringToTime, { type Time } from './utils/stringToTime.js';
|
|
17
|
+
import ensureCaretVisible from './utils/ensureCaretVisible.js';
|
|
18
|
+
import { sameDate, sameDay, sameTime } from './utils/same.js';
|
|
19
|
+
|
|
20
|
+
export const tokens = [
|
|
21
|
+
'DD',
|
|
22
|
+
'MM',
|
|
23
|
+
'YYYY',
|
|
24
|
+
'YY',
|
|
25
|
+
'hh',
|
|
26
|
+
'HH',
|
|
27
|
+
'mm',
|
|
28
|
+
'aa',
|
|
29
|
+
] as const;
|
|
30
|
+
export const numericTokens = ['DD', 'MM', 'YYYY', 'YY', 'hh', 'HH', 'mm'];
|
|
31
|
+
|
|
32
|
+
export type Token = (typeof tokens)[number];
|
|
33
|
+
|
|
34
|
+
export interface Selection {
|
|
35
|
+
start: number;
|
|
36
|
+
end: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
type JsxInputProps = Omit<
|
|
40
|
+
JSX.IntrinsicElements['input'],
|
|
41
|
+
'value' | 'defaultValue' | 'onChange' | 'ref'
|
|
42
|
+
>;
|
|
43
|
+
|
|
44
|
+
export interface InputDateUnstyledProps extends JsxInputProps {
|
|
45
|
+
/**
|
|
46
|
+
* The string format. Supported tokens:
|
|
47
|
+
* `DD` - The day of the month (01-31).
|
|
48
|
+
* `MM` - The month (01-12).
|
|
49
|
+
* `YYYY` - Four-digit year (2025).
|
|
50
|
+
* `YY` - Two-digit year (25).
|
|
51
|
+
* `hh` - The hour, 12-hour clock (01-12).
|
|
52
|
+
* `HH` - The hour, 24-hour clock (00-23).
|
|
53
|
+
* `mm` - The minute (00-59).
|
|
54
|
+
* `aa` - The meridiem (am, pm).
|
|
55
|
+
* @default `DD.MM.YYYY`
|
|
56
|
+
*/
|
|
57
|
+
format?: string;
|
|
58
|
+
/**
|
|
59
|
+
* Selected date.
|
|
60
|
+
* @default undefined
|
|
61
|
+
*/
|
|
62
|
+
value?: Date | null;
|
|
63
|
+
/**
|
|
64
|
+
* The value of the input.
|
|
65
|
+
* @default undefined
|
|
66
|
+
*/
|
|
67
|
+
inputValue?: string;
|
|
68
|
+
/**
|
|
69
|
+
* The caret position.
|
|
70
|
+
* @default undefined
|
|
71
|
+
*/
|
|
72
|
+
selection?: Selection;
|
|
73
|
+
/**
|
|
74
|
+
* The default value.
|
|
75
|
+
* @default undefined
|
|
76
|
+
*/
|
|
77
|
+
defaultValue?: Date | null;
|
|
78
|
+
/**
|
|
79
|
+
* The change event handler.
|
|
80
|
+
* @default undefined
|
|
81
|
+
*/
|
|
82
|
+
onChange?: (value: Date | null) => void;
|
|
83
|
+
/**
|
|
84
|
+
* The callback called when the input is changed.
|
|
85
|
+
* @default undefined
|
|
86
|
+
*/
|
|
87
|
+
onInputChange?: (value: string) => void;
|
|
88
|
+
/**
|
|
89
|
+
* The callback called when the selection has been changed.
|
|
90
|
+
* @default undefined
|
|
91
|
+
*/
|
|
92
|
+
onSelectionChange?: (selection: Selection) => void;
|
|
93
|
+
/**
|
|
94
|
+
* The callback called when the day is changed.
|
|
95
|
+
* @default undefined
|
|
96
|
+
*/
|
|
97
|
+
onDayChange?: (value: Day) => void;
|
|
98
|
+
/**
|
|
99
|
+
* The callback called when the time is changed.
|
|
100
|
+
* @default undefined
|
|
101
|
+
*/
|
|
102
|
+
onTimeChange?: (value: Time) => void;
|
|
103
|
+
/**
|
|
104
|
+
* The error handler called when the day has been entered incorrectly.
|
|
105
|
+
* @default undefined
|
|
106
|
+
*/
|
|
107
|
+
onDayError?: OnDayError;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* The input for entering a date/time without any styles.
|
|
112
|
+
*/
|
|
113
|
+
const InputDateUnstyled = forwardRef<HTMLInputElement, InputDateUnstyledProps>(
|
|
114
|
+
(
|
|
115
|
+
{
|
|
116
|
+
format = 'DD.MM.YYYY',
|
|
117
|
+
value,
|
|
118
|
+
inputValue,
|
|
119
|
+
selection,
|
|
120
|
+
defaultValue,
|
|
121
|
+
onChange,
|
|
122
|
+
onInputChange,
|
|
123
|
+
onSelectionChange = () => {},
|
|
124
|
+
onDayChange = () => {},
|
|
125
|
+
onTimeChange = () => {},
|
|
126
|
+
onDayError,
|
|
127
|
+
onFocus = () => {},
|
|
128
|
+
onKeyDown = () => {},
|
|
129
|
+
...rest
|
|
130
|
+
},
|
|
131
|
+
ref
|
|
132
|
+
) => {
|
|
133
|
+
const [inputRef, mergedInputRef] = useForwardedRef(ref);
|
|
134
|
+
const [forwardedValue, setForwardedValue] = useForwardedState({
|
|
135
|
+
value,
|
|
136
|
+
defaultValue,
|
|
137
|
+
onChange,
|
|
138
|
+
});
|
|
139
|
+
const [forwardedSelection, setForwardedSelection] = useForwardedState({
|
|
140
|
+
value: selection,
|
|
141
|
+
defaultValue: { start: 0, end: 0 },
|
|
142
|
+
onChange: onSelectionChange,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const defaultInputValue = useMemo(() => {
|
|
146
|
+
let res = format;
|
|
147
|
+
tokens.forEach((token) => {
|
|
148
|
+
res = res.replaceAll(token, '_'.repeat(token.length));
|
|
149
|
+
});
|
|
150
|
+
return res;
|
|
151
|
+
}, [format]);
|
|
152
|
+
|
|
153
|
+
const [forwardedInputValue, setForwardedInputValue] = useForwardedState({
|
|
154
|
+
value: inputValue,
|
|
155
|
+
defaultValue: defaultInputValue,
|
|
156
|
+
onChange: onInputChange,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const dayRef = useRef<Day | null>(null);
|
|
160
|
+
const timeRef = useRef<Time | null>(null);
|
|
161
|
+
|
|
162
|
+
const forwardedValueRef = useRef(forwardedValue);
|
|
163
|
+
useEffect(() => {
|
|
164
|
+
forwardedValueRef.current = forwardedValue;
|
|
165
|
+
}, [forwardedValue]);
|
|
166
|
+
|
|
167
|
+
const setForwardedValueRef = useRef(setForwardedValue);
|
|
168
|
+
useEffect(() => {
|
|
169
|
+
setForwardedValueRef.current = setForwardedValue;
|
|
170
|
+
}, [setForwardedValue]);
|
|
171
|
+
|
|
172
|
+
const forwardedSelectionRef = useRef(forwardedSelection);
|
|
173
|
+
useEffect(() => {
|
|
174
|
+
forwardedSelectionRef.current = forwardedSelection;
|
|
175
|
+
}, [forwardedSelection]);
|
|
176
|
+
|
|
177
|
+
const setForwardedSelectionRef = useRef(setForwardedSelection);
|
|
178
|
+
useEffect(() => {
|
|
179
|
+
setForwardedSelectionRef.current = setForwardedSelection;
|
|
180
|
+
}, [setForwardedSelection]);
|
|
181
|
+
|
|
182
|
+
const defaultInputValueRef = useRef(defaultInputValue);
|
|
183
|
+
useEffect(() => {
|
|
184
|
+
defaultInputValueRef.current = defaultInputValue;
|
|
185
|
+
}, [defaultInputValue]);
|
|
186
|
+
|
|
187
|
+
const forwardedInputValueRef = useRef(forwardedInputValue);
|
|
188
|
+
useEffect(() => {
|
|
189
|
+
forwardedInputValueRef.current = forwardedInputValue;
|
|
190
|
+
}, [forwardedInputValue]);
|
|
191
|
+
|
|
192
|
+
const setForwardedInputValueRef = useRef(setForwardedInputValue);
|
|
193
|
+
useEffect(() => {
|
|
194
|
+
setForwardedInputValueRef.current = setForwardedInputValue;
|
|
195
|
+
}, [setForwardedInputValue]);
|
|
196
|
+
|
|
197
|
+
const formatRef = useRef(format);
|
|
198
|
+
useEffect(() => {
|
|
199
|
+
formatRef.current = format;
|
|
200
|
+
}, [format]);
|
|
201
|
+
|
|
202
|
+
const onDayChangeRef = useRef(onDayChange);
|
|
203
|
+
useEffect(() => {
|
|
204
|
+
onDayChangeRef.current = onDayChange;
|
|
205
|
+
}, [onDayChange]);
|
|
206
|
+
|
|
207
|
+
const onTimeChangeRef = useRef(onTimeChange);
|
|
208
|
+
useEffect(() => {
|
|
209
|
+
onTimeChangeRef.current = onTimeChange;
|
|
210
|
+
}, [onTimeChange]);
|
|
211
|
+
|
|
212
|
+
const onDayErrorRef = useRef(onDayError);
|
|
213
|
+
useEffect(() => {
|
|
214
|
+
onDayErrorRef.current = onDayError;
|
|
215
|
+
}, [onDayError]);
|
|
216
|
+
|
|
217
|
+
const onKeyDownRef = useRef(onKeyDown);
|
|
218
|
+
useEffect(() => {
|
|
219
|
+
onKeyDownRef.current = onKeyDown;
|
|
220
|
+
}, [onKeyDown]);
|
|
221
|
+
|
|
222
|
+
// Change the input selection when the selection state is changed
|
|
223
|
+
useEffect(() => {
|
|
224
|
+
if (!inputRef.current) return;
|
|
225
|
+
const selection = forwardedSelection || { start: 0, end: 0 };
|
|
226
|
+
inputRef.current.setSelectionRange(selection.start, selection.end);
|
|
227
|
+
ensureCaretVisible(inputRef.current);
|
|
228
|
+
}, [forwardedSelection, inputRef]);
|
|
229
|
+
|
|
230
|
+
// Change the input value when the value is changed
|
|
231
|
+
useEffect(() => {
|
|
232
|
+
const nextInputValue = forwardedValue
|
|
233
|
+
? dateToString(forwardedValue, formatRef.current)
|
|
234
|
+
: defaultInputValueRef.current;
|
|
235
|
+
setForwardedInputValueRef.current(nextInputValue);
|
|
236
|
+
}, [forwardedValue]);
|
|
237
|
+
|
|
238
|
+
// Calls onDayChange, onTimeChange, onChange handlers
|
|
239
|
+
// when the input value is changed.
|
|
240
|
+
useEffect(() => {
|
|
241
|
+
const inputValue = forwardedInputValue || defaultInputValueRef.current;
|
|
242
|
+
|
|
243
|
+
const day = stringToDay(
|
|
244
|
+
inputValue,
|
|
245
|
+
formatRef.current,
|
|
246
|
+
onDayErrorRef.current
|
|
247
|
+
);
|
|
248
|
+
const time = stringToTime(inputValue, formatRef.current);
|
|
249
|
+
|
|
250
|
+
if (day && (!dayRef.current || !sameDay(day, dayRef.current))) {
|
|
251
|
+
onDayChangeRef.current(day);
|
|
252
|
+
dayRef.current = day;
|
|
253
|
+
}
|
|
254
|
+
if (time && (!timeRef.current || !sameTime(time, timeRef.current))) {
|
|
255
|
+
onTimeChangeRef.current(time);
|
|
256
|
+
timeRef.current = time;
|
|
257
|
+
}
|
|
258
|
+
if (!inputValue.includes('_')) {
|
|
259
|
+
const date = forwardedValueRef.current
|
|
260
|
+
? new Date(forwardedValueRef.current.getTime())
|
|
261
|
+
: new Date();
|
|
262
|
+
if (day) {
|
|
263
|
+
date.setFullYear(day.year);
|
|
264
|
+
date.setMonth(day.month);
|
|
265
|
+
date.setDate(day.day);
|
|
266
|
+
}
|
|
267
|
+
if (time) {
|
|
268
|
+
date.setHours(time.hour);
|
|
269
|
+
date.setMinutes(time.minute);
|
|
270
|
+
}
|
|
271
|
+
if (
|
|
272
|
+
!forwardedValueRef.current ||
|
|
273
|
+
!sameDate(date, forwardedValueRef.current)
|
|
274
|
+
) {
|
|
275
|
+
setForwardedValueRef.current(date);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}, [forwardedInputValue]);
|
|
279
|
+
|
|
280
|
+
const keyDownHandler = useCallback((e: KeyboardEvent<HTMLInputElement>) => {
|
|
281
|
+
e.preventDefault(); // Avoid the default input behaviour
|
|
282
|
+
|
|
283
|
+
let start = e.currentTarget.selectionStart || 0;
|
|
284
|
+
const end = e.currentTarget.selectionEnd || 0;
|
|
285
|
+
let nextInputValue =
|
|
286
|
+
forwardedInputValueRef.current || defaultInputValueRef.current;
|
|
287
|
+
|
|
288
|
+
if (/^[0-9]$/.test(e.key)) {
|
|
289
|
+
// If the user has selected multiple characters, erase tokens
|
|
290
|
+
// that contain them.
|
|
291
|
+
nextInputValue = eraseSelectedTokens({
|
|
292
|
+
value: nextInputValue,
|
|
293
|
+
start,
|
|
294
|
+
end,
|
|
295
|
+
format: formatRef.current,
|
|
296
|
+
tokens,
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// Move the caret to the left, skipping all `_`
|
|
300
|
+
while (start > 0 && nextInputValue.charAt(start - 1) === '_') {
|
|
301
|
+
start--;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Find the next token and check whether it's a numeric one
|
|
305
|
+
const next = nextToken(start, formatRef.current, tokens);
|
|
306
|
+
if (!next || !numericTokens.includes(next.token)) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Move the caret the the found token. There are 2 possible cases:
|
|
311
|
+
// 1. If the caret has been inside the token, the caret is not moved.
|
|
312
|
+
// 2. If the token is placed to the right of the caret, the caret
|
|
313
|
+
// moves to the start of the token.
|
|
314
|
+
start = next.pos;
|
|
315
|
+
|
|
316
|
+
let substr = e.key;
|
|
317
|
+
const digit = Number(e.key);
|
|
318
|
+
const prevChar = start > 0 ? nextInputValue.charAt(start - 1) : null;
|
|
319
|
+
const posInToken = next.pos - next.start;
|
|
320
|
+
|
|
321
|
+
switch (next.token) {
|
|
322
|
+
case 'DD':
|
|
323
|
+
if (posInToken === 0 && digit >= 4) {
|
|
324
|
+
substr = `0${digit}`;
|
|
325
|
+
} else if (posInToken === 1 && prevChar === '0' && digit === 0) {
|
|
326
|
+
return;
|
|
327
|
+
} else if (posInToken === 1 && prevChar === '3' && digit >= 2) {
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
break;
|
|
331
|
+
case 'MM':
|
|
332
|
+
if (posInToken === 0 && digit >= 2) {
|
|
333
|
+
substr = `0${digit}`;
|
|
334
|
+
} else if (posInToken === 1 && prevChar === '0' && digit === 0) {
|
|
335
|
+
return;
|
|
336
|
+
} else if (posInToken === 1 && prevChar === '1' && digit >= 3) {
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
break;
|
|
340
|
+
case 'hh':
|
|
341
|
+
if (posInToken === 0 && digit >= 2) {
|
|
342
|
+
substr = `0${digit}`;
|
|
343
|
+
} else if (posInToken === 1 && prevChar === '0' && digit === 0) {
|
|
344
|
+
return;
|
|
345
|
+
} else if (posInToken === 1 && prevChar === '1' && digit >= 3) {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
break;
|
|
349
|
+
case 'HH':
|
|
350
|
+
if (posInToken === 0 && digit >= 3) {
|
|
351
|
+
substr = `0${digit}`;
|
|
352
|
+
} else if (posInToken === 1 && prevChar === '2' && digit >= 4) {
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
break;
|
|
356
|
+
case 'mm':
|
|
357
|
+
if (posInToken === 0 && digit >= 6) {
|
|
358
|
+
substr = `0${digit}`;
|
|
359
|
+
}
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Replace the next characters of the current token with '_'
|
|
364
|
+
const suffixLength = next.end - start - substr.length;
|
|
365
|
+
const suffix = suffixLength > 0 ? '_'.repeat(suffixLength) : '';
|
|
366
|
+
|
|
367
|
+
nextInputValue = replaceSubstring(
|
|
368
|
+
nextInputValue,
|
|
369
|
+
start,
|
|
370
|
+
`${substr}${suffix}`
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
// Move the caret to the next token
|
|
374
|
+
start += substr.length;
|
|
375
|
+
const next2 = nextToken(start, formatRef.current, tokens);
|
|
376
|
+
if (next2) start = next2.pos;
|
|
377
|
+
|
|
378
|
+
setForwardedInputValueRef.current(nextInputValue);
|
|
379
|
+
setForwardedSelectionRef.current({ start, end: start });
|
|
380
|
+
} else if ('ap'.includes(e.key.toLowerCase())) {
|
|
381
|
+
if (e.metaKey && e.key === 'a') {
|
|
382
|
+
// Select all
|
|
383
|
+
setForwardedSelectionRef.current({
|
|
384
|
+
start: 0,
|
|
385
|
+
end: formatRef.current.length,
|
|
386
|
+
});
|
|
387
|
+
} else {
|
|
388
|
+
// Find the next token and check whether it's the meridiem token
|
|
389
|
+
const next = nextToken(start, formatRef.current, tokens);
|
|
390
|
+
if (!next || next.token !== 'aa') return;
|
|
391
|
+
|
|
392
|
+
// Move the caret to the start of the token even if the caret
|
|
393
|
+
// has been located inside it.
|
|
394
|
+
start = next.start;
|
|
395
|
+
|
|
396
|
+
// Fill in the token
|
|
397
|
+
const substr = e.key.toLowerCase() === 'p' ? 'pm' : 'am';
|
|
398
|
+
nextInputValue = replaceSubstring(nextInputValue, start, substr);
|
|
399
|
+
|
|
400
|
+
// Move the caret to the next token
|
|
401
|
+
start += substr.length;
|
|
402
|
+
const next2 = nextToken(start, formatRef.current, tokens);
|
|
403
|
+
if (next2) start = next2.pos;
|
|
404
|
+
|
|
405
|
+
setForwardedInputValueRef.current(nextInputValue);
|
|
406
|
+
setForwardedSelectionRef.current({ start, end: start });
|
|
407
|
+
}
|
|
408
|
+
} else if (e.key === 'Backspace') {
|
|
409
|
+
if (end > start) {
|
|
410
|
+
// If the user has selected multiple characters, erase tokens
|
|
411
|
+
// that contain them.
|
|
412
|
+
nextInputValue = eraseSelectedTokens({
|
|
413
|
+
value: nextInputValue,
|
|
414
|
+
start,
|
|
415
|
+
end,
|
|
416
|
+
format: formatRef.current,
|
|
417
|
+
tokens,
|
|
418
|
+
});
|
|
419
|
+
} else {
|
|
420
|
+
// If the caret in the first position, just ignore this event.
|
|
421
|
+
if (start === 0) return;
|
|
422
|
+
|
|
423
|
+
// Move the caret to the previous token
|
|
424
|
+
const prev = prevToken(start, formatRef.current, tokens);
|
|
425
|
+
if (!prev) return;
|
|
426
|
+
|
|
427
|
+
let substr = '_';
|
|
428
|
+
if (prev.token === 'aa') {
|
|
429
|
+
// Move the caret to the start of the meridiem token and erase
|
|
430
|
+
// all the characters of this token.
|
|
431
|
+
start = prev.start;
|
|
432
|
+
substr = '__';
|
|
433
|
+
} else {
|
|
434
|
+
// Erase the token to the end.
|
|
435
|
+
// For example, if the token contains 4 characters and the caret
|
|
436
|
+
// is located after the 2nd one, when the user presses backspace,
|
|
437
|
+
// the 2nd, 3rd, and 4th characters are erased.
|
|
438
|
+
start = prev.pos - 1;
|
|
439
|
+
substr = '_'.repeat(prev.end - start);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
nextInputValue = replaceSubstring(nextInputValue, start, substr);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Move the caret to the previous token
|
|
446
|
+
const prev2 = prevToken(start, formatRef.current, tokens);
|
|
447
|
+
if (prev2) start = prev2.pos;
|
|
448
|
+
|
|
449
|
+
setForwardedInputValueRef.current(nextInputValue);
|
|
450
|
+
setForwardedSelectionRef.current({ start, end: start });
|
|
451
|
+
} else if (e.key === 'ArrowLeft') {
|
|
452
|
+
// If the caret in the first position, just ignore this event.
|
|
453
|
+
if (start === 0) return;
|
|
454
|
+
|
|
455
|
+
// Move the caret to the beginning of the string if the meta key
|
|
456
|
+
// has been pressed (cmd or ctrl).
|
|
457
|
+
if (e.metaKey) start = 0;
|
|
458
|
+
// Move the caret to the left
|
|
459
|
+
else start--;
|
|
460
|
+
|
|
461
|
+
setForwardedSelectionRef.current({ start, end: start });
|
|
462
|
+
} else if (e.key === 'ArrowRight') {
|
|
463
|
+
// If the caret in the last position, just ignore this event.
|
|
464
|
+
if (start === formatRef.current.length) return;
|
|
465
|
+
|
|
466
|
+
// Move the caret to the end of the string if the meta key
|
|
467
|
+
// has been pressed (cmd or ctrl).
|
|
468
|
+
if (e.metaKey) start = formatRef.current.length;
|
|
469
|
+
// Move the caret to the right
|
|
470
|
+
else start++;
|
|
471
|
+
|
|
472
|
+
setForwardedSelectionRef.current({ start, end: start });
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
onKeyDownRef.current(e);
|
|
476
|
+
}, []);
|
|
477
|
+
|
|
478
|
+
// Update the selection when the user clicks inside the input
|
|
479
|
+
const updateSelection = useCallback((e: Event) => {
|
|
480
|
+
const input = e.currentTarget as HTMLInputElement;
|
|
481
|
+
const start = input.selectionStart || 0;
|
|
482
|
+
const end = input.selectionEnd || 0;
|
|
483
|
+
const selection = forwardedSelectionRef.current || { start: 0, end: 0 };
|
|
484
|
+
if (start !== selection.start || end !== selection.end) {
|
|
485
|
+
setForwardedSelectionRef.current({ start, end });
|
|
486
|
+
}
|
|
487
|
+
}, []);
|
|
488
|
+
useEvent(inputRef, 'mouseup', updateSelection);
|
|
489
|
+
useEvent(inputRef, 'touchend', updateSelection);
|
|
490
|
+
|
|
491
|
+
const focusHandler = useCallback(
|
|
492
|
+
(e: FocusEvent<HTMLInputElement>) => {
|
|
493
|
+
setTimeout(() => {
|
|
494
|
+
// Move the caret to the beginning of the first token or if
|
|
495
|
+
// there are no tokens to the beginning of the string.
|
|
496
|
+
const next = nextToken(0, formatRef.current, tokens);
|
|
497
|
+
const pos = next ? next.pos : 0;
|
|
498
|
+
setForwardedSelectionRef.current({ start: pos, end: pos });
|
|
499
|
+
onFocus(e);
|
|
500
|
+
}, 0);
|
|
501
|
+
},
|
|
502
|
+
[onFocus]
|
|
503
|
+
);
|
|
504
|
+
|
|
505
|
+
return (
|
|
506
|
+
<input
|
|
507
|
+
{...rest}
|
|
508
|
+
onFocus={focusHandler}
|
|
509
|
+
onKeyDown={keyDownHandler}
|
|
510
|
+
value={forwardedInputValue}
|
|
511
|
+
ref={mergedInputRef}
|
|
512
|
+
/>
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
);
|
|
516
|
+
|
|
517
|
+
InputDateUnstyled.displayName = 'InputDateUnstyled';
|
|
518
|
+
|
|
519
|
+
export { default as convertToFullYear } from './utils/convertToFullYear.js';
|
|
520
|
+
export { default as dateToString } from './utils/dateToString.js';
|
|
521
|
+
export { default as daysInMonth } from './utils/daysInMonth.js';
|
|
522
|
+
export { default as ensureCaretVisible } from './utils/ensureCaretVisible.js';
|
|
523
|
+
export { default as eraseSelectedTokens } from './utils/eraseSelectedTokens.js';
|
|
524
|
+
export { default as replaceSubstring } from './utils/replaceSubstring.js';
|
|
525
|
+
export { default as stringToDay } from './utils/stringToDay.js';
|
|
526
|
+
export { default as stringToTime } from './utils/stringToTime.js';
|
|
527
|
+
export * from './utils/convertHours.js';
|
|
528
|
+
export * from './utils/same.js';
|
|
529
|
+
export * from './utils/stringToDay.js';
|
|
530
|
+
export * from './utils/stringToTime.js';
|
|
531
|
+
export * from './utils/token.js';
|
|
532
|
+
|
|
533
|
+
export default InputDateUnstyled;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type Meridiem = 'am' | 'pm';
|
|
2
|
+
|
|
3
|
+
export const convertTo12hours = (hours: number): [number, Meridiem] => {
|
|
4
|
+
if (hours >= 12) {
|
|
5
|
+
const hours12 = hours % 12;
|
|
6
|
+
return [hours12 === 0 ? 12 : hours12, 'pm'];
|
|
7
|
+
} else {
|
|
8
|
+
return [hours === 0 ? 12 : hours, 'am'];
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const convertTo24hours = (hours: number, meridiem: Meridiem): number => {
|
|
13
|
+
const h = hours === 12 ? 0 : hours;
|
|
14
|
+
return h + (meridiem === 'am' ? 0 : 12);
|
|
15
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const convertToFullYear = (twoDigitYear: number, currentYear: number) => {
|
|
2
|
+
const fromYear = currentYear - 50;
|
|
3
|
+
const toYear = currentYear + 50;
|
|
4
|
+
if (twoDigitYear >= fromYear % 100) {
|
|
5
|
+
return Math.floor(fromYear / 100) * 100 + twoDigitYear;
|
|
6
|
+
} else {
|
|
7
|
+
return Math.floor(toYear / 100) * 100 + twoDigitYear;
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export default convertToFullYear;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { convertTo12hours } from './convertHours.js';
|
|
2
|
+
|
|
3
|
+
const dateToString = (date: Date, format: string) => {
|
|
4
|
+
let str = format
|
|
5
|
+
.replace('DD', date.getDate().toString().padStart(2, '0'))
|
|
6
|
+
.replace('MM', (date.getMonth() + 1).toString().padStart(2, '0'))
|
|
7
|
+
.replace('YYYY', date.getFullYear().toString())
|
|
8
|
+
.replace('YY', date.getFullYear().toString().slice(2, 4))
|
|
9
|
+
.replace('HH', date.getHours().toString().padStart(2, '0'))
|
|
10
|
+
.replace('mm', date.getMinutes().toString().padStart(2, '0'));
|
|
11
|
+
|
|
12
|
+
if (str.includes('hh')) {
|
|
13
|
+
const [hours, meridiem] = convertTo12hours(date.getHours());
|
|
14
|
+
str = str
|
|
15
|
+
.replace('hh', hours.toString().padStart(2, '0'))
|
|
16
|
+
.replace('aa', meridiem);
|
|
17
|
+
}
|
|
18
|
+
return str;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default dateToString;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
interface Month {
|
|
2
|
+
year: number;
|
|
3
|
+
month: number;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const daysInMonth = (month: Month) => {
|
|
7
|
+
switch (month.month) {
|
|
8
|
+
case 1:
|
|
9
|
+
return (month.year % 4 == 0 && month.year % 100) || month.year % 400 == 0
|
|
10
|
+
? 29
|
|
11
|
+
: 28;
|
|
12
|
+
case 3:
|
|
13
|
+
case 5:
|
|
14
|
+
case 8:
|
|
15
|
+
case 10:
|
|
16
|
+
return 30;
|
|
17
|
+
default:
|
|
18
|
+
return 31;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default daysInMonth;
|