@kalyx/core 0.2.0 → 0.3.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/README.md +76 -8
- package/dist/index.cjs +151 -20
- package/dist/index.d.cts +167 -75
- package/dist/index.d.ts +348 -9
- package/dist/index.js +538 -9
- package/package.json +20 -1
- package/dist/adapters/date-fns.d.ts +0 -16
- package/dist/adapters/date-fns.d.ts.map +0 -1
- package/dist/adapters/date-fns.js +0 -148
- package/dist/adapters/date-fns.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/types.d.ts +0 -106
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -8
- package/dist/types.js.map +0 -1
- package/dist/utils/calendar.d.ts +0 -35
- package/dist/utils/calendar.d.ts.map +0 -1
- package/dist/utils/calendar.js +0 -141
- package/dist/utils/calendar.js.map +0 -1
- package/dist/utils/date.d.ts +0 -22
- package/dist/utils/date.d.ts.map +0 -1
- package/dist/utils/date.js +0 -66
- package/dist/utils/date.js.map +0 -1
- package/dist/utils/locale.d.ts +0 -33
- package/dist/utils/locale.d.ts.map +0 -1
- package/dist/utils/locale.js +0 -70
- package/dist/utils/locale.js.map +0 -1
- package/dist/utils/time.d.ts +0 -58
- package/dist/utils/time.d.ts.map +0 -1
- package/dist/utils/time.js +0 -127
- package/dist/utils/time.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,17 +1,85 @@
|
|
|
1
1
|
# @kalyx/core
|
|
2
2
|
|
|
3
|
-
> Platform-
|
|
3
|
+
> Platform-independent date logic powering [Kalyx](https://github.com/jiji-hoon96/kalyx). Types, adapters, and UTC-safe utilities.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/@kalyx/core)
|
|
6
|
+
[](https://github.com/jiji-hoon96/kalyx/blob/main/LICENSE)
|
|
7
|
+
|
|
8
|
+
Most users should install [`@kalyx/react`](https://www.npmjs.com/package/@kalyx/react) directly — it re-exports what you need. Install `@kalyx/core` only if you're building your own picker layer or a custom platform adapter.
|
|
9
|
+
|
|
10
|
+
**📚 Full docs:** [kalyx-docs.vercel.app/docs/api/core](https://kalyx-docs.vercel.app/docs/api/core)
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pnpm add @kalyx/core
|
|
16
|
+
```
|
|
6
17
|
|
|
7
18
|
## What's inside
|
|
8
19
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
20
|
+
### Types
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import type {
|
|
24
|
+
ISODateString,
|
|
25
|
+
DisabledRule,
|
|
26
|
+
DateRange,
|
|
27
|
+
CalendarDay,
|
|
28
|
+
CalendarGrid,
|
|
29
|
+
WeekStartsOn,
|
|
30
|
+
CalendarOptions,
|
|
31
|
+
DateAdapter,
|
|
32
|
+
TimeValue,
|
|
33
|
+
} from '@kalyx/core';
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Adapter
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { DateFnsAdapter } from '@kalyx/core';
|
|
40
|
+
// UTC-safe default adapter, built on date-fns v4.
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Calendar utilities
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { getCalendarDays, isDateDisabled, minDate, maxDate } from '@kalyx/core';
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Date helpers
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import { normalizeISO, parseInputValue } from '@kalyx/core';
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Time helpers
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
import {
|
|
59
|
+
setTime, getTime,
|
|
60
|
+
parseTimeString, formatTimeString, formatTimeFromISO,
|
|
61
|
+
to12Hour, to24Hour,
|
|
62
|
+
generateHours, generateMinutes,
|
|
63
|
+
isSameTime,
|
|
64
|
+
} from '@kalyx/core';
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Locale helpers
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import {
|
|
71
|
+
getMonthName, formatMonthYear,
|
|
72
|
+
getWeekdayNames, formatFullDate,
|
|
73
|
+
} from '@kalyx/core';
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Principles
|
|
77
|
+
|
|
78
|
+
- **All dates are ISO 8601 UTC strings** — never `Date` objects.
|
|
79
|
+
- **UTC-only arithmetic** — uses `getUTC*` methods, never local-timezone variants.
|
|
80
|
+
- **Adapter abstraction** — swap date engines by implementing `DateAdapter`.
|
|
81
|
+
- **Pure functions** — zero side effects, fully testable.
|
|
14
82
|
|
|
15
83
|
## License
|
|
16
84
|
|
|
17
|
-
MIT
|
|
85
|
+
[MIT](https://github.com/jiji-hoon96/kalyx/blob/main/LICENSE)
|
package/dist/index.cjs
CHANGED
|
@@ -20,8 +20,13 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
DEFAULT_DATEPICKER_LABELS: () => DEFAULT_DATEPICKER_LABELS,
|
|
24
|
+
DEFAULT_DATETIMEPICKER_LABELS: () => DEFAULT_DATETIMEPICKER_LABELS,
|
|
25
|
+
DEFAULT_RANGEPICKER_LABELS: () => DEFAULT_RANGEPICKER_LABELS,
|
|
26
|
+
DEFAULT_TIMEPICKER_LABELS: () => DEFAULT_TIMEPICKER_LABELS,
|
|
23
27
|
DateFnsAdapter: () => DateFnsAdapter,
|
|
24
28
|
formatFullDate: () => formatFullDate,
|
|
29
|
+
formatInTimezone: () => formatInTimezone,
|
|
25
30
|
formatMonthYear: () => formatMonthYear,
|
|
26
31
|
formatTimeFromISO: () => formatTimeFromISO,
|
|
27
32
|
formatTimeString: () => formatTimeString,
|
|
@@ -30,8 +35,10 @@ __export(index_exports, {
|
|
|
30
35
|
getCalendarDays: () => getCalendarDays,
|
|
31
36
|
getMonthName: () => getMonthName,
|
|
32
37
|
getTime: () => getTime,
|
|
38
|
+
getTimezoneOffsetMinutes: () => getTimezoneOffsetMinutes,
|
|
33
39
|
getWeekdayNames: () => getWeekdayNames,
|
|
34
40
|
isDateDisabled: () => isDateDisabled,
|
|
41
|
+
isSameDayInTimezone: () => isSameDayInTimezone,
|
|
35
42
|
isSameTime: () => isSameTime,
|
|
36
43
|
maxDate: () => maxDate,
|
|
37
44
|
minDate: () => minDate,
|
|
@@ -39,8 +46,10 @@ __export(index_exports, {
|
|
|
39
46
|
parseInputValue: () => parseInputValue,
|
|
40
47
|
parseTimeString: () => parseTimeString,
|
|
41
48
|
setTime: () => setTime,
|
|
49
|
+
startOfDayInTimezone: () => startOfDayInTimezone,
|
|
42
50
|
to12Hour: () => to12Hour,
|
|
43
|
-
to24Hour: () => to24Hour
|
|
51
|
+
to24Hour: () => to24Hour,
|
|
52
|
+
todayInTimezone: () => todayInTimezone
|
|
44
53
|
});
|
|
45
54
|
module.exports = __toCommonJS(index_exports);
|
|
46
55
|
|
|
@@ -275,14 +284,14 @@ function maxDate(a, b, adapter) {
|
|
|
275
284
|
|
|
276
285
|
// src/utils/date.ts
|
|
277
286
|
var ISO_DATE_REGEX = /^\d{4}-\d{2}-\d{2}$/;
|
|
278
|
-
var ISO_DATETIME_REGEX = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}
|
|
287
|
+
var ISO_DATETIME_REGEX = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/;
|
|
279
288
|
function normalizeISO(value) {
|
|
280
289
|
if (!value) return "";
|
|
281
290
|
if (ISO_DATETIME_REGEX.test(value)) return value;
|
|
282
291
|
if (ISO_DATE_REGEX.test(value)) return `${value}T00:00:00.000Z`;
|
|
283
292
|
return value;
|
|
284
293
|
}
|
|
285
|
-
function parseInputValue(input,
|
|
294
|
+
function parseInputValue(input, adapter) {
|
|
286
295
|
if (!input.trim()) return null;
|
|
287
296
|
const cleaned = input.replace(/\//g, "-").trim();
|
|
288
297
|
if (ISO_DATE_REGEX.test(cleaned)) {
|
|
@@ -357,8 +366,8 @@ function generateHours(format = "24h") {
|
|
|
357
366
|
return Array.from({ length: 24 }, (_, i) => i);
|
|
358
367
|
}
|
|
359
368
|
function generateMinutes(step = 1) {
|
|
360
|
-
if (step < 1 || step >
|
|
361
|
-
throw new Error(`[generateMinutes] step must be between 1 and
|
|
369
|
+
if (step < 1 || step > 30) {
|
|
370
|
+
throw new Error(`[generateMinutes] step must be between 1 and 30, got ${step}`);
|
|
362
371
|
}
|
|
363
372
|
const result = [];
|
|
364
373
|
for (let i = 0; i < 60; i += step) {
|
|
@@ -369,7 +378,7 @@ function generateMinutes(step = 1) {
|
|
|
369
378
|
function isSameTime(a, b) {
|
|
370
379
|
return a.hours === b.hours && a.minutes === b.minutes && a.seconds === b.seconds;
|
|
371
380
|
}
|
|
372
|
-
function formatTimeFromISO(iso, format
|
|
381
|
+
function formatTimeFromISO(iso, format) {
|
|
373
382
|
const time = getTime(iso);
|
|
374
383
|
if (format === "h:mm a" || format === "h:mm:ss a") {
|
|
375
384
|
const { hours12, period } = to12Hour(time.hours);
|
|
@@ -384,30 +393,35 @@ function formatTimeFromISO(iso, format, _adapter) {
|
|
|
384
393
|
}
|
|
385
394
|
|
|
386
395
|
// src/utils/locale.ts
|
|
396
|
+
var formatterCache = /* @__PURE__ */ new Map();
|
|
397
|
+
function getCachedFormatter(locale, options) {
|
|
398
|
+
const key = `${locale}:${JSON.stringify(options)}`;
|
|
399
|
+
let fmt = formatterCache.get(key);
|
|
400
|
+
if (!fmt) {
|
|
401
|
+
fmt = new Intl.DateTimeFormat(locale, options);
|
|
402
|
+
formatterCache.set(key, fmt);
|
|
403
|
+
}
|
|
404
|
+
return fmt;
|
|
405
|
+
}
|
|
406
|
+
var REFERENCE_YEAR = 2026;
|
|
387
407
|
function getMonthName(month, locale = "en-US") {
|
|
388
|
-
const date = new Date(Date.UTC(
|
|
389
|
-
return
|
|
408
|
+
const date = new Date(Date.UTC(REFERENCE_YEAR, month, 1));
|
|
409
|
+
return getCachedFormatter(locale, { month: "long", timeZone: "UTC" }).format(date);
|
|
390
410
|
}
|
|
391
411
|
function formatMonthYear(year, month, locale = "en-US") {
|
|
392
412
|
const date = new Date(Date.UTC(year, month, 1));
|
|
393
|
-
return
|
|
413
|
+
return getCachedFormatter(locale, {
|
|
394
414
|
year: "numeric",
|
|
395
415
|
month: "long",
|
|
396
416
|
timeZone: "UTC"
|
|
397
417
|
}).format(date);
|
|
398
418
|
}
|
|
399
419
|
function getWeekdayNames(locale = "en-US", weekStartsOn = 0) {
|
|
400
|
-
const shortFormatter =
|
|
401
|
-
|
|
402
|
-
timeZone: "UTC"
|
|
403
|
-
});
|
|
404
|
-
const fullFormatter = new Intl.DateTimeFormat(locale, {
|
|
405
|
-
weekday: "long",
|
|
406
|
-
timeZone: "UTC"
|
|
407
|
-
});
|
|
420
|
+
const shortFormatter = getCachedFormatter(locale, { weekday: "short", timeZone: "UTC" });
|
|
421
|
+
const fullFormatter = getCachedFormatter(locale, { weekday: "long", timeZone: "UTC" });
|
|
408
422
|
const days = [];
|
|
409
423
|
for (let i = 0; i < 7; i++) {
|
|
410
|
-
const date = new Date(Date.UTC(
|
|
424
|
+
const date = new Date(Date.UTC(REFERENCE_YEAR, 0, 4 + i));
|
|
411
425
|
days.push({
|
|
412
426
|
short: shortFormatter.format(date),
|
|
413
427
|
full: fullFormatter.format(date)
|
|
@@ -421,7 +435,7 @@ function getWeekdayNames(locale = "en-US", weekStartsOn = 0) {
|
|
|
421
435
|
}
|
|
422
436
|
function formatFullDate(iso, locale = "en-US") {
|
|
423
437
|
const date = new Date(iso);
|
|
424
|
-
return
|
|
438
|
+
return getCachedFormatter(locale, {
|
|
425
439
|
year: "numeric",
|
|
426
440
|
month: "long",
|
|
427
441
|
day: "numeric",
|
|
@@ -429,10 +443,123 @@ function formatFullDate(iso, locale = "en-US") {
|
|
|
429
443
|
timeZone: "UTC"
|
|
430
444
|
}).format(date);
|
|
431
445
|
}
|
|
446
|
+
|
|
447
|
+
// src/utils/timezone.ts
|
|
448
|
+
var import_date_fns2 = require("date-fns");
|
|
449
|
+
var formatterCache2 = /* @__PURE__ */ new Map();
|
|
450
|
+
function getCachedPartsFormatter(timeZone) {
|
|
451
|
+
const key = `parts:${timeZone}`;
|
|
452
|
+
let fmt = formatterCache2.get(key);
|
|
453
|
+
if (!fmt) {
|
|
454
|
+
fmt = new Intl.DateTimeFormat("en-US", {
|
|
455
|
+
timeZone,
|
|
456
|
+
year: "numeric",
|
|
457
|
+
month: "2-digit",
|
|
458
|
+
day: "2-digit",
|
|
459
|
+
hour: "2-digit",
|
|
460
|
+
minute: "2-digit",
|
|
461
|
+
second: "2-digit",
|
|
462
|
+
hourCycle: "h23"
|
|
463
|
+
});
|
|
464
|
+
formatterCache2.set(key, fmt);
|
|
465
|
+
}
|
|
466
|
+
return fmt;
|
|
467
|
+
}
|
|
468
|
+
function partsInTimezone(utc, timeZone) {
|
|
469
|
+
const dtf = getCachedPartsFormatter(timeZone);
|
|
470
|
+
const parts = Object.fromEntries(
|
|
471
|
+
dtf.formatToParts(utc).map((p) => [p.type, p.value])
|
|
472
|
+
);
|
|
473
|
+
return {
|
|
474
|
+
year: Number(parts.year),
|
|
475
|
+
month: Number(parts.month),
|
|
476
|
+
day: Number(parts.day),
|
|
477
|
+
// Some locales/engines return '24' instead of '0' at midnight
|
|
478
|
+
hour: parts.hour === "24" ? 0 : Number(parts.hour),
|
|
479
|
+
minute: Number(parts.minute),
|
|
480
|
+
second: Number(parts.second)
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
function formatInTimezone(iso, formatStr, timeZone) {
|
|
484
|
+
const p = partsInTimezone((0, import_date_fns2.parseISO)(iso), timeZone);
|
|
485
|
+
const tokens = {
|
|
486
|
+
yyyy: String(p.year),
|
|
487
|
+
MM: String(p.month).padStart(2, "0"),
|
|
488
|
+
dd: String(p.day).padStart(2, "0"),
|
|
489
|
+
HH: String(p.hour).padStart(2, "0"),
|
|
490
|
+
mm: String(p.minute).padStart(2, "0"),
|
|
491
|
+
ss: String(p.second).padStart(2, "0")
|
|
492
|
+
};
|
|
493
|
+
let result = formatStr;
|
|
494
|
+
for (const [token, value] of Object.entries(tokens).sort((a, b) => b[0].length - a[0].length)) {
|
|
495
|
+
result = result.split(token).join(value);
|
|
496
|
+
}
|
|
497
|
+
return result;
|
|
498
|
+
}
|
|
499
|
+
function getTimezoneOffsetMinutes(iso, timeZone) {
|
|
500
|
+
const utc = (0, import_date_fns2.parseISO)(iso);
|
|
501
|
+
const p = partsInTimezone(utc, timeZone);
|
|
502
|
+
const asUtcEpoch = Date.UTC(p.year, p.month - 1, p.day, p.hour, p.minute, p.second);
|
|
503
|
+
return Math.round((asUtcEpoch - utc.getTime()) / 6e4);
|
|
504
|
+
}
|
|
505
|
+
function startOfDayInTimezone(iso, timeZone) {
|
|
506
|
+
const utc = (0, import_date_fns2.parseISO)(iso);
|
|
507
|
+
const p = partsInTimezone(utc, timeZone);
|
|
508
|
+
const civilMidnightUtc = Date.UTC(p.year, p.month - 1, p.day, 0, 0, 0);
|
|
509
|
+
const midnightProbe = new Date(civilMidnightUtc).toISOString();
|
|
510
|
+
const offsetMinutes = getTimezoneOffsetMinutes(midnightProbe, timeZone);
|
|
511
|
+
return new Date(civilMidnightUtc - offsetMinutes * 6e4).toISOString();
|
|
512
|
+
}
|
|
513
|
+
function isSameDayInTimezone(a, b, timeZone) {
|
|
514
|
+
const pa = partsInTimezone((0, import_date_fns2.parseISO)(a), timeZone);
|
|
515
|
+
const pb = partsInTimezone((0, import_date_fns2.parseISO)(b), timeZone);
|
|
516
|
+
return pa.year === pb.year && pa.month === pb.month && pa.day === pb.day;
|
|
517
|
+
}
|
|
518
|
+
function todayInTimezone(timeZone) {
|
|
519
|
+
return startOfDayInTimezone((/* @__PURE__ */ new Date()).toISOString(), timeZone);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// src/utils/labels.ts
|
|
523
|
+
var DEFAULT_DATEPICKER_LABELS = {
|
|
524
|
+
triggerOpen: "Open calendar",
|
|
525
|
+
triggerClose: "Close calendar",
|
|
526
|
+
popoverLabel: "Choose date",
|
|
527
|
+
prevMonth: "Previous month",
|
|
528
|
+
nextMonth: "Next month",
|
|
529
|
+
prevYear: "Previous year",
|
|
530
|
+
nextYear: "Next year",
|
|
531
|
+
prevDecade: "Previous decade",
|
|
532
|
+
nextDecade: "Next decade"
|
|
533
|
+
};
|
|
534
|
+
var DEFAULT_RANGEPICKER_LABELS = {
|
|
535
|
+
...DEFAULT_DATEPICKER_LABELS,
|
|
536
|
+
popoverLabel: "Choose date range",
|
|
537
|
+
startInput: "Start date",
|
|
538
|
+
endInput: "End date",
|
|
539
|
+
presetsGroup: "Date range presets"
|
|
540
|
+
};
|
|
541
|
+
var DEFAULT_TIMEPICKER_LABELS = {
|
|
542
|
+
timeInput: "Time",
|
|
543
|
+
hourList: "Hour",
|
|
544
|
+
minuteList: "Minute",
|
|
545
|
+
amPmToggle: "AM/PM",
|
|
546
|
+
hourOption: (hour) => `${hour} hours`,
|
|
547
|
+
minuteOption: (minute) => `${minute} minutes`
|
|
548
|
+
};
|
|
549
|
+
var DEFAULT_DATETIMEPICKER_LABELS = {
|
|
550
|
+
...DEFAULT_DATEPICKER_LABELS,
|
|
551
|
+
...DEFAULT_TIMEPICKER_LABELS,
|
|
552
|
+
dateTimeInput: "Date and time"
|
|
553
|
+
};
|
|
432
554
|
// Annotate the CommonJS export names for ESM import in node:
|
|
433
555
|
0 && (module.exports = {
|
|
556
|
+
DEFAULT_DATEPICKER_LABELS,
|
|
557
|
+
DEFAULT_DATETIMEPICKER_LABELS,
|
|
558
|
+
DEFAULT_RANGEPICKER_LABELS,
|
|
559
|
+
DEFAULT_TIMEPICKER_LABELS,
|
|
434
560
|
DateFnsAdapter,
|
|
435
561
|
formatFullDate,
|
|
562
|
+
formatInTimezone,
|
|
436
563
|
formatMonthYear,
|
|
437
564
|
formatTimeFromISO,
|
|
438
565
|
formatTimeString,
|
|
@@ -441,8 +568,10 @@ function formatFullDate(iso, locale = "en-US") {
|
|
|
441
568
|
getCalendarDays,
|
|
442
569
|
getMonthName,
|
|
443
570
|
getTime,
|
|
571
|
+
getTimezoneOffsetMinutes,
|
|
444
572
|
getWeekdayNames,
|
|
445
573
|
isDateDisabled,
|
|
574
|
+
isSameDayInTimezone,
|
|
446
575
|
isSameTime,
|
|
447
576
|
maxDate,
|
|
448
577
|
minDate,
|
|
@@ -450,6 +579,8 @@ function formatFullDate(iso, locale = "en-US") {
|
|
|
450
579
|
parseInputValue,
|
|
451
580
|
parseTimeString,
|
|
452
581
|
setTime,
|
|
582
|
+
startOfDayInTimezone,
|
|
453
583
|
to12Hour,
|
|
454
|
-
to24Hour
|
|
584
|
+
to24Hour,
|
|
585
|
+
todayInTimezone
|
|
455
586
|
});
|