@xsolla/xui-date-picker 0.205.1 → 0.206.1
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 +26 -3
- package/native/index.d.mts +26 -9
- package/native/index.d.ts +26 -9
- package/native/index.js +125 -45
- package/native/index.js.map +1 -1
- package/native/index.mjs +120 -33
- package/native/index.mjs.map +1 -1
- package/package.json +6 -6
- package/web/index.d.mts +26 -9
- package/web/index.d.ts +26 -9
- package/web/index.js +125 -35
- package/web/index.js.map +1 -1
- package/web/index.mjs +120 -33
- package/web/index.mjs.map +1 -1
package/web/index.mjs
CHANGED
|
@@ -4,7 +4,8 @@ import {
|
|
|
4
4
|
DualCalendar as DualCalendar2,
|
|
5
5
|
CalendarHeader,
|
|
6
6
|
CalendarGrid,
|
|
7
|
-
CalendarChips
|
|
7
|
+
CalendarChips,
|
|
8
|
+
formatDate
|
|
8
9
|
} from "@xsolla/xui-calendar";
|
|
9
10
|
|
|
10
11
|
// src/DatePicker.tsx
|
|
@@ -288,15 +289,6 @@ import { Calendar, DualCalendar } from "@xsolla/xui-calendar";
|
|
|
288
289
|
import { Calendar as CalendarIcon } from "@xsolla/xui-icons-base";
|
|
289
290
|
|
|
290
291
|
// src/utils.ts
|
|
291
|
-
import { format } from "date-fns";
|
|
292
|
-
import * as locales from "date-fns/locale";
|
|
293
|
-
var defaultLocale = "enUS";
|
|
294
|
-
function formatDate(date, formatStr, locale = defaultLocale) {
|
|
295
|
-
const localeObj = locales[locale] || locales[defaultLocale];
|
|
296
|
-
return format(date, formatStr, {
|
|
297
|
-
locale: localeObj
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
292
|
var SEGMENT_WIDTH = {
|
|
301
293
|
day: 2,
|
|
302
294
|
month: 2,
|
|
@@ -307,21 +299,109 @@ var SEGMENT_TOKEN = {
|
|
|
307
299
|
month: "mm",
|
|
308
300
|
year: "yyyy"
|
|
309
301
|
};
|
|
310
|
-
|
|
302
|
+
var DEFAULT_DATE_FORMAT = "MM/dd/yyyy";
|
|
303
|
+
var SEGMENT_LETTER = {
|
|
304
|
+
d: "day",
|
|
305
|
+
M: "month",
|
|
306
|
+
y: "year"
|
|
307
|
+
};
|
|
308
|
+
var CASING_REPAIRS = { D: "d", Y: "y", m: "M" };
|
|
309
|
+
var SUPPORTED_TOKENS_HINT = "supported tokens are `d`/`dd` (day), `M`/`MM` (month) and `y`/`yyyy` (year); any other character is a literal separator. Textual and localised tokens (`MMM`, `MMMM`, `P`, `PP`) are not supported \u2014 the field renders numeric segments only.";
|
|
310
|
+
var ALL_SEGMENTS = ["day", "month", "year"];
|
|
311
|
+
var isLetter = (c) => /[A-Za-z]/.test(c);
|
|
312
|
+
var isSupportedRun = (letter, length) => {
|
|
313
|
+
if (letter === "y") return true;
|
|
314
|
+
if (letter === "d" || letter === "M") return length <= 2;
|
|
315
|
+
return false;
|
|
316
|
+
};
|
|
317
|
+
function analyzeDateFormat(format, fallback = DEFAULT_DATE_FORMAT) {
|
|
318
|
+
const issues = [];
|
|
319
|
+
const used = /* @__PURE__ */ new Set();
|
|
320
|
+
for (let i2 = 0; i2 < format.length; i2++) {
|
|
321
|
+
if (isLetter(format[i2])) used.add(format[i2]);
|
|
322
|
+
}
|
|
323
|
+
let repaired = "";
|
|
324
|
+
let hasUnsupported = false;
|
|
325
|
+
let i = 0;
|
|
326
|
+
while (i < format.length) {
|
|
327
|
+
const c = format[i];
|
|
328
|
+
if (!isLetter(c)) {
|
|
329
|
+
repaired += c;
|
|
330
|
+
i++;
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
let end = i;
|
|
334
|
+
while (end < format.length && format[end] === c) end++;
|
|
335
|
+
const run = format.slice(i, end);
|
|
336
|
+
i = end;
|
|
337
|
+
if (isSupportedRun(c, run.length)) {
|
|
338
|
+
repaired += run;
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
const repair = CASING_REPAIRS[c];
|
|
342
|
+
if (repair && !used.has(repair) && isSupportedRun(repair, run.length)) {
|
|
343
|
+
const fixed = repair.repeat(run.length);
|
|
344
|
+
issues.push({ token: run, kind: "casing", suggestion: fixed });
|
|
345
|
+
repaired += fixed;
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
issues.push({ token: run, kind: "unsupported" });
|
|
349
|
+
hasUnsupported = true;
|
|
350
|
+
repaired += run;
|
|
351
|
+
}
|
|
352
|
+
const present = new Set(
|
|
353
|
+
parseDateFormat(repaired).filter(
|
|
354
|
+
(t) => t.kind === "segment"
|
|
355
|
+
).map((t) => t.type)
|
|
356
|
+
);
|
|
357
|
+
const missing = ALL_SEGMENTS.filter((type) => !present.has(type));
|
|
358
|
+
for (const type of missing) issues.push({ token: type, kind: "missing" });
|
|
359
|
+
const usable = !hasUnsupported && missing.length === 0;
|
|
360
|
+
return {
|
|
361
|
+
format: usable ? repaired : fallback,
|
|
362
|
+
usedFallback: !usable,
|
|
363
|
+
issues
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
function describeDateFormatIssues(format, analysis) {
|
|
367
|
+
const clauses = analysis.issues.map((issue) => {
|
|
368
|
+
if (issue.kind === "missing") {
|
|
369
|
+
return `it has no ${issue.token} segment, so the field can never be completed`;
|
|
370
|
+
}
|
|
371
|
+
if (issue.kind === "casing") {
|
|
372
|
+
return `\`${issue.token}\` is not a \`date-fns\` date token \u2014 use \`${issue.suggestion}\``;
|
|
373
|
+
}
|
|
374
|
+
return `\`${issue.token}\` is not a supported token`;
|
|
375
|
+
});
|
|
376
|
+
const resolution = analysis.usedFallback ? `Falling back to \`${analysis.format}\`.` : `Reading it as \`${analysis.format}\`.`;
|
|
377
|
+
return `[DatePicker] Invalid \`dateFormat\` \`${format}\`: ${clauses.join("; ")}. ${resolution} Note ${SUPPORTED_TOKENS_HINT}`;
|
|
378
|
+
}
|
|
379
|
+
var warnedFormats = /* @__PURE__ */ new Set();
|
|
380
|
+
function resolveDateFormat(format, fallback = DEFAULT_DATE_FORMAT) {
|
|
381
|
+
const analysis = analyzeDateFormat(format, fallback);
|
|
382
|
+
if (analysis.issues.length > 0) {
|
|
383
|
+
const key = `${format}|${fallback}`;
|
|
384
|
+
const isProduction = globalThis.process?.env?.NODE_ENV === "production";
|
|
385
|
+
if (!isProduction && !warnedFormats.has(key)) {
|
|
386
|
+
warnedFormats.add(key);
|
|
387
|
+
console.warn(describeDateFormatIssues(format, analysis));
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return analysis.format;
|
|
391
|
+
}
|
|
392
|
+
function parseDateFormat(format) {
|
|
311
393
|
const tokens = [];
|
|
312
394
|
let i = 0;
|
|
313
|
-
while (i <
|
|
314
|
-
const c =
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
type: c === "d" ? "day" : c === "M" ? "month" : "year"
|
|
320
|
-
});
|
|
395
|
+
while (i < format.length) {
|
|
396
|
+
const c = format[i];
|
|
397
|
+
const type = SEGMENT_LETTER[c];
|
|
398
|
+
if (type) {
|
|
399
|
+
while (i < format.length && format[i] === c) i++;
|
|
400
|
+
tokens.push({ kind: "segment", type });
|
|
321
401
|
} else {
|
|
322
402
|
let text = "";
|
|
323
|
-
while (i <
|
|
324
|
-
text +=
|
|
403
|
+
while (i < format.length && !SEGMENT_LETTER[format[i]]) {
|
|
404
|
+
text += format[i];
|
|
325
405
|
i++;
|
|
326
406
|
}
|
|
327
407
|
tokens.push({ kind: "literal", text });
|
|
@@ -365,8 +445,8 @@ function stepDatePart(type, current, delta, parts) {
|
|
|
365
445
|
const dim = daysInMonth(year, monthIndex);
|
|
366
446
|
return ((current - 1 + delta) % dim + dim) % dim + 1;
|
|
367
447
|
}
|
|
368
|
-
function buildSegmentedValue(
|
|
369
|
-
const tokens = parseDateFormat(
|
|
448
|
+
function buildSegmentedValue(format, parts, active) {
|
|
449
|
+
const tokens = parseDateFormat(format);
|
|
370
450
|
let text = "";
|
|
371
451
|
const ranges = [];
|
|
372
452
|
for (const token of tokens) {
|
|
@@ -407,14 +487,14 @@ function isSegmentComplete(type, buffer) {
|
|
|
407
487
|
function daysInMonth(year, monthIndex) {
|
|
408
488
|
return new Date(year, monthIndex + 1, 0).getDate();
|
|
409
489
|
}
|
|
410
|
-
function getDateSegments(
|
|
490
|
+
function getDateSegments(format, value) {
|
|
411
491
|
const segments = [];
|
|
412
492
|
let fi = 0;
|
|
413
493
|
let vi = 0;
|
|
414
|
-
while (fi <
|
|
415
|
-
const fc =
|
|
494
|
+
while (fi < format.length && vi < value.length) {
|
|
495
|
+
const fc = format[fi];
|
|
416
496
|
if (fc === "d" || fc === "M" || fc === "y") {
|
|
417
|
-
while (fi <
|
|
497
|
+
while (fi < format.length && format[fi] === fc) fi++;
|
|
418
498
|
const start = vi;
|
|
419
499
|
while (vi < value.length && value[vi] >= "0" && value[vi] <= "9") vi++;
|
|
420
500
|
if (vi > start) {
|
|
@@ -459,7 +539,6 @@ function stepDateSegment(date, type, delta) {
|
|
|
459
539
|
// src/DatePicker.tsx
|
|
460
540
|
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
461
541
|
var PLACEMENT_OFFSET = 4;
|
|
462
|
-
var DEFAULT_DATE_FORMAT = "MM/dd/yyyy";
|
|
463
542
|
var RANGE_SEPARATOR = /\s*[-–—]\s*/;
|
|
464
543
|
var RANGE_DISPLAY_SEP = " - ";
|
|
465
544
|
var CalendarFieldIcon = () => /* @__PURE__ */ jsx2(CalendarIcon, { size: "100%" });
|
|
@@ -561,6 +640,10 @@ var DatePicker = forwardRef(
|
|
|
561
640
|
const editable = !readOnly && !rest.disabled;
|
|
562
641
|
const isDual = variant === "dual";
|
|
563
642
|
const isRange = isDual || selectsRange;
|
|
643
|
+
const resolvedFormat = useMemo(
|
|
644
|
+
() => resolveDateFormat(dateFormat),
|
|
645
|
+
[dateFormat]
|
|
646
|
+
);
|
|
564
647
|
const propGroups = useCallback(
|
|
565
648
|
() => isRange ? [datePartsFromDate(startDate), datePartsFromDate(endDate)] : [datePartsFromDate(selectedDate)],
|
|
566
649
|
[isRange, startDate, endDate, selectedDate]
|
|
@@ -608,7 +691,7 @@ var DatePicker = forwardRef(
|
|
|
608
691
|
};
|
|
609
692
|
const composeField = () => {
|
|
610
693
|
const build = (group) => buildSegmentedValue(
|
|
611
|
-
|
|
694
|
+
resolvedFormat,
|
|
612
695
|
groups[group] ?? { day: null, month: null, year: null },
|
|
613
696
|
edit && edit.group === group ? { type: edit.type, buffer: edit.buffer } : void 0
|
|
614
697
|
);
|
|
@@ -855,7 +938,7 @@ var DatePicker = forwardRef(
|
|
|
855
938
|
const parseOne = (token) => {
|
|
856
939
|
const trimmed = token.trim();
|
|
857
940
|
if (!trimmed) return null;
|
|
858
|
-
const parsed = parse(trimmed,
|
|
941
|
+
const parsed = parse(trimmed, resolvedFormat, /* @__PURE__ */ new Date());
|
|
859
942
|
return isValid(parsed) ? parsed : null;
|
|
860
943
|
};
|
|
861
944
|
const handlePaste = (e) => {
|
|
@@ -872,7 +955,7 @@ var DatePicker = forwardRef(
|
|
|
872
955
|
const end = parseOne(endText ?? "");
|
|
873
956
|
if (!start) {
|
|
874
957
|
setParseError(
|
|
875
|
-
`Enter a valid date range (${
|
|
958
|
+
`Enter a valid date range (${resolvedFormat} - ${resolvedFormat})`
|
|
876
959
|
);
|
|
877
960
|
return;
|
|
878
961
|
}
|
|
@@ -880,7 +963,7 @@ var DatePicker = forwardRef(
|
|
|
880
963
|
} else {
|
|
881
964
|
const date = parseOne(text);
|
|
882
965
|
if (!date) {
|
|
883
|
-
setParseError(`Enter a valid date (${
|
|
966
|
+
setParseError(`Enter a valid date (${resolvedFormat})`);
|
|
884
967
|
return;
|
|
885
968
|
}
|
|
886
969
|
next = [datePartsFromDate(date)];
|
|
@@ -1076,7 +1159,7 @@ var DatePicker = forwardRef(
|
|
|
1076
1159
|
...rest,
|
|
1077
1160
|
ref: inputRef,
|
|
1078
1161
|
size,
|
|
1079
|
-
placeholder: placeholder || (isRange ? "
|
|
1162
|
+
placeholder: placeholder || (isRange ? "Choose a date range" : "Choose a date"),
|
|
1080
1163
|
value: inputValue,
|
|
1081
1164
|
readOnly,
|
|
1082
1165
|
onFocus: handleFieldFocus,
|
|
@@ -1123,18 +1206,22 @@ export {
|
|
|
1123
1206
|
CalendarChips,
|
|
1124
1207
|
CalendarGrid,
|
|
1125
1208
|
CalendarHeader,
|
|
1209
|
+
DEFAULT_DATE_FORMAT,
|
|
1126
1210
|
DatePicker,
|
|
1127
1211
|
DualCalendar2 as DualCalendar,
|
|
1212
|
+
analyzeDateFormat,
|
|
1128
1213
|
buildSegmentedValue,
|
|
1129
1214
|
clampDatePart,
|
|
1130
1215
|
datePartsFromDate,
|
|
1131
1216
|
datePartsToDate,
|
|
1132
1217
|
daysInMonth,
|
|
1218
|
+
describeDateFormatIssues,
|
|
1133
1219
|
formatDate,
|
|
1134
1220
|
getDateSegments,
|
|
1135
1221
|
isDatePartsComplete,
|
|
1136
1222
|
isSegmentComplete,
|
|
1137
1223
|
parseDateFormat,
|
|
1224
|
+
resolveDateFormat,
|
|
1138
1225
|
stepDatePart,
|
|
1139
1226
|
stepDateSegment
|
|
1140
1227
|
};
|