;
44 |
45 | export const DatePicker = {
46 | parameters: {
47 | override: {
48 | children: `{({ monthName, year }) => (
49 | <>
50 |
51 | ${storyToJsx(Prev, {}, 'Button', 4)}
52 |
53 | ${storyToJsx(ToggleMonth, { children: '{monthName}' }, 'Button', 5)}
54 | ${storyToJsx(ToggleYear, { children: '{year}' }, 'Button', 5)}
55 |
56 | ${storyToJsx(Next, {}, 'Button', 4)}
57 |
58 | ${storyToJsx(DateItems, { type: false }, 'Items', 3)}
59 | ${storyToJsx(
60 | Today,
61 | {
62 | className:
63 | '"mt-4 w-full bg-blue-700 p-2 text-sm font-medium hover:bg-blue-600"',
64 | },
65 | 'Button',
66 | 3,
67 | )}
68 | >
69 | )}`,
70 | middleware: false,
71 | },
72 | },
73 | args: {
74 | defaultType: 'day',
75 | alwaysOpen: true,
76 | className:
77 | 'rounded-md bg-white p-4 shadow-md dark:bg-gray-800 dark:text-gray-300 w-[352px]',
78 | middleware: [offset(10), shift()],
79 | children: ({ monthName, year }) => (
80 | <>
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
94 | >
95 | ),
96 | },
97 | } satisfies Story;
98 |
99 | export const HourPicker = {
100 | parameters: {
101 | override: {
102 | children: `${storyToJsx(HourItems, { type: '"hour"' }, 'Items', 1)}
103 | ${storyToJsx(HourItems, { type: '"minute"' }, 'Items', 1)}`,
104 | middleware: false,
105 | },
106 | },
107 | args: {
108 | className:
109 | 'flex max-h-56 rounded-md border border-gray-600 bg-white py-2 shadow-md rtl:flex-row-reverse dark:bg-gray-800 dark:text-gray-300',
110 | children: (
111 | <>
112 |
113 |
114 | >
115 | ),
116 | },
117 | } satisfies Story;
118 |
119 | export const DateHourPicker: Story = {
120 | parameters: {
121 | override: {
122 | children: `{({ monthName, hour, minute, year }) => (
123 | <>
124 |
125 | ${storyToJsx(Prev, {}, 'Button', 4)}
126 |
127 | ${storyToJsx(
128 | Toggle,
129 | {
130 | action: '"toggleHourPicker"',
131 | children:
132 | "{('0' + hour).slice(-2) + ':' + ('0' + minute).slice(-2)}",
133 | },
134 | 'Button',
135 | 5,
136 | )}
137 | ${storyToJsx(ToggleMonth, { children: '{monthName}' }, 'Button', 5)}
138 | ${storyToJsx(ToggleYear, { children: '{year}' }, 'Button', 5)}
139 |
140 | ${storyToJsx(Next, {}, 'Button', 4)}
141 |
142 | ${storyToJsx(DateItems, { type: false }, 'Items', 3)}
143 | ${storyToJsx(
144 | Today,
145 | {
146 | className:
147 | '"mt-4 w-full bg-blue-700 p-2 text-sm font-medium hover:bg-blue-600"',
148 | },
149 | 'Button',
150 | 3,
151 | )}
152 | ${storyToJsx(HourPicker, { id: '"HourPicker"' }, 'Picker', 3)}
153 | >
154 | )}`,
155 | middleware: false,
156 | },
157 | },
158 | args: {
159 | ...DatePicker.args,
160 | children: ({ monthName, hour, minute, year }) => (
161 | <>
162 |
163 |
164 |
165 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
178 |
179 | >
180 | ),
181 | },
182 | };
183 |
184 | export const Calendar: Story = {
185 | parameters: {
186 | override: {
187 | children: `{({ monthName, year }) => (
188 | <>
189 |
190 | ${storyToJsx(Prev, {}, 'Button', 4)}
191 |
192 | ${storyToJsx(
193 | Toggle,
194 | {
195 | children:
196 | "{('0' + hour).slice(-2) + ':' + ('0' + minute).slice(-2)}",
197 | },
198 | 'Button',
199 | 5,
200 | )}
201 | ${storyToJsx(ToggleMonth, { children: '{monthName}' }, 'Button', 5)}
202 | ${storyToJsx(ToggleYear, { children: '{year}' }, 'Button', 5)}
203 |
204 | ${storyToJsx(Next, {}, 'Button', 4)}
205 |
206 | ${storyToJsx(DateItems, { type: false }, 'Items', 3)}
207 | ${storyToJsx(
208 | Today,
209 | {
210 | className:
211 | '"mt-4 w-full bg-blue-700 p-2 text-sm font-medium hover:bg-blue-600"',
212 | },
213 | 'Button',
214 | 3,
215 | )}
216 | >
217 | )}`,
218 | middleware: false,
219 | },
220 | },
221 | args: {
222 | alwaysOpen: true,
223 | defaultType: 'day',
224 | className:
225 | 'flex flex-col w-full h-full bg-white dark:bg-gray-800 dark:text-gray-300 absolute inset-0 p-4',
226 | children: ({ monthName, year }) => (
227 | <>
228 |
229 |
230 |
{monthName}
231 |
232 |
233 |
244 |
245 |
246 |
247 | {({ items }) =>
248 | items.map((item) => (
249 | -
262 | {item.isHeader ? item.text.substring(0, 2) : item.text}
263 |
264 | ))
265 | }
266 |
267 |
268 |
272 | {({ items }) =>
273 | items.map((item) => (
274 | -
282 | {item.text}
283 |
284 | ))
285 | }
286 |
287 |
288 | >
289 | ),
290 | },
291 | };
292 |
--------------------------------------------------------------------------------
/src/jalali/format.ts:
--------------------------------------------------------------------------------
1 | import { config } from './config';
2 |
3 | export const monthsShort = [
4 | 'فر',
5 | 'ارد',
6 | 'خر',
7 | 'تیر',
8 | 'مرد',
9 | 'شهر',
10 | 'مهر',
11 | 'آبا',
12 | 'آذر',
13 | 'دی',
14 | 'بهم',
15 | 'اسفن',
16 | ];
17 | export const weekdaysShort = ['یک', 'دو', 'سه', 'چهار', 'پنج', 'جمعه', 'شنبه'];
18 | export const weekdaysMin = ['یک', 'دو', 'سه', 'چه', 'پن', 'جم', 'شن'];
19 |
20 | const REGEX_FORMAT =
21 | /\[([^\]]+)]|YYYY|YY?|yyyy|yy?|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|m{1,2}|s{1,2}|Z{1,2}|S{1,3}|w{1,2}|x|X|a|A/g;
22 | /**
23 | Era G..GGG AD, BC
24 | Era GGGG Anno Domini, Before Christ 2
25 | Era GGGGG A, B
26 | Calendar year y 44, 1, 1900, 2017 5
27 | Calendar year yo 44th, 1st, 0th, 17th 5,7
28 | Calendar year yy 44, 01, 00, 17 5
29 | Calendar year yyy 044, 001, 1900, 2017 5
30 | Calendar year yyyy 0044, 0001, 1900, 2017 5
31 | Calendar year yyyyy ... 3,5
32 | Local week-numbering year Y 44, 1, 1900, 2017 5
33 | Local week-numbering year Yo 44th, 1st, 1900th, 2017th 5,7
34 | Local week-numbering year YY 44, 01, 00, 17 5,8
35 | Local week-numbering year YYY 044, 001, 1900, 2017 5
36 | Local week-numbering year YYYY 0044, 0001, 1900, 2017 5,8
37 | Local week-numbering year YYYYY ... 3,5
38 | ISO week-numbering year R -43, 0, 1, 1900, 2017 5,7
39 | ISO week-numbering year RR -43, 00, 01, 1900, 2017 5,7
40 | ISO week-numbering year RRR -043, 000, 001, 1900, 2017 5,7
41 | ISO week-numbering year RRRR -0043, 0000, 0001, 1900, 2017 5,7
42 | ISO week-numbering year RRRRR ... 3,5,7
43 | Extended year u -43, 0, 1, 1900, 2017 5
44 | Extended year uu -43, 01, 1900, 2017 5
45 | Extended year uuu -043, 001, 1900, 2017 5
46 | Extended year uuuu -0043, 0001, 1900, 2017 5
47 | Extended year uuuuu ... 3,5
48 | Quarter (formatting) Q 1, 2, 3, 4
49 | Quarter (formatting) Qo 1st, 2nd, 3rd, 4th 7
50 | Quarter (formatting) QQ 01, 02, 03, 04
51 | Quarter (formatting) QQQ Q1, Q2, Q3, Q4
52 | Quarter (formatting) QQQQ 1st quarter, 2nd quarter, ... 2
53 | Quarter (formatting) QQQQQ 1, 2, 3, 4 4
54 | Quarter (stand-alone) q 1, 2, 3, 4
55 | Quarter (stand-alone) qo 1st, 2nd, 3rd, 4th 7
56 | Quarter (stand-alone) qq 01, 02, 03, 04
57 | Quarter (stand-alone) qqq Q1, Q2, Q3, Q4
58 | Quarter (stand-alone) qqqq 1st quarter, 2nd quarter, ... 2
59 | Quarter (stand-alone) qqqqq 1, 2, 3, 4 4
60 | Month (formatting) M 1, 2, ..., 12
61 | Month (formatting) Mo 1st, 2nd, ..., 12th 7
62 | Month (formatting) MM 01, 02, ..., 12
63 | Month (formatting) MMM Jan, Feb, ..., Dec
64 | Month (formatting) MMMM January, February, ..., December 2
65 | Month (formatting) MMMMM J, F, ..., D
66 | Month (stand-alone) L 1, 2, ..., 12
67 | Month (stand-alone) Lo 1st, 2nd, ..., 12th 7
68 | Month (stand-alone) LL 01, 02, ..., 12
69 | Month (stand-alone) LLL Jan, Feb, ..., Dec
70 | Month (stand-alone) LLLL January, February, ..., December 2
71 | Month (stand-alone) LLLLL J, F, ..., D
72 | Local week of year w 1, 2, ..., 53
73 | Local week of year wo 1st, 2nd, ..., 53th 7
74 | Local week of year ww 01, 02, ..., 53
75 | ISO week of year I 1, 2, ..., 53 7
76 | ISO week of year Io 1st, 2nd, ..., 53th 7
77 | ISO week of year II 01, 02, ..., 53 7
78 | Day of month d 1, 2, ..., 31
79 | Day of month do 1st, 2nd, ..., 31st 7
80 | Day of month dd 01, 02, ..., 31
81 | Day of year D 1, 2, ..., 365, 366 9
82 | Day of year Do 1st, 2nd, ..., 365th, 366th 7
83 | Day of year DD 01, 02, ..., 365, 366 9
84 | Day of year DDD 001, 002, ..., 365, 366
85 | Day of year DDDD ... 3
86 | Day of week (formatting) E..EEE Mon, Tue, Wed, ..., Sun
87 | Day of week (formatting) EEEE Monday, Tuesday, ..., Sunday 2
88 | Day of week (formatting) EEEEE M, T, W, T, F, S, S
89 | Day of week (formatting) EEEEEE Mo, Tu, We, Th, Fr, Sa, Su
90 | ISO day of week (formatting) i 1, 2, 3, ..., 7 7
91 | ISO day of week (formatting) io 1st, 2nd, ..., 7th 7
92 | ISO day of week (formatting) ii 01, 02, ..., 07 7
93 | ISO day of week (formatting) iii Mon, Tue, Wed, ..., Sun 7
94 | ISO day of week (formatting) iiii Monday, Tuesday, ..., Sunday 2,7
95 | ISO day of week (formatting) iiiii M, T, W, T, F, S, S 7
96 | ISO day of week (formatting) iiiiii Mo, Tu, We, Th, Fr, Sa, Su 7
97 | Local day of week (formatting) e 2, 3, 4, ..., 1
98 | Local day of week (formatting) eo 2nd, 3rd, ..., 1st 7
99 | Local day of week (formatting) ee 02, 03, ..., 01
100 | Local day of week (formatting) eee Mon, Tue, Wed, ..., Sun
101 | Local day of week (formatting) eeee Monday, Tuesday, ..., Sunday 2
102 | Local day of week (formatting) eeeee M, T, W, T, F, S, S
103 | Local day of week (formatting) eeeeee Mo, Tu, We, Th, Fr, Sa, Su
104 | Local day of week (stand-alone) c 2, 3, 4, ..., 1
105 | Local day of week (stand-alone) co 2nd, 3rd, ..., 1st 7
106 | Local day of week (stand-alone) cc 02, 03, ..., 01
107 | Local day of week (stand-alone) ccc Mon, Tue, Wed, ..., Sun
108 | Local day of week (stand-alone) cccc Monday, Tuesday, ..., Sunday 2
109 | Local day of week (stand-alone) ccccc M, T, W, T, F, S, S
110 | Local day of week (stand-alone) cccccc Mo, Tu, We, Th, Fr, Sa, Su
111 | AM, PM a..aa AM, PM
112 | AM, PM aaa am, pm
113 | AM, PM aaaa a.m., p.m. 2
114 | AM, PM aaaaa a, p
115 | AM, PM, noon, midnight b..bb AM, PM, noon, midnight
116 | AM, PM, noon, midnight bbb am, pm, noon, midnight
117 | AM, PM, noon, midnight bbbb a.m., p.m., noon, midnight 2
118 | AM, PM, noon, midnight bbbbb a, p, n, mi
119 | Flexible day period B..BBB at night, in the morning, ...
120 | Flexible day period BBBB at night, in the morning, ... 2
121 | Flexible day period BBBBB at night, in the morning, ...
122 | Hour [1-12] h 1, 2, ..., 11, 12
123 | Hour [1-12] ho 1st, 2nd, ..., 11th, 12th 7
124 | Hour [1-12] hh 01, 02, ..., 11, 12
125 | Hour [0-23] H 0, 1, 2, ..., 23
126 | Hour [0-23] Ho 0th, 1st, 2nd, ..., 23rd 7
127 | Hour [0-23] HH 00, 01, 02, ..., 23
128 | Hour [0-11] K 1, 2, ..., 11, 0
129 | Hour [0-11] Ko 1st, 2nd, ..., 11th, 0th 7
130 | Hour [0-11] KK 01, 02, ..., 11, 00
131 | Hour [1-24] k 24, 1, 2, ..., 23
132 | Hour [1-24] ko 24th, 1st, 2nd, ..., 23rd 7
133 | Hour [1-24] kk 24, 01, 02, ..., 23
134 | Minute m 0, 1, ..., 59
135 | Minute mo 0th, 1st, ..., 59th 7
136 | Minute mm 00, 01, ..., 59
137 | Second s 0, 1, ..., 59
138 | Second so 0th, 1st, ..., 59th 7
139 | Second ss 00, 01, ..., 59
140 | Fraction of second S 0, 1, ..., 9
141 | Fraction of second SS 00, 01, ..., 99
142 | Fraction of second SSS 000, 001, ..., 999
143 | Fraction of second SSSS ... 3
144 | Timezone (ISO-8601 w/ Z) X -08, +0530, Z
145 | Timezone (ISO-8601 w/ Z) XX -0800, +0530, Z
146 | Timezone (ISO-8601 w/ Z) XXX -08:00, +05:30, Z
147 | Timezone (ISO-8601 w/ Z) XXXX -0800, +0530, Z, +123456 2
148 | Timezone (ISO-8601 w/ Z) XXXXX -08:00, +05:30, Z, +12:34:56
149 | Timezone (ISO-8601 w/o Z) x -08, +0530, +00
150 | Timezone (ISO-8601 w/o Z) xx -0800, +0530, +0000
151 | Timezone (ISO-8601 w/o Z) xxx -08:00, +05:30, +00:00 2
152 | Timezone (ISO-8601 w/o Z) xxxx -0800, +0530, +0000, +123456
153 | Timezone (ISO-8601 w/o Z) xxxxx -08:00, +05:30, +00:00, +12:34:56
154 | Timezone (GMT) O...OOO GMT-8, GMT+5:30, GMT+0
155 | Timezone (GMT) OOOO GMT-08:00, GMT+05:30, GMT+00:00 2
156 | Timezone (specific non-locat.) z...zzz GMT-8, GMT+5:30, GMT+0 6
157 | Timezone (specific non-locat.) zzzz GMT-08:00, GMT+05:30, GMT+00:00 2,6
158 | Seconds timestamp t 512969520 7
159 | Seconds timestamp tt ... 3,7
160 | Milliseconds timestamp T 512969520900 7
161 | Milliseconds timestamp TT ... 3,7
162 | Long localized date P 04/29/1453 7
163 | Long localized date PP Apr 29, 1453 7
164 | Long localized date PPP April 29th, 1453 7
165 | Long localized date PPPP Friday, April 29th, 1453 2,7
166 | Long localized time p 12:00 AM 7
167 | Long localized time pp 12:00:00 AM 7
168 | Long localized time ppp 12:00:00 AM GMT+2 7
169 | Long localized time pppp 12:00:00 AM GMT+02:00 2,7
170 | Combination of date and time Pp 04/29/1453, 12:00 AM 7
171 | Combination of date and time PPpp Apr 29, 1453, 12:00:00 AM 7
172 | Combination of date and time PPPppp April 29th, 1453 at ... 7
173 | Combination of date and time PPPPpppp Friday, April 29th, 1453 at ... 2,7
174 | */
175 |
176 | interface Date {
177 | timezoneOffset: number;
178 | year: number;
179 | month: number;
180 | day: number;
181 | hour: number;
182 | minute: number;
183 | second: number;
184 | millisecond: number;
185 | timestamp: number;
186 | }
187 |
188 | function pad(val: number, len = 2) {
189 | let output = `${Math.abs(val)}`;
190 | const sign = val < 0 ? '-' : '';
191 | while (output.length < len) {
192 | output = `0${output}`;
193 | }
194 | return sign + output;
195 | }
196 |
197 | function getOffset(date: Date) {
198 | return Math.round(date.timezoneOffset / 15) * 15;
199 | }
200 |
201 | function formatTimezone(offset: number, delimeter = '') {
202 | const sign = offset > 0 ? '-' : '+';
203 | const absOffset = Math.abs(offset);
204 | const hours = Math.floor(absOffset / 60);
205 | const minutes = absOffset % 60;
206 | return sign + pad(hours, 2) + delimeter + pad(minutes, 2);
207 | }
208 |
209 | interface FormatFlag {
210 | [key: string]: (date: Date) => string | number;
211 | }
212 |
213 | const meridiem = (h: number, _: number, isLowercase: boolean) => {
214 | const word = h < 12 ? 'AM' : 'PM';
215 | return isLowercase ? word.toLocaleLowerCase() : word;
216 | };
217 |
218 | const formatFlags: FormatFlag = {
219 | Y(date) {
220 | const y = date.year;
221 | return y <= 9999 ? `${y}` : `+${y}`;
222 | },
223 |
224 | // Year: 00, 01, ..., 99
225 | yy(date) {
226 | return pad(date.year, 4).substr(2);
227 | },
228 |
229 | // Year: 1900, 1901, ..., 2099
230 | yyyy(date) {
231 | return pad(date.year, 4);
232 | },
233 |
234 | // Month: 1, 2, ..., 12
235 | M(date) {
236 | return date.month + 1;
237 | },
238 |
239 | // Month: 01, 02, ..., 12
240 | MM(date) {
241 | return pad(date.month + 1, 2);
242 | },
243 |
244 | MMM(date) {
245 | return monthsShort[date.month];
246 | },
247 |
248 | MMMM(date) {
249 | return config.monthNames[date.month];
250 | },
251 |
252 | // Day of month: 1, 2, ..., 31
253 | d(date) {
254 | return date.day;
255 | },
256 |
257 | // Day of month: 01, 02, ..., 31
258 | dd(date) {
259 | return pad(date.day, 2);
260 | },
261 |
262 | // Hour: 0, 1, ... 23
263 | H(date) {
264 | return date.hour;
265 | },
266 |
267 | // Hour: 00, 01, ..., 23
268 | HH(date) {
269 | return pad(date.hour, 2);
270 | },
271 |
272 | // Hour: 1, 2, ..., 12
273 | h(date) {
274 | const hours = date.hour;
275 | if (hours === 0) {
276 | return 12;
277 | }
278 | if (hours > 12) {
279 | return hours % 12;
280 | }
281 | return hours;
282 | },
283 |
284 | // Hour: 01, 02, ..., 12
285 | hh(...args) {
286 | const hours = formatFlags.h(...args) as number;
287 | return pad(hours, 2);
288 | },
289 |
290 | // Minute: 0, 1, ..., 59
291 | m(date) {
292 | return date.minute;
293 | },
294 |
295 | // Minute: 00, 01, ..., 59
296 | mm(date) {
297 | return pad(date.minute, 2);
298 | },
299 |
300 | // Second: 0, 1, ..., 59
301 | s(date) {
302 | return date.second;
303 | },
304 |
305 | // Second: 00, 01, ..., 59
306 | ss(date) {
307 | return pad(date.second, 2);
308 | },
309 |
310 | // 1/10 of second: 0, 1, ..., 9
311 | S(date) {
312 | return Math.floor(date.millisecond / 100);
313 | },
314 |
315 | // 1/100 of second: 00, 01, ..., 99
316 | SS(date) {
317 | return pad(Math.floor(date.millisecond / 10), 2);
318 | },
319 |
320 | // Millisecond: 000, 001, ..., 999
321 | SSS(date) {
322 | return pad(date.millisecond, 3);
323 | },
324 |
325 | // Day of week: 0, 1, ..., 6
326 | D(date) {
327 | return date.day;
328 | },
329 | // Day of week: 'Su', 'Mo', ..., 'Sa'
330 | DD(date) {
331 | return weekdaysMin[date.day];
332 | },
333 |
334 | // Day of week: 'Sun', 'Mon',..., 'Sat'
335 | ddd(date) {
336 | return weekdaysShort[date.day];
337 | },
338 |
339 | // Day of week: 'Sunday', 'Monday', ...,'Saturday'
340 | dddd(date) {
341 | return config.dayNames[date.day];
342 | },
343 |
344 | // AM, PM
345 | A(date) {
346 | return meridiem(date.hour, date.minute, false);
347 | },
348 |
349 | // am, pm
350 | a(date) {
351 | return meridiem(date.hour, date.minute, true);
352 | },
353 |
354 | // Timezone: -01:00, +00:00, ... +12:00
355 | Z(date) {
356 | return formatTimezone(getOffset(date), ':');
357 | },
358 |
359 | // Timezone: -0100, +0000, ... +1200
360 | ZZ(date) {
361 | return formatTimezone(getOffset(date));
362 | },
363 |
364 | // Seconds timestamp: 512969520
365 | X(date) {
366 | return Math.floor(date.timestamp / 1000);
367 | },
368 |
369 | // Milliseconds timestamp: 512969520900
370 | x(date) {
371 | return date.timestamp;
372 | },
373 |
374 | // w(date) {
375 | // return getWeek(date, {
376 | // firstDayOfWeek: firstDayOfWeek,
377 | // });
378 | // },
379 |
380 | ww(date) {
381 | return pad(formatFlags.w(date) as number, 2);
382 | },
383 | };
384 |
385 | export function format(date: Date, str: string) {
386 | const formatStr = str ? String(str) : 'YYYY-MM-DDTHH:mm:ss.SSSZ';
387 |
388 | return formatStr.replace(REGEX_FORMAT, (match, p1: string) => {
389 | if (p1) {
390 | return p1;
391 | }
392 | if (typeof formatFlags[match] === 'function') {
393 | return `${formatFlags[match](date)}`;
394 | }
395 | return match;
396 | });
397 | }
398 |
399 | // function getWeek(date: Date, { firstDayOfWeek = 0 } = {}) {
400 | // const firstDateOfThisWeek = startOfWeek(date, firstDayOfWeek);
401 | // const firstDateOfFirstWeek = startOfWeekYear(date, {
402 | // firstDayOfWeek,
403 | // });
404 | // const diff = firstDateOfThisWeek.getTime() - firstDateOfFirstWeek.getTime();
405 |
406 | // return Math.round(diff / (7 * 24 * 3600 * 1000)) + 1;
407 | // }
408 |
409 | // function startOfWeek(date: Date, firstDayOfWeek = 0) {
410 | // if (!(firstDayOfWeek >= 0 && firstDayOfWeek <= 6)) {
411 | // throw new RangeError('weekStartsOn must be between 0 and 6');
412 | // }
413 | // const day = date.day;
414 | // const diff = (day + 7 - firstDayOfWeek) % 7;
415 | // date.setDate(date.day - diff);
416 | // date.setHours(0, 0, 0, 0);
417 | // return date;
418 | // }
419 |
420 | // function startOfWeekYear(date: Date, { firstDayOfWeek = 0 } = {}) {
421 | // const year = date.year;
422 | // let firstDateOfFirstWeek = new Date(0);
423 | // for (let i = year + 1; i >= year - 1; i--) {
424 | // firstDateOfFirstWeek.setFullYear(i, 0, 1);
425 | // firstDateOfFirstWeek.setHours(0, 0, 0, 0);
426 | // firstDateOfFirstWeek = startOfWeek(firstDateOfFirstWeek, firstDayOfWeek);
427 | // if (date.getTime() >= firstDateOfFirstWeek.getTime()) {
428 | // break;
429 | // }
430 | // }
431 | // return firstDateOfFirstWeek;
432 | // }
433 |
--------------------------------------------------------------------------------
/src/jalali/parse.ts:
--------------------------------------------------------------------------------
1 | import { monthsShort } from './format';
2 |
3 | const monthNames = [
4 | 'فروردین',
5 | 'اردیبهشت',
6 | 'خرداد',
7 | 'تیر',
8 | 'مرداد',
9 | 'شهریور',
10 | 'مهر',
11 | 'آبان',
12 | 'آذر',
13 | 'دی',
14 | 'بهمن',
15 | 'اسفند',
16 | ];
17 |
18 | interface Date {
19 | timezoneOffset: number;
20 | year: number;
21 | month: number;
22 | day: number;
23 | hour: number;
24 | minute: number;
25 | second: number;
26 | millisecond: number;
27 | }
28 |
29 | const formattingTokens =
30 | /(\[[^[]*\])|(MM?M?M?|Do|DD?|ddd?d?|w[o|w]?|YYYY|YY|yyyy|yy|a|A|hh?|HH?|mm?|ss?|S{1,3}|x|X|ZZ?|.)/g;
31 | /**
32 | Era G..GGG AD, BC
33 | Era GGGG Anno Domini, Before Christ 2
34 | Era GGGGG A, B
35 | Calendar year y 44, 1, 1900, 2017 5
36 | Calendar year yo 44th, 1st, 0th, 17th 5,7
37 | Calendar year yy 44, 01, 00, 17 5
38 | Calendar year yyy 044, 001, 1900, 2017 5
39 | Calendar year yyyy 0044, 0001, 1900, 2017 5
40 | Calendar year yyyyy ... 3,5
41 | Local week-numbering year Y 44, 1, 1900, 2017 5
42 | Local week-numbering year Yo 44th, 1st, 1900th, 2017th 5,7
43 | Local week-numbering year YY 44, 01, 00, 17 5,8
44 | Local week-numbering year YYY 044, 001, 1900, 2017 5
45 | Local week-numbering year YYYY 0044, 0001, 1900, 2017 5,8
46 | Local week-numbering year YYYYY ... 3,5
47 | ISO week-numbering year R -43, 0, 1, 1900, 2017 5,7
48 | ISO week-numbering year RR -43, 00, 01, 1900, 2017 5,7
49 | ISO week-numbering year RRR -043, 000, 001, 1900, 2017 5,7
50 | ISO week-numbering year RRRR -0043, 0000, 0001, 1900, 2017 5,7
51 | ISO week-numbering year RRRRR ... 3,5,7
52 | Extended year u -43, 0, 1, 1900, 2017 5
53 | Extended year uu -43, 01, 1900, 2017 5
54 | Extended year uuu -043, 001, 1900, 2017 5
55 | Extended year uuuu -0043, 0001, 1900, 2017 5
56 | Extended year uuuuu ... 3,5
57 | Quarter (formatting) Q 1, 2, 3, 4
58 | Quarter (formatting) Qo 1st, 2nd, 3rd, 4th 7
59 | Quarter (formatting) QQ 01, 02, 03, 04
60 | Quarter (formatting) QQQ Q1, Q2, Q3, Q4
61 | Quarter (formatting) QQQQ 1st quarter, 2nd quarter, ... 2
62 | Quarter (formatting) QQQQQ 1, 2, 3, 4 4
63 | Quarter (stand-alone) q 1, 2, 3, 4
64 | Quarter (stand-alone) qo 1st, 2nd, 3rd, 4th 7
65 | Quarter (stand-alone) qq 01, 02, 03, 04
66 | Quarter (stand-alone) qqq Q1, Q2, Q3, Q4
67 | Quarter (stand-alone) qqqq 1st quarter, 2nd quarter, ... 2
68 | Quarter (stand-alone) qqqqq 1, 2, 3, 4 4
69 | Month (formatting) M 1, 2, ..., 12
70 | Month (formatting) Mo 1st, 2nd, ..., 12th 7
71 | Month (formatting) MM 01, 02, ..., 12
72 | Month (formatting) MMM Jan, Feb, ..., Dec
73 | Month (formatting) MMMM January, February, ..., December 2
74 | Month (formatting) MMMMM J, F, ..., D
75 | Month (stand-alone) L 1, 2, ..., 12
76 | Month (stand-alone) Lo 1st, 2nd, ..., 12th 7
77 | Month (stand-alone) LL 01, 02, ..., 12
78 | Month (stand-alone) LLL Jan, Feb, ..., Dec
79 | Month (stand-alone) LLLL January, February, ..., December 2
80 | Month (stand-alone) LLLLL J, F, ..., D
81 | Local week of year w 1, 2, ..., 53
82 | Local week of year wo 1st, 2nd, ..., 53th 7
83 | Local week of year ww 01, 02, ..., 53
84 | ISO week of year I 1, 2, ..., 53 7
85 | ISO week of year Io 1st, 2nd, ..., 53th 7
86 | ISO week of year II 01, 02, ..., 53 7
87 | Day of month d 1, 2, ..., 31
88 | Day of month do 1st, 2nd, ..., 31st 7
89 | Day of month dd 01, 02, ..., 31
90 | Day of year D 1, 2, ..., 365, 366 9
91 | Day of year Do 1st, 2nd, ..., 365th, 366th 7
92 | Day of year DD 01, 02, ..., 365, 366 9
93 | Day of year DDD 001, 002, ..., 365, 366
94 | Day of year DDDD ... 3
95 | Day of week (formatting) E..EEE Mon, Tue, Wed, ..., Sun
96 | Day of week (formatting) EEEE Monday, Tuesday, ..., Sunday 2
97 | Day of week (formatting) EEEEE M, T, W, T, F, S, S
98 | Day of week (formatting) EEEEEE Mo, Tu, We, Th, Fr, Sa, Su
99 | ISO day of week (formatting) i 1, 2, 3, ..., 7 7
100 | ISO day of week (formatting) io 1st, 2nd, ..., 7th 7
101 | ISO day of week (formatting) ii 01, 02, ..., 07 7
102 | ISO day of week (formatting) iii Mon, Tue, Wed, ..., Sun 7
103 | ISO day of week (formatting) iiii Monday, Tuesday, ..., Sunday 2,7
104 | ISO day of week (formatting) iiiii M, T, W, T, F, S, S 7
105 | ISO day of week (formatting) iiiiii Mo, Tu, We, Th, Fr, Sa, Su 7
106 | Local day of week (formatting) e 2, 3, 4, ..., 1
107 | Local day of week (formatting) eo 2nd, 3rd, ..., 1st 7
108 | Local day of week (formatting) ee 02, 03, ..., 01
109 | Local day of week (formatting) eee Mon, Tue, Wed, ..., Sun
110 | Local day of week (formatting) eeee Monday, Tuesday, ..., Sunday 2
111 | Local day of week (formatting) eeeee M, T, W, T, F, S, S
112 | Local day of week (formatting) eeeeee Mo, Tu, We, Th, Fr, Sa, Su
113 | Local day of week (stand-alone) c 2, 3, 4, ..., 1
114 | Local day of week (stand-alone) co 2nd, 3rd, ..., 1st 7
115 | Local day of week (stand-alone) cc 02, 03, ..., 01
116 | Local day of week (stand-alone) ccc Mon, Tue, Wed, ..., Sun
117 | Local day of week (stand-alone) cccc Monday, Tuesday, ..., Sunday 2
118 | Local day of week (stand-alone) ccccc M, T, W, T, F, S, S
119 | Local day of week (stand-alone) cccccc Mo, Tu, We, Th, Fr, Sa, Su
120 | AM, PM a..aa AM, PM
121 | AM, PM aaa am, pm
122 | AM, PM aaaa a.m., p.m. 2
123 | AM, PM aaaaa a, p
124 | AM, PM, noon, midnight b..bb AM, PM, noon, midnight
125 | AM, PM, noon, midnight bbb am, pm, noon, midnight
126 | AM, PM, noon, midnight bbbb a.m., p.m., noon, midnight 2
127 | AM, PM, noon, midnight bbbbb a, p, n, mi
128 | Flexible day period B..BBB at night, in the morning, ...
129 | Flexible day period BBBB at night, in the morning, ... 2
130 | Flexible day period BBBBB at night, in the morning, ...
131 | Hour [1-12] h 1, 2, ..., 11, 12
132 | Hour [1-12] ho 1st, 2nd, ..., 11th, 12th 7
133 | Hour [1-12] hh 01, 02, ..., 11, 12
134 | Hour [0-23] H 0, 1, 2, ..., 23
135 | Hour [0-23] Ho 0th, 1st, 2nd, ..., 23rd 7
136 | Hour [0-23] HH 00, 01, 02, ..., 23
137 | Hour [0-11] K 1, 2, ..., 11, 0
138 | Hour [0-11] Ko 1st, 2nd, ..., 11th, 0th 7
139 | Hour [0-11] KK 01, 02, ..., 11, 00
140 | Hour [1-24] k 24, 1, 2, ..., 23
141 | Hour [1-24] ko 24th, 1st, 2nd, ..., 23rd 7
142 | Hour [1-24] kk 24, 01, 02, ..., 23
143 | Minute m 0, 1, ..., 59
144 | Minute mo 0th, 1st, ..., 59th 7
145 | Minute mm 00, 01, ..., 59
146 | Second s 0, 1, ..., 59
147 | Second so 0th, 1st, ..., 59th 7
148 | Second ss 00, 01, ..., 59
149 | Fraction of second S 0, 1, ..., 9
150 | Fraction of second SS 00, 01, ..., 99
151 | Fraction of second SSS 000, 001, ..., 999
152 | Fraction of second SSSS ... 3
153 | Timezone (ISO-8601 w/ Z) X -08, +0530, Z
154 | Timezone (ISO-8601 w/ Z) XX -0800, +0530, Z
155 | Timezone (ISO-8601 w/ Z) XXX -08:00, +05:30, Z
156 | Timezone (ISO-8601 w/ Z) XXXX -0800, +0530, Z, +123456 2
157 | Timezone (ISO-8601 w/ Z) XXXXX -08:00, +05:30, Z, +12:34:56
158 | Timezone (ISO-8601 w/o Z) x -08, +0530, +00
159 | Timezone (ISO-8601 w/o Z) xx -0800, +0530, +0000
160 | Timezone (ISO-8601 w/o Z) xxx -08:00, +05:30, +00:00 2
161 | Timezone (ISO-8601 w/o Z) xxxx -0800, +0530, +0000, +123456
162 | Timezone (ISO-8601 w/o Z) xxxxx -08:00, +05:30, +00:00, +12:34:56
163 | Timezone (GMT) O...OOO GMT-8, GMT+5:30, GMT+0
164 | Timezone (GMT) OOOO GMT-08:00, GMT+05:30, GMT+00:00 2
165 | Timezone (specific non-locat.) z...zzz GMT-8, GMT+5:30, GMT+0 6
166 | Timezone (specific non-locat.) zzzz GMT-08:00, GMT+05:30, GMT+00:00 2,6
167 | Seconds timestamp t 512969520 7
168 | Seconds timestamp tt ... 3,7
169 | Milliseconds timestamp T 512969520900 7
170 | Milliseconds timestamp TT ... 3,7
171 | Long localized date P 04/29/1453 7
172 | Long localized date PP Apr 29, 1453 7
173 | Long localized date PPP April 29th, 1453 7
174 | Long localized date PPPP Friday, April 29th, 1453 2,7
175 | Long localized time p 12:00 AM 7
176 | Long localized time pp 12:00:00 AM 7
177 | Long localized time ppp 12:00:00 AM GMT+2 7
178 | Long localized time pppp 12:00:00 AM GMT+02:00 2,7
179 | Combination of date and time Pp 04/29/1453, 12:00 AM 7
180 | Combination of date and time PPpp Apr 29, 1453, 12:00:00 AM 7
181 | Combination of date and time PPPppp April 29th, 1453 at ... 7
182 | Combination of date and time PPPPpppp Friday, April 29th, 1453 at ... 2,7
183 | */
184 |
185 | const match1 = /\d/; // 0 - 9
186 | const match2 = /\d\d/; // 00 - 99
187 | const match3 = /\d{3}/; // 000 - 999
188 | const match4 = /\d{4}/; // 0000 - 9999
189 | const match1to2 = /\d\d?/; // 0 - 99
190 | const matchShortOffset = /[+-]\d\d:?\d\d/; // +00:00 -00:00 +0000 or -0000
191 | const matchSigned = /[+-]?\d+/; // -inf - inf
192 |
193 | // const matchWord = /[0-9]{0,256}['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFF07\uFF10-\uFFEF]{1,256}|[\u0600-\u06FF\/]{1,256}(\s*?[\u0600-\u06FF]{1,256}){1,2}/i; // Word
194 |
195 | const YEAR = 'year';
196 | const MONTH = 'month';
197 | const DAY = 'day';
198 | const HOUR = 'hour';
199 | const MINUTE = 'minute';
200 | const SECOND = 'second';
201 | const MILLISECOND = 'millisecond';
202 |
203 | export interface ParseFlagMark {
204 | year: number;
205 | month: number;
206 | day: number;
207 | hour: number;
208 | minute: number;
209 | second: number;
210 | millisecond: number;
211 | offset: number;
212 | isPM: boolean;
213 | }
214 |
215 | export type ParseFlagCallBackReturn = Unionize;
216 |
217 | export type ParseFlagRegExp = RegExp | (() => RegExp);
218 | export type ParseFlagCallBack = (input: string) => ParseFlagCallBackReturn;
219 |
220 | export interface ParseFlag {
221 | [key: string]: [ParseFlagRegExp, ParseFlagCallBack];
222 | }
223 |
224 | const parseFlags: ParseFlag = {};
225 |
226 | const addParseFlag = (
227 | token: string | string[],
228 | regex: ParseFlagRegExp,
229 | callback: ParseFlagCallBack | keyof PickByValue,
230 | ) => {
231 | const tokens = Array.isArray(token) ? token : [token];
232 | let func: ParseFlagCallBack;
233 | if (typeof callback === 'string') {
234 | func = (input) => {
235 | const value = parseInt(input, 10);
236 | return { [callback]: value } as Unionize<
237 | PickByValue
238 | >;
239 | };
240 | } else {
241 | func = callback;
242 | }
243 | tokens.forEach((key) => {
244 | parseFlags[key] = [regex, func];
245 | });
246 | };
247 |
248 | const escapeStringRegExp = (str: string) => {
249 | return str.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&');
250 | };
251 |
252 | const matchWordRegExp = (array: string[]) => {
253 | return () => {
254 | return new RegExp(array.map(escapeStringRegExp).join('|'));
255 | };
256 | };
257 |
258 | const matchWordCallback = (array: string[], key: 'month') => {
259 | return (input: string) => {
260 | const index = array.indexOf(input);
261 | if (index < 0) {
262 | throw new Error('Invalid Word');
263 | }
264 | return { [key]: index } as Unionize>;
265 | };
266 | };
267 |
268 | addParseFlag('Y', matchSigned, YEAR);
269 | addParseFlag('YY', match2, (input) => {
270 | const year = new Date().getFullYear();
271 | const cent = Math.floor(year / 100);
272 | let value = parseInt(input, 10);
273 | value = (value > 68 ? cent - 1 : cent) * 100 + value;
274 | return { [YEAR]: value };
275 | });
276 | addParseFlag('yyyy', match4, YEAR);
277 | addParseFlag('M', match1to2, (input) => ({ [MONTH]: parseInt(input, 10) - 1 }));
278 | addParseFlag('MM', match2, (input) => ({ [MONTH]: parseInt(input, 10) - 1 }));
279 | addParseFlag(
280 | 'MMM',
281 | matchWordRegExp(monthsShort),
282 | matchWordCallback(monthsShort, MONTH),
283 | );
284 | addParseFlag(
285 | 'MMMM',
286 | matchWordRegExp(monthNames),
287 | matchWordCallback(monthNames, MONTH),
288 | );
289 | addParseFlag('d', match1to2, DAY);
290 | addParseFlag('dd', match2, DAY);
291 | addParseFlag(['H', 'h'], match1to2, HOUR);
292 | addParseFlag(['HH', 'hh'], match2, HOUR);
293 | addParseFlag('m', match1to2, MINUTE);
294 | addParseFlag('mm', match2, MINUTE);
295 | addParseFlag('s', match1to2, SECOND);
296 | addParseFlag('ss', match2, SECOND);
297 | addParseFlag('S', match1, (input) => {
298 | return {
299 | [MILLISECOND]: parseInt(input, 10) * 100,
300 | };
301 | });
302 | addParseFlag('SS', match2, (input) => {
303 | return {
304 | [MILLISECOND]: parseInt(input, 10) * 10,
305 | };
306 | });
307 | addParseFlag('SSS', match3, MILLISECOND);
308 |
309 | function matchMeridiem() {
310 | return /[ap]\.?m?\.?/i;
311 | }
312 |
313 | function defaultIsPM(input: string) {
314 | return `${input}`.toLowerCase().charAt(0) === 'p';
315 | }
316 |
317 | addParseFlag(['A', 'a'], matchMeridiem, (input) => {
318 | const isPM = defaultIsPM(input);
319 | return { isPM };
320 | });
321 |
322 | function offsetFromString(str: string) {
323 | const [symbol, hour, minute] = str.match(/([+-]|\d\d)/g) || ['-', '0', '0'];
324 | const minutes = parseInt(hour, 10) * 60 + parseInt(minute, 10);
325 | if (minutes === 0) {
326 | return 0;
327 | }
328 | return symbol === '+' ? -minutes : +minutes;
329 | }
330 |
331 | addParseFlag(['Z', 'ZZ'], matchShortOffset, (input) => {
332 | return { offset: offsetFromString(input) };
333 | });
334 |
335 | // TODO: uncomment this
336 | // addParseFlag('x', matchSigned, (input) => {
337 | // return { date: new Date(parseInt(input, 10)) };
338 | // });
339 |
340 | // addParseFlag('X', matchTimestamp, (input) => {
341 | // return { date: new Date(parseFloat(input) * 1000) };
342 | // });
343 |
344 | // addParseFlag('d', match1, 'weekday');
345 |
346 | // addParseFlag('w', match1to2, 'week');
347 | // addParseFlag('ww', match2, 'week');
348 |
349 | function to24hour(hour?: number, isPM?: boolean) {
350 | if (hour !== undefined && isPM !== undefined) {
351 | if (isPM) {
352 | if (hour < 12) {
353 | return hour + 12;
354 | }
355 | } else if (hour === 12) {
356 | return 0;
357 | }
358 | }
359 | return hour;
360 | }
361 |
362 | function makeParser(dateString: string, format: string) {
363 | const tokens = format.match(formattingTokens);
364 | if (!tokens) {
365 | throw new Error();
366 | }
367 | const { length } = tokens;
368 | let mark: Partial = {};
369 | for (let i = 0; i < length; i += 1) {
370 | const token = tokens[i];
371 | const parseTo = parseFlags[token];
372 | if (!parseTo) {
373 | const word = token.replace(/^\[|\]$/g, '');
374 | if (dateString.indexOf(word) === 0) {
375 | dateString = dateString.substr(word.length);
376 | } else {
377 | throw new Error('not match');
378 | }
379 | } else {
380 | const regex =
381 | typeof parseTo[0] === 'function' ? parseTo[0]() : parseTo[0];
382 | const parser = parseTo[1];
383 | const value = (regex.exec(dateString) || [])[0];
384 | const obj = parser(value || '');
385 | mark = { ...mark, ...obj };
386 | dateString = dateString.replace(value || '', '');
387 | }
388 | }
389 | return mark;
390 | }
391 |
392 | export function parse(str: string, format: string, fallback = new Date()) {
393 | const result: Date = {
394 | year: fallback.getFullYear(),
395 | month: fallback.getMonth(),
396 | day: fallback.getDate(),
397 | hour: fallback.getHours(),
398 | minute: fallback.getMinutes(),
399 | second: fallback.getSeconds(),
400 | millisecond: fallback.getMilliseconds(),
401 | timezoneOffset: fallback.getTimezoneOffset(),
402 | };
403 | const parseResult = makeParser(str, format);
404 | (
405 | ['year', 'month', 'day', 'hour', 'minute', 'second', 'millisecond'] as const
406 | ).forEach((key) => {
407 | if (parseResult[key]) result[key] = parseResult[key] || 0;
408 | });
409 |
410 | result.hour = to24hour(result.hour, parseResult.isPM) || 0;
411 |
412 | if (parseResult.offset !== undefined) {
413 | result.timezoneOffset = parseResult.offset;
414 | }
415 | return result;
416 | }
417 |
418 | type Unionize = {
419 | [P in keyof T]: { [Q in P]: T[P] };
420 | }[keyof T];
421 |
422 | type PickByValue = Pick<
423 | T,
424 | { [Key in keyof T]-?: T[Key] extends ValueType ? Key : never }[keyof T]
425 | >;
426 |
--------------------------------------------------------------------------------
/src/context/context.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Key,
3 | MutableRefObject,
4 | RefObject,
5 | createContext,
6 | useContext,
7 | } from 'react';
8 | import { match } from '../utils/match';
9 | import { mod } from '../utils/mod';
10 |
11 | export type DateItemType =
12 | | {
13 | type: 'day';
14 |
15 | /**
16 | * remove this
17 | */
18 | key: Key;
19 |
20 | /**
21 | * Is today day or month or year depend of the type
22 | */
23 | isToday: boolean;
24 |
25 | isDisabled: boolean;
26 |
27 | isSelected: boolean;
28 |
29 | isHeader: true;
30 |
31 | text: string;
32 |
33 | value: number;
34 | }
35 | | {
36 | type: 'day';
37 |
38 | /**
39 | * remove this
40 | */
41 | key: Key;
42 |
43 | /**
44 | * Is today day or month or year depend of the type
45 | */
46 | isToday: boolean;
47 |
48 | isInCurrentMonth: boolean;
49 |
50 | isSelected: boolean;
51 |
52 | isHeader: false;
53 |
54 | isDisabled: boolean;
55 |
56 | text: string;
57 |
58 | value: Date;
59 | }
60 | | {
61 | type: 'month';
62 |
63 | /**
64 | * remove this
65 | */
66 | key: Key;
67 |
68 | /**
69 | * Is today day or month or year depend of the type
70 | */
71 | isToday: boolean;
72 |
73 | isSelected: boolean;
74 |
75 | isHeader: boolean;
76 |
77 | isDisabled: boolean;
78 |
79 | text: string;
80 |
81 | value: number;
82 | }
83 | | {
84 | type: 'year';
85 |
86 | /**
87 | * remove this
88 | */
89 | key: Key;
90 |
91 | /**
92 | * Is today day or month or year depend of the type
93 | */
94 | isToday: boolean;
95 |
96 | isSelected: boolean;
97 |
98 | isHeader: boolean;
99 |
100 | isDisabled: boolean;
101 |
102 | text: string;
103 |
104 | value: number;
105 | };
106 |
107 | export type HourItemType =
108 | | {
109 | key: Key;
110 |
111 | type: 'hour';
112 |
113 | isToday: false;
114 |
115 | isSelected: boolean;
116 |
117 | isHeader: false;
118 |
119 | isDisabled: boolean;
120 |
121 | value: number;
122 |
123 | text: string;
124 | }
125 | | {
126 | key: Key;
127 |
128 | type: 'minute';
129 |
130 | isToday: false;
131 |
132 | isSelected: boolean;
133 |
134 | isHeader: false;
135 |
136 | isDisabled: boolean;
137 |
138 | value: number;
139 |
140 | text: string;
141 | };
142 |
143 | export type ItemType = DateItemType | HourItemType;
144 |
145 | export type DateParts = {
146 | day: number;
147 | month: number;
148 | year: number;
149 | };
150 |
151 | type RemoveS = T extends `${infer Type}s`
152 | ? Type
153 | : never;
154 |
155 | export type GetDateItems = {
156 | [key in `${DateItemType['type']}s`]: (state: {
157 | type: RemoveS;
158 | year: number;
159 | month: number;
160 | value: Date | null;
161 | startOfWeek: number;
162 | }) => Array }>>;
163 | } & {
164 | [key in `${HourItemType['type']}s`]: (state: {
165 | type: RemoveS;
166 | hour: number;
167 | minute: number;
168 | }) => Array }>>;
169 | };
170 |
171 | export type Action =
172 | | `open${string}`
173 | | `close${string}`
174 | | `toggle${string}`
175 | | `next${string}`
176 | | `prev${string}`
177 | | `showYear${string}`
178 | | `showMonth${string}`
179 | | `showDay${string}`
180 | | `toggleYear${string}`
181 | | `toggleMonth${string}`
182 | | `toggleDay${string}`
183 | | 'today'
184 | | 'todayHour';
185 |
186 | export type DatepickerContextActions =
187 | | {
188 | type: 'action';
189 | payload: {
190 | action: Action;
191 | ref?: RefObject;
192 | pickerId?: undefined | string;
193 | };
194 | }
195 | | {
196 | type: 'select';
197 | payload: {
198 | item: ItemType;
199 | pickerId: string | undefined;
200 | action?: Action;
201 | };
202 | }
203 | | {
204 | type: 'externalValueChanged';
205 | payload: Date;
206 | }
207 | | { type: 'defaultChanged'; payload: Partial }
208 | | {
209 | type: 'registerPicker';
210 | payload: {
211 | id: string;
212 | nestedLevel: number;
213 | defaultType?: ItemType['type'];
214 | defaultOpen: boolean;
215 | alwaysOpen?: boolean;
216 | };
217 | }
218 | | {
219 | type: 'unregisterPicker';
220 | payload: string;
221 | };
222 |
223 | export type DatepickerSlot = {
224 | pickers: {
225 | [index: string]: {
226 | nestedLevel: number;
227 | attach: MutableRefObject | undefined;
228 | isOpen: boolean;
229 | alwaysOpen?: boolean;
230 | type?: ItemType['type'];
231 | defaultType?: ItemType['type'];
232 | };
233 | };
234 |
235 | disabled: boolean;
236 |
237 | value: Date | null;
238 | month: number;
239 | monthName: string;
240 | year: number;
241 | hour: number;
242 | minute: number;
243 | };
244 |
245 | export type DatepickerConfig = {
246 | dayNames: string[];
247 | monthNames: string[];
248 | format: (date: Date | null, format: string) => string;
249 | parse: (date: string, format: string, referenceDate: Date | null) => Date;
250 | toDateParts: (date: Date) => DateParts;
251 | } & GetDateItems;
252 |
253 | export type DatepickerState = Omit & {
254 | startOfWeek: number;
255 |
256 | config: DatepickerConfig;
257 |
258 | valueRef: RefObject;
259 |
260 | onChange: (value: Date | null) => void;
261 |
262 | filterDate: (date: Date) => boolean;
263 | };
264 |
265 | export const datePickerReducer = (
266 | state: DatepickerState,
267 | { type, payload }: DatepickerContextActions,
268 | ): DatepickerState => {
269 | switch (type) {
270 | case 'action':
271 | return runAction(state, payload);
272 | case 'registerPicker':
273 | return {
274 | ...state,
275 | pickers: {
276 | ...state.pickers,
277 | [payload.id]: {
278 | nestedLevel: payload.nestedLevel,
279 | defaultType: payload.defaultType,
280 | type: payload.defaultType,
281 | attach: undefined,
282 | isOpen: payload.defaultOpen,
283 | alwaysOpen: payload.alwaysOpen,
284 | },
285 | },
286 | };
287 | case 'unregisterPicker': {
288 | const { [payload]: _, ...pickers } = state.pickers;
289 | return { ...state, pickers };
290 | }
291 | case 'select': {
292 | const newState = payload.action
293 | ? runAction(state, {
294 | action: payload.action,
295 | pickerId: payload.pickerId,
296 | })
297 | : { ...state };
298 |
299 | let newValue: Date | null = null;
300 |
301 | switch (payload.item.type) {
302 | case 'day': {
303 | newValue = new Date(payload.item.value);
304 | newValue.setHours(state.hour, state.minute);
305 |
306 | break;
307 | }
308 | case 'month': {
309 | newState.month = payload.item.value;
310 | break;
311 | }
312 | case 'year': {
313 | newState.year = payload.item.value;
314 | break;
315 | }
316 | case 'hour': {
317 | newValue = state.valueRef.current
318 | ? new Date(state.valueRef.current)
319 | : new Date();
320 |
321 | newValue.setHours(payload.item.value);
322 |
323 | newState.hour = payload.item.value;
324 | break;
325 | }
326 | case 'minute': {
327 | newValue = state.valueRef.current
328 | ? new Date(state.valueRef.current)
329 | : new Date();
330 |
331 | newValue.setMinutes(payload.item.value);
332 |
333 | newState.minute = payload.item.value;
334 | break;
335 | }
336 | default: {
337 | return state;
338 | }
339 | }
340 |
341 | if (newValue) {
342 | newState.onChange(newValue);
343 | }
344 |
345 | return newState;
346 | }
347 | case 'defaultChanged': {
348 | return {
349 | ...state,
350 | ...payload,
351 | };
352 | }
353 | case 'externalValueChanged': {
354 | const parts = state.config.toDateParts(payload);
355 |
356 | return {
357 | ...state,
358 | year: parts.year,
359 | month: parts.month,
360 | hour: payload.getHours(),
361 | minute: payload.getMinutes(),
362 | };
363 | }
364 | default:
365 | throw new Error('Invalid action ' + type);
366 | }
367 | };
368 |
369 | function runAction(
370 | state: DatepickerState,
371 | payload: Extract['payload'],
372 | ): DatepickerState {
373 | let type = payload.action;
374 | let index = '';
375 |
376 | const _match = payload.action.match(
377 | /^(open|close|next|prev|showYear|showMonth|showDay|toggleYear|toggleMonth|toggleDay|toggle)(.*)$/,
378 | );
379 | if (_match) {
380 | type = _match[1] as Action;
381 | index = _match[2];
382 |
383 | if (index === '') {
384 | index =
385 | (payload as any).pickerId || Object.keys(state.pickers).reverse()[0];
386 |
387 | if (index === undefined) {
388 | throw new Error('There is no Picker in the current Provider');
389 | }
390 | }
391 | }
392 |
393 | switch (type) {
394 | case 'open':
395 | return {
396 | ...state,
397 | pickers: {
398 | ...state.pickers,
399 | [index]: {
400 | ...state.pickers[index],
401 | attach: payload.ref,
402 | isOpen: true,
403 | },
404 | },
405 | };
406 | case 'close':
407 | return {
408 | ...state,
409 | pickers: {
410 | ...state.pickers,
411 | [index]: {
412 | ...state.pickers[index],
413 | isOpen: false,
414 | type: state.pickers[index].defaultType,
415 | },
416 | },
417 | };
418 | case 'toggle':
419 | return {
420 | ...state,
421 | pickers: {
422 | ...state.pickers,
423 | [index]: {
424 | ...state.pickers[index],
425 | attach: payload.ref,
426 | isOpen: !state.pickers[index].isOpen,
427 | type: state.pickers[index].defaultType,
428 | },
429 | },
430 | };
431 | case 'next': {
432 | if (!state.pickers[index].type) return state;
433 |
434 | const { month, year } = state;
435 | return match(state.pickers[index].type!, {
436 | hour: () => state,
437 | minute: () => state,
438 | day: () => ({
439 | ...state,
440 | year: month === 12 ? year + 1 : year,
441 | month: (month % 12) + 1,
442 | }),
443 | month: () => ({
444 | ...state,
445 | year: year + 1,
446 | month: month,
447 | }),
448 | year: () => ({
449 | ...state,
450 | year: year + 1,
451 | month: month,
452 | }),
453 | });
454 | }
455 | case 'prev': {
456 | if (!state.pickers[index].type) return state;
457 |
458 | const { month, year } = state;
459 |
460 | return match(state.pickers[index].type!, {
461 | hour: () => state,
462 | minute: () => state,
463 | day: () => ({
464 | ...state,
465 | year: month === 1 ? year - 1 : year,
466 | month: mod(month - 2, 12) + 1,
467 | }),
468 | month: () => ({
469 | ...state,
470 | year: year - 1,
471 | month: month,
472 | }),
473 | year: () => ({
474 | ...state,
475 | year: year - 1,
476 | month: month,
477 | }),
478 | });
479 | }
480 | case 'showYear': {
481 | return {
482 | ...state,
483 | pickers: {
484 | ...state.pickers,
485 | [index]: {
486 | ...state.pickers[index],
487 | type: 'year',
488 | },
489 | },
490 | };
491 | }
492 | case 'toggleYear': {
493 | return {
494 | ...state,
495 | pickers: {
496 | ...state.pickers,
497 | [index]: {
498 | ...state.pickers[index],
499 | type:
500 | state.pickers[index].type === 'year'
501 | ? state.pickers[index].defaultType
502 | : 'year',
503 | },
504 | },
505 | };
506 | }
507 | case 'showMonth': {
508 | return {
509 | ...state,
510 | pickers: {
511 | ...state.pickers,
512 | [index]: {
513 | ...state.pickers[index],
514 | type: 'month',
515 | },
516 | },
517 | };
518 | }
519 | case 'toggleMonth': {
520 | return {
521 | ...state,
522 | pickers: {
523 | ...state.pickers,
524 | [index]: {
525 | ...state.pickers[index],
526 | type:
527 | state.pickers[index].type === 'month'
528 | ? state.pickers[index].defaultType
529 | : 'month',
530 | },
531 | },
532 | };
533 | }
534 | case 'showDay': {
535 | return {
536 | ...state,
537 | pickers: {
538 | ...state.pickers,
539 | [index]: {
540 | ...state.pickers[index],
541 | type: 'day',
542 | },
543 | },
544 | };
545 | }
546 | case 'toggleDay': {
547 | return {
548 | ...state,
549 | pickers: {
550 | ...state.pickers,
551 | [index]: {
552 | ...state.pickers[index],
553 | type:
554 | state.pickers[index].type === 'day'
555 | ? state.pickers[index].defaultType
556 | : 'day',
557 | },
558 | },
559 | };
560 | }
561 | case 'today': {
562 | const today = new Date();
563 | today.setHours(state.hour, state.minute);
564 | state.onChange(today);
565 |
566 | const parts = state.config.toDateParts(today);
567 |
568 | return {
569 | ...state,
570 | year: parts.year,
571 | month: parts.month,
572 | };
573 | }
574 | case 'todayHour': {
575 | const today = new Date();
576 | state.onChange(today);
577 |
578 | const parts = state.config.toDateParts(today);
579 |
580 | return {
581 | ...state,
582 | year: parts.year,
583 | month: parts.month,
584 | };
585 | }
586 | default: {
587 | throw new Error('Invalid action ' + payload.action);
588 | }
589 | }
590 | }
591 |
592 | export const DatepickerContext = createContext<{
593 | state: DatepickerState;
594 | dispatch: (action: DatepickerContextActions) => void;
595 | } | null>(null);
596 |
597 | export function useDatepickerContext() {
598 | const context = useContext(DatepickerContext);
599 | if (!context) throw new Error('You need to use component inside Datepicker');
600 | return context;
601 | }
602 |
603 | export function useDatepickerSlot() {
604 | const context = useDatepickerContext();
605 | return {
606 | ...context,
607 | slot: getSlot(context.state),
608 | };
609 | }
610 |
611 | export function getSlot(state: DatepickerState): DatepickerSlot {
612 | return {
613 | pickers: state.pickers,
614 | disabled: state.disabled,
615 |
616 | value: state.valueRef.current,
617 | month: state.month,
618 | monthName: state.config.monthNames[state.month - 1],
619 | year: state.year,
620 | hour: state.hour,
621 | minute: state.minute,
622 | };
623 | }
624 |
--------------------------------------------------------------------------------