@kalyx/core 1.0.0-rc.6 → 1.0.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/dist/index.d.ts CHANGED
@@ -15,7 +15,8 @@ type ISODateString = string;
15
15
  * { before: '2026-01-01T00:00:00.000Z' }, // disable before Jan 1
16
16
  * { after: '2026-12-31T00:00:00.000Z' }, // disable after Dec 31
17
17
  * { date: '2026-06-15T00:00:00.000Z' }, // disable a specific date
18
- * { dayOfWeek: [0, 6] }, // disable weekends
18
+ * { dayOfWeek: [0, 6] }, // disable weekends
19
+ * { filter: (iso) => holidays.has(iso) }, // programmatic filter
19
20
  * ];
20
21
  * ```
21
22
  */
@@ -27,6 +28,8 @@ type DisabledRule = {
27
28
  after: ISODateString;
28
29
  } | {
29
30
  dayOfWeek: number[];
31
+ } | {
32
+ filter: (iso: ISODateString) => boolean;
30
33
  };
31
34
  /** Date range (RangePicker) */
32
35
  interface DateRange {
@@ -108,23 +111,14 @@ interface CalendarOptions {
108
111
  * midnight form while the grid iterates in UTC.
109
112
  */
110
113
  timezone?: string;
114
+ /**
115
+ * Always emit exactly six weeks (42 days). The default (`false`) emits 4–6 weeks
116
+ * depending on the month, breaking after the last week containing a current-month day.
117
+ * Setting `true` is useful for layouts that need a fixed-height grid.
118
+ */
119
+ fixedWeeks?: boolean;
111
120
  }
112
121
 
113
- /**
114
- * DateAdapter implementation backed by date-fns.
115
- * All operations run in UTC to avoid timezone interference.
116
- *
117
- * @example
118
- * ```ts
119
- * import { DateFnsAdapter } from '@kalyx/core';
120
- *
121
- * DateFnsAdapter.format('2026-01-15T00:00:00.000Z', 'yyyy-MM-dd'); // "2026-01-15"
122
- * DateFnsAdapter.addDays('2026-01-15T00:00:00.000Z', 7); // "2026-01-22T..."
123
- * DateFnsAdapter.isSameDay('2026-01-15T00:00:00.000Z', '2026-01-15T23:59:59.000Z'); // true
124
- * ```
125
- */
126
- declare const DateFnsAdapter: DateAdapter;
127
-
128
122
  /**
129
123
  * Builds the calendar grid for the given month.
130
124
  * Returns a 2D array (`CalendarGrid`) organized by week.
@@ -146,6 +140,16 @@ declare const DateFnsAdapter: DateAdapter;
146
140
  * ```
147
141
  */
148
142
  declare function getCalendarDays(monthISO: string, adapter: DateAdapter, options?: CalendarOptions): CalendarGrid;
143
+ /**
144
+ * Returns the ISO 8601 week number (1-53) of the given date. ISO weeks start on Monday;
145
+ * week 1 is the week containing the year's first Thursday. The computation works in UTC
146
+ * to match the calendar grid's iteration semantics.
147
+ *
148
+ * @example
149
+ * getISOWeekNumber('2026-01-01T00:00:00.000Z'); // 1 (Jan 1 2026 is a Thursday → ISO week 1)
150
+ * getISOWeekNumber('2026-12-31T00:00:00.000Z'); // 53
151
+ */
152
+ declare function getISOWeekNumber(iso: ISODateString): number;
149
153
  /**
150
154
  * Checks whether the given date matches any disable rule.
151
155
  */
@@ -199,7 +203,13 @@ declare function parseTimeString(input: string): TimeValue | null;
199
203
  declare function formatTimeString(time: TimeValue, withSeconds?: boolean): string;
200
204
  /**
201
205
  * Converts a 24-hour value to 12-hour form.
202
- * 0 → 12 AM, 12 → 12 PM
206
+ *
207
+ * - `0` → `{ hours12: 12, period: 'AM' }` (midnight)
208
+ * - `12` → `{ hours12: 12, period: 'PM' }` (noon)
209
+ *
210
+ * Throws if `hours24` isn't an integer in `[0, 23]`. The previous silent
211
+ * modulo behaviour mapped invalid inputs (e.g. `24`, `25`, `-1`) onto
212
+ * arbitrary valid-looking outputs, which masked caller bugs.
203
213
  */
