@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/CHANGELOG.md +286 -0
- package/README.md +6 -2
- package/dist/index.cjs +133 -253
- package/dist/index.d.cts +51 -21
- package/dist/index.d.ts +51 -21
- package/dist/index.js +132 -260
- package/package.json +5 -6
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 >
|
|
487
|
-
throw new Error(`[generateMinutes] step must be between 1 and
|
|
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
|
|
287
|
+
var formatterCache = /* @__PURE__ */ new Map();
|
|
514
288
|
function getCachedFormatter(locale, options) {
|
|
515
289
|
const key = `${locale}:${JSON.stringify(options)}`;
|
|
516
|
-
let fmt =
|
|
290
|
+
let fmt = formatterCache.get(key);
|
|
517
291
|
if (!fmt) {
|
|
518
292
|
fmt = new Intl.DateTimeFormat(locale, options);
|
|
519
|
-
|
|
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] },
|
|
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
|
-
*
|
|
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
|
-
*
|
|
219
|
-
* step=
|
|
220
|
-
* step=
|
|
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,
|
|
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 };
|