@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.cjs CHANGED
@@ -24,7 +24,6 @@ __export(index_exports, {
24
24
  DEFAULT_DATETIMEPICKER_LABELS: () => DEFAULT_DATETIMEPICKER_LABELS,
25
25
  DEFAULT_RANGEPICKER_LABELS: () => DEFAULT_RANGEPICKER_LABELS,
26
26
  DEFAULT_TIMEPICKER_LABELS: () => DEFAULT_TIMEPICKER_LABELS,
27
- DateFnsAdapter: () => DateFnsAdapter,
28
27
  civilMidnightFromUtcDay: () => civilMidnightFromUtcDay,
29
28
  formatFullDate: () => formatFullDate,
30
29
  formatInTimezone: () => formatInTimezone,
@@ -34,6 +33,7 @@ __export(index_exports, {
34
33
  generateHours: () => generateHours,
35
34
  generateMinutes: () => generateMinutes,
36
35
  getCalendarDays: () => getCalendarDays,
36
+ getISOWeekNumber: () => getISOWeekNumber,
37
37
  getMonthName: () => getMonthName,
38
38
  getTime: () => getTime,
39
39
  getTimeInTimezone: () => getTimeInTimezone,
@@ -56,249 +56,6 @@ __export(index_exports, {
56
56
  });
57
57
  module.exports = __toCommonJS(index_exports);
58
58
 
59
- // src/adapters/date-fns.ts
60
- var import_date_fns2 = require("date-fns");
61
-
62
- // src/utils/timezone.ts
63
- var import_date_fns = require("date-fns");
64
- var formatterCache = /* @__PURE__ */ new Map();
65
- function getCachedPartsFormatter(timeZone) {
66
- const key = `parts:${timeZone}`;
67
- let fmt = formatterCache.get(key);
68
- if (!fmt) {
69
- fmt = new Intl.DateTimeFormat("en-US", {
70
- timeZone,
71
- year: "numeric",
72
- month: "2-digit",
73
- day: "2-digit",
74
- hour: "2-digit",
75
- minute: "2-digit",
76
- second: "2-digit",
77
- hourCycle: "h23"
78
- });
79
- formatterCache.set(key, fmt);
80
- }
81
- return fmt;
82
- }
83
- function partsInTimezone(utc, timeZone) {
84
- const dtf = getCachedPartsFormatter(timeZone);
85
- const parts = Object.fromEntries(dtf.formatToParts(utc).map((p) => [p.type, p.value]));
86
- return {
87
- year: Number(parts.year),
88
- month: Number(parts.month),
89
- day: Number(parts.day),
90
- // Some locales/engines return '24' instead of '0' at midnight
91
- hour: parts.hour === "24" ? 0 : Number(parts.hour),
92
- minute: Number(parts.minute),
93
- second: Number(parts.second)
94
- };
95
- }
96
- function formatInTimezone(iso, formatStr, timeZone) {
97
- const p = partsInTimezone((0, import_date_fns.parseISO)(iso), timeZone);
98
- const tokens = {
99
- yyyy: String(p.year),
100
- MM: String(p.month).padStart(2, "0"),
101
- dd: String(p.day).padStart(2, "0"),
102
- HH: String(p.hour).padStart(2, "0"),
103
- mm: String(p.minute).padStart(2, "0"),
104
- ss: String(p.second).padStart(2, "0")
105
- };
106
- let result = formatStr;
107
- for (const [token, value] of Object.entries(tokens).sort((a, b) => b[0].length - a[0].length)) {
108
- result = result.split(token).join(value);
109
- }
110
- return result;
111
- }
112
- function getTimezoneOffsetMinutes(iso, timeZone) {
113
- const utc = (0, import_date_fns.parseISO)(iso);
114
- const p = partsInTimezone(utc, timeZone);
115
- const asUtcEpoch = Date.UTC(p.year, p.month - 1, p.day, p.hour, p.minute, p.second);
116
- return Math.round((asUtcEpoch - utc.getTime()) / 6e4);
117
- }
118
- function startOfDayInTimezone(iso, timeZone) {
119
- const utc = (0, import_date_fns.parseISO)(iso);
120
- const p = partsInTimezone(utc, timeZone);
121
- const civilMidnightUtc = Date.UTC(p.year, p.month - 1, p.day, 0, 0, 0);
122
- const midnightProbe = new Date(civilMidnightUtc).toISOString();
123
- const offsetMinutes = getTimezoneOffsetMinutes(midnightProbe, timeZone);
124
- return new Date(civilMidnightUtc - offsetMinutes * 6e4).toISOString();
125
- }
126
- function isSameDayInTimezone(a, b, timeZone) {
127
- const pa = partsInTimezone((0, import_date_fns.parseISO)(a), timeZone);
128
- const pb = partsInTimezone((0, import_date_fns.parseISO)(b), timeZone);
129
- return pa.year === pb.year && pa.month === pb.month && pa.day === pb.day;
130
- }
131
- function todayInTimezone(timeZone) {
132
- return startOfDayInTimezone((/* @__PURE__ */ new Date()).toISOString(), timeZone);
133
- }
134
- function civilMidnightFromUtcDay(gridUtcIso, timeZone) {
135
- const utc = (0, import_date_fns.parseISO)(gridUtcIso);
136
- const probe = new Date(
137
- Date.UTC(utc.getUTCFullYear(), utc.getUTCMonth(), utc.getUTCDate(), 12, 0, 0)
138
- ).toISOString();
139
- return startOfDayInTimezone(probe, timeZone);
140
- }
141
- function getTimeInTimezone(iso, timeZone) {
142
- const p = partsInTimezone((0, import_date_fns.parseISO)(iso), timeZone);
143
- return { hours: p.hour, minutes: p.minute, seconds: p.second };
144
- }
145
- function setTimeInTimezone(iso, partial, timeZone) {
146
- const p = partsInTimezone((0, import_date_fns.parseISO)(iso), timeZone);
147
- const targetHours = partial.hours ?? p.hour;
148
- const targetMinutes = partial.minutes ?? p.minute;
149
- const targetSeconds = partial.seconds ?? p.second;
150
- const civilEpoch = Date.UTC(
151
- p.year,
152
- p.month - 1,
153
- p.day,
154
- targetHours,
155
- targetMinutes,
156
- targetSeconds
157
- );
158
- const probe1 = new Date(civilEpoch).toISOString();
159
- const offset1 = getTimezoneOffsetMinutes(probe1, timeZone);
160
- const realEpoch1 = civilEpoch - offset1 * 6e4;
161
- const probe2 = new Date(realEpoch1).toISOString();
162
- const offset2 = getTimezoneOffsetMinutes(probe2, timeZone);
163
- const realEpoch2 = civilEpoch - offset2 * 6e4;
164
- return new Date(realEpoch2).toISOString();
165
- }
166
-
167
- // src/adapters/date-fns.ts
168
- function toDate(iso) {
169
- return (0, import_date_fns2.parseISO)(iso);
170
- }
171
- function toISO(date) {
172
- return date.toISOString();
173
- }
174
- function normalize(value) {
175
- if (/^\d{4}-\d{2}-\d{2}$/.test(value)) {
176
- return `${value}T00:00:00.000Z`;
177
- }
178
- return value;
179
- }
180
- function utcStartOfDay(d) {
181
- return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
182
- }
183
- function utcStartOfMonth(d) {
184
- return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), 1));
185
- }
186
- function utcEndOfMonth(d) {
187
- return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth() + 1, 0, 23, 59, 59, 999));
188
- }
189
- function utcStartOfWeek(d, weekStartsOn) {
190
- const day = d.getUTCDay();
191
- const diff = (day - weekStartsOn + 7) % 7;
192
- return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate() - diff));
193
- }
194
- function utcEndOfWeek(d, weekStartsOn) {
195
- const start = utcStartOfWeek(d, weekStartsOn);
196
- return new Date(
197
- Date.UTC(start.getUTCFullYear(), start.getUTCMonth(), start.getUTCDate() + 6, 23, 59, 59, 999)
198
- );
199
- }
200
- var DateFnsAdapter = {
201
- parse(value) {
202
- if (!value) return "";
203
- return normalize(value);
204
- },
205
- format(iso, formatStr, timezone) {
206
- if (timezone) {
207
- return formatInTimezone(iso, formatStr, timezone);
208
- }
209
- const d = toDate(iso);
210
- const tokens = {
211
- yyyy: String(d.getUTCFullYear()),
212
- MM: String(d.getUTCMonth() + 1).padStart(2, "0"),
213
- dd: String(d.getUTCDate()).padStart(2, "0"),
214
- HH: String(d.getUTCHours()).padStart(2, "0"),
215
- mm: String(d.getUTCMinutes()).padStart(2, "0"),
216
- ss: String(d.getUTCSeconds()).padStart(2, "0"),
217
- M: String(d.getUTCMonth() + 1),
218
- d: String(d.getUTCDate())
219
- };
220
- let result = formatStr;
221
- for (const [token, value] of Object.entries(tokens).sort((a, b) => b[0].length - a[0].length)) {
222
- result = result.split(token).join(value);
223
- }
224
- return result;
225
- },
226
- addDays(iso, n) {
227
- return toISO((0, import_date_fns2.addDays)(toDate(iso), n));
228
- },
229
- addMonths(iso, n) {
230
- return toISO((0, import_date_fns2.addMonths)(toDate(iso), n));
231
- },
232
- addYears(iso, n) {
233
- return toISO((0, import_date_fns2.addYears)(toDate(iso), n));
234
- },
235
- isBefore(a, b) {
236
- return (0, import_date_fns2.isBefore)(toDate(a), toDate(b));
237
- },
238
- isAfter(a, b) {
239
- return (0, import_date_fns2.isAfter)(toDate(a), toDate(b));
240
- },
241
- isSameDay(a, b, timezone) {
242
- if (!a || !b) return false;
243
- if (timezone) {
244
- return isSameDayInTimezone(a, b, timezone);
245
- }
246
- const da = toDate(a);
247
- const db = toDate(b);
248
- return da.getUTCFullYear() === db.getUTCFullYear() && da.getUTCMonth() === db.getUTCMonth() && da.getUTCDate() === db.getUTCDate();
249
- },
250
- isSameMonth(a, b) {
251
- const da = toDate(a);
252
- const db = toDate(b);
253
- return da.getUTCFullYear() === db.getUTCFullYear() && da.getUTCMonth() === db.getUTCMonth();
254
- },
255
- startOfDay(iso, timezone) {
256
- if (timezone) {
257
- return startOfDayInTimezone(iso, timezone);
258
- }
259
- return toISO(utcStartOfDay(toDate(iso)));
260
- },
261
- startOfMonth(iso) {
262
- return toISO(utcStartOfMonth(toDate(iso)));
263
- },
264
- endOfMonth(iso) {
265
- return toISO(utcEndOfMonth(toDate(iso)));
266
- },
267
- startOfWeek(iso, weekStartsOn = 0) {
268
- return toISO(utcStartOfWeek(toDate(iso), weekStartsOn));
269
- },
270
- endOfWeek(iso, weekStartsOn = 0) {
271
- return toISO(utcEndOfWeek(toDate(iso), weekStartsOn));
272
- },
273
- now() {
274
- return (/* @__PURE__ */ new Date()).toISOString();
275
- },
276
- today(timezone) {
277
- if (timezone) {
278
- return todayInTimezone(timezone);
279
- }
280
- return toISO(utcStartOfDay(/* @__PURE__ */ new Date()));
281
- },
282
- isValid(value) {
283
- if (!value) return false;
284
- const normalized = normalize(value);
285
- const date = (0, import_date_fns2.parseISO)(normalized);
286
- return (0, import_date_fns2.isValid)(date);
287
- },
288
- getYear(iso) {
289
- return toDate(iso).getUTCFullYear();
290
- },
291
- getMonth(iso) {
292
- return toDate(iso).getUTCMonth();
293
- },
294
- getDate(iso) {
295
- return toDate(iso).getUTCDate();
296
- },
297
- getDay(iso) {
298
- return toDate(iso).getUTCDay();
299
- }
300
- };
301
-
302
59
  // src/utils/calendar.ts