204
214
  declare function to12Hour(hours24: number): {
205
215
  hours12: number;
@@ -207,6 +217,10 @@ declare function to12Hour(hours24: number): {
207
217
  };
208
218
  /**
209
219
  * Converts a 12-hour value to 24-hour form.
220
+ *
221
+ * Throws if `hours12` isn't an integer in `[1, 12]`. The previous silent
222
+ * arithmetic produced out-of-range outputs (e.g. `to24Hour(13, 'PM')` → `25`).
223
+ * `period` is constrained at the type level to `'AM' | 'PM'`.
210
224
  */
211
225
  declare function to24Hour(hours12: number, period: 'AM' | 'PM'): number;
212
226
  /**
@@ -215,9 +229,15 @@ declare function to24Hour(hours12: number, period: 'AM' | 'PM'): number;
215
229
  declare function generateHours(format?: '12h' | '24h'): number[];
216
230
  /**
217
231
  * Builds a minutes list at the given step.
218
- * step=1 → [0, 1, 2, ..., 59]
219
- * step=15 → [0, 15, 30, 45]
220
- * step=5 → [0, 5, 10, ..., 55]
232
+ *
233
+ * - `step=1``[0, 1, 2, ..., 59]`
234
+ * - `step=15``[0, 15, 30, 45]`
235
+ * - `step=5` → `[0, 5, 10, ..., 55]`
236
+ * - `step=45` → `[0, 45]`
237
+ * - `step=60` → `[0]` (on-the-hour only)
238
+ *
239
+ * Steps above 60 are rejected because they always collapse to `[0]` — useful UX
240
+ * is impossible past that point.
221
241
  */
222
242
  declare function generateMinutes(step?: number): number[];
223
243
  /**
@@ -380,6 +400,16 @@ interface RangePickerLabels extends DatePickerLabels {
380
400
  startInput: string;
381
401
  endInput: string;
382
402
  presetsGroup: string;
403
+ /**
404
+ * Screen-reader prompt appended after the start date is picked, telling the user
405
+ * the next click commits the end of the range.
406
+ */
407
+ selectingEnd: string;
408
+ /**
409
+ * Screen-reader prefix announced when both endpoints are committed, e.g.
410
+ * `"Range selected: Jan 5, 2026 – Jan 12, 2026"`.
411
+ */
412
+ rangeSelected: string;
383
413
  }
384
414
  interface TimePickerLabels {
385
415
  timeInput: string;
@@ -397,4 +427,4 @@ declare const DEFAULT_RANGEPICKER_LABELS: RangePickerLabels;
397
427
  declare const DEFAULT_TIMEPICKER_LABELS: TimePickerLabels;
398
428
  declare const DEFAULT_DATETIMEPICKER_LABELS: DateTimePickerLabels;
399
429
 
400
- export { type CalendarDay, type CalendarGrid, type CalendarOptions, type CalendarWeek, DEFAULT_DATEPICKER_LABELS, DEFAULT_DATETIMEPICKER_LABELS, DEFAULT_RANGEPICKER_LABELS, DEFAULT_TIMEPICKER_LABELS, type DateAdapter, DateFnsAdapter, type DatePickerLabels, type DateRange, type DateTimePickerLabels, type DisabledRule, type ISODateString, type RangePickerLabels, type TimePickerLabels, type TimeValue, type WeekStartsOn, type WeekdayInfo, civilMidnightFromUtcDay, formatFullDate, formatInTimezone, formatMonthYear, formatTimeFromISO, formatTimeString, generateHours, generateMinutes, getCalendarDays, getMonthName, getTime, getTimeInTimezone, getTimezoneOffsetMinutes, getWeekdayNames, isDateDisabled, isSameDayInTimezone, isSameTime, maxDate, minDate, normalizeISO, parseInputValue, parseTimeString, setTime, setTimeInTimezone, startOfDayInTimezone, to12Hour, to24Hour, todayInTimezone };
430
+ export { type CalendarDay, type CalendarGrid, type CalendarOptions, type CalendarWeek, DEFAULT_DATEPICKER_LABELS, DEFAULT_DATETIMEPICKER_LABELS, DEFAULT_RANGEPICKER_LABELS, DEFAULT_TIMEPICKER_LABELS, type DateAdapter, type DatePickerLabels, type DateRange, type DateTimePickerLabels, type DisabledRule, type ISODateString, type RangePickerLabels, type TimePickerLabels, type TimeValue, type WeekStartsOn, type WeekdayInfo, civilMidnightFromUtcDay, formatFullDate, formatInTimezone, formatMonthYear, formatTimeFromISO, formatTimeString, generateHours, generateMinutes, getCalendarDays, getISOWeekNumber, getMonthName, getTime, getTimeInTimezone, getTimezoneOffsetMinutes, getWeekdayNames, isDateDisabled, isSameDayInTimezone, isSameTime, maxDate, minDate, normalizeISO, parseInputValue, parseTimeString, setTime, setTimeInTimezone, startOfDayInTimezone, to12Hour, to24Hour, todayInTimezone };
package/dist/index.js CHANGED
@@ -1,254 +1,3 @@
1
- // src/adapters/date-fns.ts
2
- import {
3
- parseISO as parseISO2,
4
- addDays as dfAddDays,
5
- addMonths as dfAddMonths,
6
- addYears as dfAddYears,
7
- isBefore as dfIsBefore,
8
- isAfter as dfIsAfter,
9
- isValid as dfIsValid
10
- } from "date-fns";
11
-
12
- // src/utils/timezone.ts
13
- import { parseISO } from "date-fns";
14
- var formatterCache = /* @__PURE__ */ new Map();
15
- function getCachedPartsFormatter(timeZone) {
16
- const key = `parts:${timeZone}`;
17
- let fmt = formatterCache.get(key);
18
- if (!fmt) {
19
- fmt = new Intl.DateTimeFormat("en-US", {
20
- timeZone,
21
- year: "numeric",
22
- month: "2-digit",
23
- day: "2-digit",
24
- hour: "2-digit",
25
- minute: "2-digit",
26
- second: "2-digit",
27
- hourCycle: "h23"
28
- });
29
- formatterCache.set(key, fmt);
30
- }
31
- return fmt;
32
- }
33
- function partsInTimezone(utc, timeZone) {
34
- const dtf = getCachedPartsFormatter(timeZone);
35
- const parts = Object.fromEntries(dtf.formatToParts(utc).map((p) => [p.type, p.value]));
36
- return {
37
- year: Number(parts.year),
38
- month: Number(parts.month),
39
- day: Number(parts.day),
40
- // Some locales/engines return '24' instead of '0' at midnight
41
- hour: parts.hour === "24" ? 0 : Number(parts.hour),
42
- minute: Number(parts.minute),
43
- second: Number(parts.second)
44
- };
45
- }
46
- function formatInTimezone(iso, formatStr, timeZone) {
47
- const p = partsInTimezone(parseISO(iso), timeZone);
48
- const tokens = {
49
- yyyy: String(p.year),
50
- MM: String(p.month).padStart(2, "0"),
51
- dd: String(p.day).padStart(2, "0"),
52
- HH: String(p.hour).padStart(2, "0"),
53
- mm: String(p.minute).padStart(2, "0"),
54
- ss: String(p.second).padStart(2, "0")
55
- };
56
- let result = formatStr;
57
- for (const [token, value] of Object.entries(tokens).sort((a, b) => b[0].length - a[0].length)) {
58
- result = result.split(token).join(value);
59
- }
60
- return result;
61
- }
62
- function getTimezoneOffsetMinutes(iso, timeZone) {
63
- const utc = parseISO(iso);
64
- const p = partsInTimezone(utc, timeZone);
65
- const asUtcEpoch = Date.UTC(p.year, p.month - 1, p.day, p.hour, p.minute, p.second);
66
- return Math.round((asUtcEpoch - utc.getTime()) / 6e4);
67
- }
68
- function startOfDayInTimezone(iso, timeZone) {
69
- const utc = parseISO(iso);
70
- const p = partsInTimezone(utc, timeZone);
71
- const civilMidnightUtc = Date.UTC(p.year, p.month - 1, p.day, 0, 0, 0);
72
- const midnightProbe = new Date(civilMidnightUtc).toISOString();
73
- const offsetMinutes = getTimezoneOffsetMinutes(midnightProbe, timeZone);
74
- return new Date(civilMidnightUtc - offsetMinutes * 6e4).toISOString();
75
- }
76
- function isSameDayInTimezone(a, b, timeZone) {
77
- const pa = partsInTimezone(parseISO(a), timeZone);
78
- const pb = partsInTimezone(parseISO(b), timeZone);
79
- return pa.year === pb.year && pa.month === pb.month && pa.day === pb.day;
80
- }
81
- function todayInTimezone(timeZone) {
82
- return startOfDayInTimezone((/* @__PURE__ */ new Date()).toISOString(), timeZone);
83
- }
84
- function civilMidnightFromUtcDay(gridUtcIso, timeZone) {
85
- const utc = parseISO(gridUtcIso);
86
- const probe = new Date(
87
- Date.UTC(utc.getUTCFullYear(), utc.getUTCMonth(), utc.getUTCDate(), 12, 0, 0)
88
- ).toISOString();
89
- return startOfDayInTimezone(probe, timeZone);
90
- }
91
- function getTimeInTimezone(iso, timeZone) {
92
- const p = partsInTimezone(parseISO(iso), timeZone);
93
- return { hours: p.hour, minutes: p.minute, seconds: p.second };
94
- }
95
- function setTimeInTimezone(iso, partial, timeZone) {
96
- const p = partsInTimezone(parseISO(iso), timeZone);
97
- const targetHours = partial.hours ?? p.hour;
98
- const targetMinutes = partial.minutes ?? p.minute;
99
- const targetSeconds = partial.seconds ?? p.second;
100
- const civilEpoch = Date.UTC(
101
- p.year,
102
- p.month - 1,
103
- p.day,
104
- targetHours,
105
- targetMinutes,
106
- targetSeconds
107
- );
108
- const probe1 = new Date(civilEpoch).toISOString();
109
- const offset1 = getTimezoneOffsetMinutes(probe1, timeZone);
110
- const realEpoch1 = civilEpoch - offset1 * 6e4;
111
- const probe2 = new Date(realEpoch1).toISOString();
112
- const offset2 = getTimezoneOffsetMinutes(probe2, timeZone);
113
- const realEpoch2 = civilEpoch - offset2 * 6e4;
114
- return new Date(realEpoch2).toISOString();
115
- }
116
-
117
- // src/adapters/date-fns.ts
118
- function toDate(iso) {
119
- return parseISO2(iso);
120
- }
121
- function toISO(date) {
122
- return date.toISOString();
123
- }
124
- function normalize(value) {
125
- if (/^\d{4}-\d{2}-\d{2}$/.test(value)) {
126
- return `${value}T00:00:00.000Z`;
127
- }
128
- return value;
129
- }
130
- function utcStartOfDay(d) {
131
- return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
132
- }
133
- function utcStartOfMonth(d) {
134
- return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), 1));
135
- }
136
- function utcEndOfMonth(d) {
137
- return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth() + 1, 0, 23, 59, 59, 999));
138
- }
139
- function utcStartOfWeek(d, weekStartsOn) {
140
- const day = d.getUTCDay();
141
- const diff = (day - weekStartsOn + 7) % 7;
142
- return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate() - diff));
143
- }
144
- function utcEndOfWeek(d, weekStartsOn) {
145
- const start = utcStartOfWeek(d, weekStartsOn);
146
- return new Date(
147
- Date.UTC(start.getUTCFullYear(), start.getUTCMonth(), start.getUTCDate() + 6, 23, 59, 59, 999)
148
- );
149
- }
150
- var DateFnsAdapter = {
151
- parse(value) {
152
- if (!value) return "";
153
- return normalize(value);
154
- },
155
- format(iso, formatStr, timezone) {
156
- if (timezone) {
157
- return formatInTimezone(iso, formatStr, timezone);
158
- }
159
- const d = toDate(iso);
160
- const tokens = {
161
- yyyy: String(d.getUTCFullYear()),
162
- MM: String(d.getUTCMonth() + 1).padStart(2, "0"),
163
- dd: String(d.getUTCDate()).padStart(2, "0"),
164
- HH: String(d.getUTCHours()).padStart(2, "0"),
165
- mm: String(d.getUTCMinutes()).padStart(2, "0"),
166
- ss: String(d.getUTCSeconds()).padStart(2, "0"),
167
- M: String(d.getUTCMonth() + 1),
168
- d: String(d.getUTCDate())
169
- };
170
- let result = formatStr;
171
- for (const [token, value] of Object.entries(tokens).sort((a, b) => b[0].length - a[0].length)) {
172
- result = result.split(token).join(value);
173
- }
174
- return result;
175
- },
176
- addDays(iso, n) {
177
- return toISO(dfAddDays(toDate(iso), n));
178
- },
179
- addMonths(iso, n) {
180
- return toISO(dfAddMonths(toDate(iso), n));
181
- },
182
- addYears(iso, n) {
183
- return toISO(dfAddYears(toDate(iso), n));
184
- },
185
- isBefore(a, b) {
186
- return dfIsBefore(toDate(a), toDate(b));
187
- },
188
- isAfter(a, b) {
189
- return dfIsAfter(toDate(a), toDate(b));
190
- },
191
- isSameDay(a, b, timezone) {
192
- if (!a || !b) return false;
193
- if (timezone) {
194
- return isSameDayInTimezone(a, b, timezone);
195
- }
196
- const da = toDate(a);
197
- const db = toDate(b);
198
- return da.getUTCFullYear() === db.getUTCFullYear() && da.getUTCMonth() === db.getUTCMonth() && da.getUTCDate() === db.getUTCDate();
199
- },
200
- isSameMonth(a, b) {
201
- const da = toDate(a);
202
- const db = toDate(b);
203
- return da.getUTCFullYear() === db.getUTCFullYear() && da.getUTCMonth() === db.getUTCMonth();
204
- },
205
- startOfDay(iso, timezone) {
206
- if (timezone) {
207
- return startOfDayInTimezone(iso, timezone);
208
- }
209
- return toISO(utcStartOfDay(toDate(iso)));
210
- },
211
- startOfMonth(iso) {
212
- return toISO(utcStartOfMonth(toDate(iso)));
213
- },
214
- endOfMonth(iso) {
215
- return toISO(utcEndOfMonth(toDate(iso)));
216
- },
217
- startOfWeek(iso, weekStartsOn = 0) {
218
- return toISO(utcStartOfWeek(toDate(iso), weekStartsOn));
219
- },
220
- endOfWeek(iso, weekStartsOn = 0) {
221
- return toISO(utcEndOfWeek(toDate(iso), weekStartsOn));
222
- },
223
- now() {
224
- return (/* @__PURE__ */ new Date()).toISOString();
225
- },
226
- today(timezone) {
227
- if (timezone) {
228
- return todayInTimezone(timezone);
229
- }
230
- return toISO(utcStartOfDay(/* @__PURE__ */ new Date()));
231
- },
232
- isValid(value) {
233
- if (!value) return false;
234
- const normalized = normalize(value);
235
- const date = parseISO2(normalized);
236
- return dfIsValid(date);
237
- },
238
- getYear(iso) {
239
- return toDate(iso).getUTCFullYear();
240
- },
241
- getMonth(iso) {
242
- return toDate(iso).getUTCMonth();
243
- },
244
- getDate(iso) {
245
- return toDate(iso).getUTCDate();
246
- },
247
- getDay(iso) {
248
- return toDate(iso).getUTCDay();
249
- }
250
- };
251
-
252
1
  // src/utils/calendar.ts