303
60
  function getCalendarDays(monthISO, adapter, options = {}) {
304
61
  const {
@@ -309,7 +66,8 @@ function getCalendarDays(monthISO, adapter, options = {}) {
309
66
  disabled = [],
310
67
  range,
311
68
  rangeHover,
312
- timezone
69
+ timezone,
70
+ fixedWeeks = false
313
71
  } = options;
314
72
  const todayISO = today ?? adapter.today(timezone);
315
73
  const monthStart = adapter.startOfMonth(monthISO);
@@ -339,12 +97,20 @@ function getCalendarDays(monthISO, adapter, options = {}) {
339
97
  current = adapter.addDays(current, 1);
340
98
  }
341
99
  weeks.push(days);
342
- if (!adapter.isSameMonth(current, monthISO) && week >= 3) {
100
+ if (!fixedWeeks && !adapter.isSameMonth(current, monthISO) && week >= 3) {
343
101
  break;
344
102
  }
345
103
  }
346
104
  return weeks;
347
105
  }
106
+ function getISOWeekNumber(iso) {
107
+ const d = new Date(iso);
108
+ const utc = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
109
+ const isoDay = utc.getUTCDay() || 7;
110
+ utc.setUTCDate(utc.getUTCDate() + 4 - isoDay);
111
+ const yearStart = new Date(Date.UTC(utc.getUTCFullYear(), 0, 1));
112
+ return Math.ceil(((utc.getTime() - yearStart.getTime()) / 864e5 + 1) / 7);
113
+ }
348
114
  function normalizeRangeForDisplay(range, hover, adapter, _timezone) {
349
115
  if (!range) return { start: null, end: null };
350
116
  const { start, end } = range;
@@ -388,6 +154,8 @@ function isDateDisabled(iso, rules, adapter) {
388
154
  if (adapter.isAfter(iso, rule.after)) return true;
389
155
  } else if ("dayOfWeek" in rule) {
390
156
  if (rule.dayOfWeek.includes(adapter.getDay(iso))) return true;
157
+ } else if ("filter" in rule) {
158
+ if (rule.filter(iso)) return true;
391
159
  }
392
160
  }
393
161
  return false;
@@ -465,12 +233,18 @@ function formatTimeString(time, withSeconds = false) {
465
233
  return `${hh}:${mm}`;
466
234
  }
467
235
  function to12Hour(hours24) {
236
+ if (!Number.isInteger(hours24) || hours24 < 0 || hours24 > 23) {
237
+ throw new RangeError(`[to12Hour] hours24 must be an integer in [0, 23], got ${hours24}`);
238
+ }
468
239
  const period = hours24 >= 12 ? "PM" : "AM";
469
240
  let hours12 = hours24 % 12;
470
241
  if (hours12 === 0) hours12 = 12;
471
242
  return { hours12, period };
472
243
  }
473
244
  function to24Hour(hours12, period) {
245
+ if (!Number.isInteger(hours12) || hours12 < 1 || hours12 > 12) {
246
+ throw new RangeError(`[to24Hour] hours12 must be an integer in [1, 12], got ${hours12}`);
247
+ }
474
248
  if (period === "AM") {
475
249
  return hours12 === 12 ? 0 : hours12;
476
250
  }
@@ -483,8 +257,8 @@ function generateHours(format = "24h") {
483
257
  return Array.from({ length: 24 }, (_, i) => i);
484
258
  }
485
259
  function generateMinutes(step = 1) {
486
- if (step < 1 || step > 30) {
487
- throw new Error(`[generateMinutes] step must be between 1 and 30, got ${step}`);
260
+ if (step < 1 || step > 60) {
261
+ throw new Error(`[generateMinutes] step must be between 1 and 60, got ${step}`);
488
262
  }
489
263
  const result = [];
490
264
  for (let i = 0; i < 60; i += step) {
@@ -510,13 +284,13 @@ function formatTimeFromISO(iso, format) {
510
284
  }
511
285
 
512
286
  // src/utils/locale.ts
513
- var formatterCache2 = /* @__PURE__ */ new Map();
287
+ var formatterCache = /* @__PURE__ */ new Map();
514
288
  function getCachedFormatter(locale, options) {
515
289
  const key = `${locale}:${JSON.stringify(options)}`;
516
- let fmt = formatterCache2.get(key);
290
+ let fmt = formatterCache.get(key);
517
291
  if (!fmt) {
518
292
  fmt = new Intl.DateTimeFormat(locale, options);
519
- formatterCache2.set(key, fmt);
293
+ formatterCache.set(key, fmt);
520
294
  }
521
295
  return fmt;
522
296
  }
@@ -561,6 +335,110 @@ function formatFullDate(iso, locale = "en-US") {
561
335
  }).format(date);
562
336
  }
563
337
 
338
+ // src/utils/timezone.ts
339
+ var formatterCache2 = /* @__PURE__ */ new Map();
340
+ function getCachedPartsFormatter(timeZone) {
341
+ const key = `parts:${timeZone}`;
342
+ let fmt = formatterCache2.get(key);
343
+ if (!fmt) {
344
+ fmt = new Intl.DateTimeFormat("en-US", {
345
+ timeZone,
346
+ year: "numeric",
347
+ month: "2-digit",
348
+ day: "2-digit",
349
+ hour: "2-digit",
350
+ minute: "2-digit",
351
+ second: "2-digit",
352
+ hourCycle: "h23"
353
+ });
354
+ formatterCache2.set(key, fmt);
355
+ }
356
+ return fmt;
357
+ }
358
+ function partsInTimezone(utc, timeZone) {
359
+ const dtf = getCachedPartsFormatter(timeZone);
360
+ const parts = Object.fromEntries(dtf.formatToParts(utc).map((p) => [p.type, p.value]));
361
+ return {
362
+ year: Number(parts.year),
363
+ month: Number(parts.month),
364
+ day: Number(parts.day),
365
+ // Some locales/engines return '24' instead of '0' at midnight
366
+ hour: parts.hour === "24" ? 0 : Number(parts.hour),
367
+ minute: Number(parts.minute),
368
+ second: Number(parts.second)
369
+ };
370
+ }
371
+ function formatInTimezone(iso, formatStr, timeZone) {
372
+ const p = partsInTimezone(new Date(iso), timeZone);
373
+ const tokens = {
374
+ yyyy: String(p.year),
375
+ MM: String(p.month).padStart(2, "0"),
376
+ dd: String(p.day).padStart(2, "0"),
377
+ HH: String(p.hour).padStart(2, "0"),
378
+ mm: String(p.minute).padStart(2, "0"),
379
+ ss: String(p.second).padStart(2, "0")
380
+ };
381
+ let result = formatStr;
382
+ for (const [token, value] of Object.entries(tokens).sort((a, b) => b[0].length - a[0].length)) {
383
+ result = result.split(token).join(value);
384
+ }
385
+ return result;
386
+ }
387
+ function getTimezoneOffsetMinutes(iso, timeZone) {
388
+ const utc = new Date(iso);
389
+ const p = partsInTimezone(utc, timeZone);
390
+ const asUtcEpoch = Date.UTC(p.year, p.month - 1, p.day, p.hour, p.minute, p.second);
391
+ return Math.round((asUtcEpoch - utc.getTime()) / 6e4);
392
+ }
393
+ function startOfDayInTimezone(iso, timeZone) {
394
+ const utc = new Date(iso);
395
+ const p = partsInTimezone(utc, timeZone);
396
+ const civilMidnightUtc = Date.UTC(p.year, p.month - 1, p.day, 0, 0, 0);
397
+ const midnightProbe = new Date(civilMidnightUtc).toISOString();
398
+ const offsetMinutes = getTimezoneOffsetMinutes(midnightProbe, timeZone);
399
+ return new Date(civilMidnightUtc - offsetMinutes * 6e4).toISOString();
400
+ }
401
+ function isSameDayInTimezone(a, b, timeZone) {
402
+ const pa = partsInTimezone(new Date(a), timeZone);
403
+ const pb = partsInTimezone(new Date(b), timeZone);
404
+ return pa.year === pb.year && pa.month === pb.month && pa.day === pb.day;
405
+ }
406
+ function todayInTimezone(timeZone) {
407
+ return startOfDayInTimezone((/* @__PURE__ */ new Date()).toISOString(), timeZone);
408
+ }
409
+ function civilMidnightFromUtcDay(gridUtcIso, timeZone) {
410
+ const utc = new Date(gridUtcIso);
411
+ const probe = new Date(
412
+ Date.UTC(utc.getUTCFullYear(), utc.getUTCMonth(), utc.getUTCDate(), 12, 0, 0)
413
+ ).toISOString();
414
+ return startOfDayInTimezone(probe, timeZone);
415
+ }
416
+ function getTimeInTimezone(iso, timeZone) {
417
+ const p = partsInTimezone(new Date(iso), timeZone);
418
+ return { hours: p.hour, minutes: p.minute, seconds: p.second };
419
+ }
420
+ function setTimeInTimezone(iso, partial, timeZone) {
421
+ const p = partsInTimezone(new Date(iso), timeZone);
422
+ const targetHours = partial.hours ?? p.hour;
423
+ const targetMinutes = partial.minutes ?? p.minute;
424
+ const targetSeconds = partial.seconds ?? p.second;
425
+ const civilEpoch = Date.UTC(
426
+ p.year,
427
+ p.month - 1,
428
+ p.day,
429
+ targetHours,
430
+ targetMinutes,
431
+ targetSeconds
432
+ );
433
+ const probe1 = new Date(civilEpoch).toISOString();
434
+ const offset1 = getTimezoneOffsetMinutes(probe1, timeZone);
435
+ const realEpoch1 = civilEpoch - offset1 * 6e4;
436
+ const probe2 = new Date(realEpoch1).toISOString();
437
+ const offset2 = getTimezoneOffsetMinutes(probe2, timeZone);
438
+ const realEpoch2 = civilEpoch - offset2 * 6e4;
439
+ return new Date(realEpoch2).toISOString();
440
+ }
441
+
564
442
  // src/utils/labels.ts
565
443
  var DEFAULT_DATEPICKER_LABELS = {
566
444
  triggerOpen: "Open calendar",
@@ -578,7 +456,9 @@ var DEFAULT_RANGEPICKER_LABELS = {
578
456
  popoverLabel: "Choose date range",
579
457
  startInput: "Start date",
580
458
  endInput: "End date",
581
- presetsGroup: "Date range presets"
459
+ presetsGroup: "Date range presets",
460
+ selectingEnd: "Now select end date",
461
+ rangeSelected: "Range selected"
582
462
  };
583
463
  var DEFAULT_TIMEPICKER_LABELS = {
584
464
  timeInput: "Time",
@@ -599,7 +479,6 @@ var DEFAULT_DATETIMEPICKER_LABELS = {
599
479
  DEFAULT_DATETIMEPICKER_LABELS,
600
480
  DEFAULT_RANGEPICKER_LABELS,
601
481
  DEFAULT_TIMEPICKER_LABELS,
602
- DateFnsAdapter,
603
482
  civilMidnightFromUtcDay,
604
483
  formatFullDate,
605
484
  formatInTimezone,
@@ -609,6 +488,7 @@ var DEFAULT_DATETIMEPICKER_LABELS = {
609
488
  generateHours,
610
489
  generateMinutes,
611
490
  getCalendarDays,
491
+ getISOWeekNumber,
612
492
  getMonthName,
613
493
  getTime,
614
494
  getTimeInTimezone,
package/dist/index.d.cts 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 };