253
2
  function getCalendarDays(monthISO, adapter, options = {}) {
254
3
  const {
@@ -259,7 +8,8 @@ function getCalendarDays(monthISO, adapter, options = {}) {
259
8
  disabled = [],
260
9
  range,
261
10
  rangeHover,
262
- timezone
11
+ timezone,
12
+ fixedWeeks = false
263
13
  } = options;
264
14
  const todayISO = today ?? adapter.today(timezone);
265
15
  const monthStart = adapter.startOfMonth(monthISO);
@@ -289,12 +39,20 @@ function getCalendarDays(monthISO, adapter, options = {}) {
289
39
  current = adapter.addDays(current, 1);
290
40
  }
291
41
  weeks.push(days);
292
- if (!adapter.isSameMonth(current, monthISO) && week >= 3) {
42
+ if (!fixedWeeks && !adapter.isSameMonth(current, monthISO) && week >= 3) {
293
43
  break;
294
44
  }
295
45
  }
296
46
  return weeks;
297
47
  }
48
+ function getISOWeekNumber(iso) {
49
+ const d = new Date(iso);
50
+ const utc = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
51
+ const isoDay = utc.getUTCDay() || 7;
52
+ utc.setUTCDate(utc.getUTCDate() + 4 - isoDay);
53
+ const yearStart = new Date(Date.UTC(utc.getUTCFullYear(), 0, 1));
54
+ return Math.ceil(((utc.getTime() - yearStart.getTime()) / 864e5 + 1) / 7);
55
+ }
298
56
  function normalizeRangeForDisplay(range, hover, adapter, _timezone) {
299
57
  if (!range) return { start: null, end: null };
300
58
  const { start, end } = range;
@@ -338,6 +96,8 @@ function isDateDisabled(iso, rules, adapter) {
338
96
  if (adapter.isAfter(iso, rule.after)) return true;
339
97
  } else if ("dayOfWeek" in rule) {
340
98
  if (rule.dayOfWeek.includes(adapter.getDay(iso))) return true;
99
+ } else if ("filter" in rule) {
100
+ if (rule.filter(iso)) return true;
341
101
  }
342
102
  }
343
103
  return false;
@@ -415,12 +175,18 @@ function formatTimeString(time, withSeconds = false) {
415
175
  return `${hh}:${mm}`;
416
176
  }
417
177
  function to12Hour(hours24) {
178
+ if (!Number.isInteger(hours24) || hours24 < 0 || hours24 > 23) {
179
+ throw new RangeError(`[to12Hour] hours24 must be an integer in [0, 23], got ${hours24}`);
180
+ }
418
181
  const period = hours24 >= 12 ? "PM" : "AM";
419
182
  let hours12 = hours24 % 12;
420
183
  if (hours12 === 0) hours12 = 12;
421
184
  return { hours12, period };
422
185
  }
423
186
  function to24Hour(hours12, period) {
187
+ if (!Number.isInteger(hours12) || hours12 < 1 || hours12 > 12) {
188
+ throw new RangeError(`[to24Hour] hours12 must be an integer in [1, 12], got ${hours12}`);
189
+ }
424
190
  if (period === "AM") {
425
191
  return hours12 === 12 ? 0 : hours12;
426
192
  }
@@ -433,8 +199,8 @@ function generateHours(format = "24h") {
433
199
  return Array.from({ length: 24 }, (_, i) => i);
434
200
  }
435
201
  function generateMinutes(step = 1) {
436
- if (step < 1 || step > 30) {
437
- throw new Error(`[generateMinutes] step must be between 1 and 30, got ${step}`);
202
+ if (step < 1 || step > 60) {
203
+ throw new Error(`[generateMinutes] step must be between 1 and 60, got ${step}`);
438
204
  }
439
205
  const result = [];
440
206
  for (let i = 0; i < 60; i += step) {
@@ -460,13 +226,13 @@ function formatTimeFromISO(iso, format) {
460
226
  }
461
227
 
462
228
  // src/utils/locale.ts
463
- var formatterCache2 = /* @__PURE__ */ new Map();
229
+ var formatterCache = /* @__PURE__ */ new Map();
464
230
  function getCachedFormatter(locale, options) {
465
231
  const key = `${locale}:${JSON.stringify(options)}`;
466
- let fmt = formatterCache2.get(key);
232
+ let fmt = formatterCache.get(key);
467
233
  if (!fmt) {
468
234
  fmt = new Intl.DateTimeFormat(locale, options);
469
- formatterCache2.set(key, fmt);
235
+ formatterCache.set(key, fmt);
470
236
  }
471
237
  return fmt;
472
238
  }
@@ -511,6 +277,110 @@ function formatFullDate(iso, locale = "en-US") {
511
277
  }).format(date);
512
278
  }
513
279
 
280
+ // src/utils/timezone.ts
281
+ var formatterCache2 = /* @__PURE__ */ new Map();
282
+ function getCachedPartsFormatter(timeZone) {
283
+ const key = `parts:${timeZone}`;
284
+ let fmt = formatterCache2.get(key);
285
+ if (!fmt) {
286
+ fmt = new Intl.DateTimeFormat("en-US", {
287
+ timeZone,
288
+ year: "numeric",
289
+ month: "2-digit",
290
+ day: "2-digit",
291
+ hour: "2-digit",
292
+ minute: "2-digit",
293
+ second: "2-digit",
294
+ hourCycle: "h23"
295
+ });
296
+ formatterCache2.set(key, fmt);
297
+ }
298
+ return fmt;
299
+ }
300
+ function partsInTimezone(utc, timeZone) {
301
+ const dtf = getCachedPartsFormatter(timeZone);
302
+ const parts = Object.fromEntries(dtf.formatToParts(utc).map((p) => [p.type, p.value]));
303
+ return {
304
+ year: Number(parts.year),
305
+ month: Number(parts.month),
306
+ day: Number(parts.day),
307
+ // Some locales/engines return '24' instead of '0' at midnight
308
+ hour: parts.hour === "24" ? 0 : Number(parts.hour),
309
+ minute: Number(parts.minute),
310
+ second: Number(parts.second)
311
+ };
312
+ }
313
+ function formatInTimezone(iso, formatStr, timeZone) {
314
+ const p = partsInTimezone(new Date(iso), timeZone);
315
+ const tokens = {
316
+ yyyy: String(p.year),
317
+ MM: String(p.month).padStart(2, "0"),
318
+ dd: String(p.day).padStart(2, "0"),
319
+ HH: String(p.hour).padStart(2, "0"),
320
+ mm: String(p.minute).padStart(2, "0"),
321
+ ss: String(p.second).padStart(2, "0")
322
+ };
323
+ let result = formatStr;
324
+ for (const [token, value] of Object.entries(tokens).sort((a, b) => b[0].length - a[0].length)) {
325
+ result = result.split(token).join(value);
326
+ }
327
+ return result;
328
+ }
329
+ function getTimezoneOffsetMinutes(iso, timeZone) {
330
+ const utc = new Date(iso);
331
+ const p = partsInTimezone(utc, timeZone);
332
+ const asUtcEpoch = Date.UTC(p.year, p.month - 1, p.day, p.hour, p.minute, p.second);
333
+ return Math.round((asUtcEpoch - utc.getTime()) / 6e4);
334
+ }
335
+ function startOfDayInTimezone(iso, timeZone) {
336
+ const utc = new Date(iso);
337
+ const p = partsInTimezone(utc, timeZone);
338
+ const civilMidnightUtc = Date.UTC(p.year, p.month - 1, p.day, 0, 0, 0);
339
+ const midnightProbe = new Date(civilMidnightUtc).toISOString();
340
+ const offsetMinutes = getTimezoneOffsetMinutes(midnightProbe, timeZone);
341
+ return new Date(civilMidnightUtc - offsetMinutes * 6e4).toISOString();
342
+ }
343
+ function isSameDayInTimezone(a, b, timeZone) {
344
+ const pa = partsInTimezone(new Date(a), timeZone);
345
+ const pb = partsInTimezone(new Date(b), timeZone);
346
+ return pa.year === pb.year && pa.month === pb.month && pa.day === pb.day;
347
+ }
348
+ function todayInTimezone(timeZone) {
349
+ return startOfDayInTimezone((/* @__PURE__ */ new Date()).toISOString(), timeZone);
350
+ }
351
+ function civilMidnightFromUtcDay(gridUtcIso, timeZone) {
352
+ const utc = new Date(gridUtcIso);
353
+ const probe = new Date(
354
+ Date.UTC(utc.getUTCFullYear(), utc.getUTCMonth(), utc.getUTCDate(), 12, 0, 0)
355
+ ).toISOString();
356
+ return startOfDayInTimezone(probe, timeZone);
357
+ }
358
+ function getTimeInTimezone(iso, timeZone) {
359
+ const p = partsInTimezone(new Date(iso), timeZone);
360
+ return { hours: p.hour, minutes: p.minute, seconds: p.second };
361
+ }
362
+ function setTimeInTimezone(iso, partial, timeZone) {
363
+ const p = partsInTimezone(new Date(iso), timeZone);
364
+ const targetHours = partial.hours ?? p.hour;
365
+ const targetMinutes = partial.minutes ?? p.minute;
366
+ const targetSeconds = partial.seconds ?? p.second;
367
+ const civilEpoch = Date.UTC(
368
+ p.year,
369
+ p.month - 1,
370
+ p.day,
371
+ targetHours,
372
+ targetMinutes,
373
+ targetSeconds
374
+ );
375
+ const probe1 = new Date(civilEpoch).toISOString();
376
+ const offset1 = getTimezoneOffsetMinutes(probe1, timeZone);
377
+ const realEpoch1 = civilEpoch - offset1 * 6e4;
378
+ const probe2 = new Date(realEpoch1).toISOString();
379
+ const offset2 = getTimezoneOffsetMinutes(probe2, timeZone);
380
+ const realEpoch2 = civilEpoch - offset2 * 6e4;
381
+ return new Date(realEpoch2).toISOString();
382
+ }
383
+
514
384
  // src/utils/labels.ts
515
385
  var DEFAULT_DATEPICKER_LABELS = {
516
386
  triggerOpen: "Open calendar",
@@ -528,7 +398,9 @@ var DEFAULT_RANGEPICKER_LABELS = {
528
398
  popoverLabel: "Choose date range",
529
399
  startInput: "Start date",
530
400
  endInput: "End date",
531
- presetsGroup: "Date range presets"
401
+ presetsGroup: "Date range presets",
402
+ selectingEnd: "Now select end date",
403
+ rangeSelected: "Range selected"
532
404
  };
533
405
  var DEFAULT_TIMEPICKER_LABELS = {
534
406
  timeInput: "Time",
@@ -548,7 +420,6 @@ export {
548
420
  DEFAULT_DATETIMEPICKER_LABELS,
549
421
  DEFAULT_RANGEPICKER_LABELS,
550
422
  DEFAULT_TIMEPICKER_LABELS,
551
- DateFnsAdapter,
552
423
  civilMidnightFromUtcDay,
553
424
  formatFullDate,
554
425
  formatInTimezone,
@@ -558,6 +429,7 @@ export {
558
429
  generateHours,
559
430
  generateMinutes,
560
431
  getCalendarDays,
432
+ getISOWeekNumber,
561
433
  getMonthName,
562
434
  getTime,
563
435
  getTimeInTimezone,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kalyx/core",
3
- "version": "1.0.0-rc.6",
3
+ "version": "1.0.0",
4
4
  "description": "Kalyx core — platform-agnostic date logic, IANA timezone helpers, and the DateAdapter contract used by @kalyx/react",
5
5
  "license": "MIT",
6
6
  "author": "jiji-hoon96",
@@ -22,7 +22,7 @@
22
22
  "dst",
23
23
  "iso8601",
24
24
  "utc",
25
- "date-fns"
25
+ "adapter"
26
26
  ],
27
27
  "type": "module",
28
28
  "sideEffects": false,
@@ -46,12 +46,11 @@
46
46
  "CHANGELOG.md",
47
47
  "LICENSE"
48
48
  ],
49
- "dependencies": {
50
- "date-fns": "^4.0.0",
51
- "date-fns-tz": "^3.0.0"
49
+ "devDependencies": {
50
+ "@kalyx/adapter-date-fns": "1.0.0"
52
51
  },
53
52
  "engines": {
54
- "node": ">=18.0.0"
53
+ "node": ">=20.0.0"
55
54
  },
56
55
  "publishConfig": {
57
56
  "access": "public",