@xsolla/xui-date-picker 0.206.0 → 0.206.2
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.js
CHANGED
|
@@ -34,18 +34,22 @@ __export(index_exports, {
|
|
|
34
34
|
CalendarChips: () => import_xui_calendar2.CalendarChips,
|
|
35
35
|
CalendarGrid: () => import_xui_calendar2.CalendarGrid,
|
|
36
36
|
CalendarHeader: () => import_xui_calendar2.CalendarHeader,
|
|
37
|
+
DEFAULT_DATE_FORMAT: () => DEFAULT_DATE_FORMAT,
|
|
37
38
|
DatePicker: () => DatePicker,
|
|
38
39
|
DualCalendar: () => import_xui_calendar2.DualCalendar,
|
|
40
|
+
analyzeDateFormat: () => analyzeDateFormat,
|
|
39
41
|
buildSegmentedValue: () => buildSegmentedValue,
|
|
40
42
|
clampDatePart: () => clampDatePart,
|
|
41
43
|
datePartsFromDate: () => datePartsFromDate,
|
|
42
44
|
datePartsToDate: () => datePartsToDate,
|
|
43
45
|
daysInMonth: () => daysInMonth,
|
|
44
|
-
|
|
46
|
+
describeDateFormatIssues: () => describeDateFormatIssues,
|
|
47
|
+
formatDate: () => import_xui_calendar2.formatDate,
|
|
45
48
|
getDateSegments: () => getDateSegments,
|
|
46
49
|
isDatePartsComplete: () => isDatePartsComplete,
|
|
47
50
|
isSegmentComplete: () => isSegmentComplete,
|
|
48
51
|
parseDateFormat: () => parseDateFormat,
|
|
52
|
+
resolveDateFormat: () => resolveDateFormat,
|
|
49
53
|
stepDatePart: () => stepDatePart,
|
|
50
54
|
stepDateSegment: () => stepDateSegment
|
|
51
55
|
});
|
|
@@ -321,20 +325,11 @@ var isNative = false;
|
|
|
321
325
|
|
|
322
326
|
// src/DatePicker.tsx
|
|
323
327
|
var import_xui_input = require("@xsolla/xui-input");
|
|
324
|
-
var
|
|
328
|
+
var import_date_fns = require("date-fns");
|
|
325
329
|
var import_xui_calendar = require("@xsolla/xui-calendar");
|
|
326
330
|
var import_xui_icons_base = require("@xsolla/xui-icons-base");
|
|
327
331
|
|
|
328
332
|
// src/utils.ts
|
|
329
|
-
var import_date_fns = require("date-fns");
|
|
330
|
-
var locales = __toESM(require("date-fns/locale"));
|
|
331
|
-
var defaultLocale = "enUS";
|
|
332
|
-
function formatDate(date, formatStr, locale = defaultLocale) {
|
|
333
|
-
const localeObj = locales[locale] || locales[defaultLocale];
|
|
334
|
-
return (0, import_date_fns.format)(date, formatStr, {
|
|
335
|
-
locale: localeObj
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
333
|
var SEGMENT_WIDTH = {
|
|
339
334
|
day: 2,
|
|
340
335
|
month: 2,
|
|
@@ -345,21 +340,109 @@ var SEGMENT_TOKEN = {
|
|
|
345
340
|
month: "mm",
|
|
346
341
|
year: "yyyy"
|
|
347
342
|
};
|
|
348
|
-
|
|
343
|
+
var DEFAULT_DATE_FORMAT = "MM/dd/yyyy";
|
|
344
|
+
var SEGMENT_LETTER = {
|
|
345
|
+
d: "day",
|
|
346
|
+
M: "month",
|
|
347
|
+
y: "year"
|
|
348
|
+
};
|
|
349
|
+
var CASING_REPAIRS = { D: "d", Y: "y", m: "M" };
|
|
350
|
+
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.";
|
|
351
|
+
var ALL_SEGMENTS = ["day", "month", "year"];
|
|
352
|
+
var isLetter = (c) => /[A-Za-z]/.test(c);
|
|
353
|
+
var isSupportedRun = (letter, length) => {
|
|
354
|
+
if (letter === "y") return true;
|
|
355
|
+
if (letter === "d" || letter === "M") return length <= 2;
|
|
356
|
+
return false;
|
|
357
|
+
};
|
|
358
|
+
function analyzeDateFormat(format, fallback = DEFAULT_DATE_FORMAT) {
|
|
359
|
+
const issues = [];
|
|
360
|
+
const used = /* @__PURE__ */ new Set();
|
|
361
|
+
for (let i2 = 0; i2 < format.length; i2++) {
|
|
362
|
+
if (isLetter(format[i2])) used.add(format[i2]);
|
|
363
|
+
}
|
|
364
|
+
let repaired = "";
|
|
365
|
+
let hasUnsupported = false;
|
|
366
|
+
let i = 0;
|
|
367
|
+
while (i < format.length) {
|
|
368
|
+
const c = format[i];
|
|
369
|
+
if (!isLetter(c)) {
|
|
370
|
+
repaired += c;
|
|
371
|
+
i++;
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
let end = i;
|
|
375
|
+
while (end < format.length && format[end] === c) end++;
|
|
376
|
+
const run = format.slice(i, end);
|
|
377
|
+
i = end;
|
|
378
|
+
if (isSupportedRun(c, run.length)) {
|
|
379
|
+
repaired += run;
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
const repair = CASING_REPAIRS[c];
|
|
383
|
+
if (repair && !used.has(repair) && isSupportedRun(repair, run.length)) {
|
|
384
|
+
const fixed = repair.repeat(run.length);
|
|
385
|
+
issues.push({ token: run, kind: "casing", suggestion: fixed });
|
|
386
|
+
repaired += fixed;
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
issues.push({ token: run, kind: "unsupported" });
|
|
390
|
+
hasUnsupported = true;
|
|
391
|
+
repaired += run;
|
|
392
|
+
}
|
|
393
|
+
const present = new Set(
|
|
394
|
+
parseDateFormat(repaired).filter(
|
|
395
|
+
(t) => t.kind === "segment"
|
|
396
|
+
).map((t) => t.type)
|
|
397
|
+
);
|
|
398
|
+
const missing = ALL_SEGMENTS.filter((type) => !present.has(type));
|
|
399
|
+
for (const type of missing) issues.push({ token: type, kind: "missing" });
|
|
400
|
+
const usable = !hasUnsupported && missing.length === 0;
|
|
401
|
+
return {
|
|
402
|
+
format: usable ? repaired : fallback,
|
|
403
|
+
usedFallback: !usable,
|
|
404
|
+
issues
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
function describeDateFormatIssues(format, analysis) {
|
|
408
|
+
const clauses = analysis.issues.map((issue) => {
|
|
409
|
+
if (issue.kind === "missing") {
|
|
410
|
+
return `it has no ${issue.token} segment, so the field can never be completed`;
|
|
411
|
+
}
|
|
412
|
+
if (issue.kind === "casing") {
|
|
413
|
+
return `\`${issue.token}\` is not a \`date-fns\` date token \u2014 use \`${issue.suggestion}\``;
|
|
414
|
+
}
|
|
415
|
+
return `\`${issue.token}\` is not a supported token`;
|
|
416
|
+
});
|
|
417
|
+
const resolution = analysis.usedFallback ? `Falling back to \`${analysis.format}\`.` : `Reading it as \`${analysis.format}\`.`;
|
|
418
|
+
return `[DatePicker] Invalid \`dateFormat\` \`${format}\`: ${clauses.join("; ")}. ${resolution} Note ${SUPPORTED_TOKENS_HINT}`;
|
|
419
|
+
}
|
|
420
|
+
var warnedFormats = /* @__PURE__ */ new Set();
|
|
421
|
+
function resolveDateFormat(format, fallback = DEFAULT_DATE_FORMAT) {
|
|
422
|
+
const analysis = analyzeDateFormat(format, fallback);
|
|
423
|
+
if (analysis.issues.length > 0) {
|
|
424
|
+
const key = `${format}|${fallback}`;
|
|
425
|
+
const isProduction = globalThis.process?.env?.NODE_ENV === "production";
|
|
426
|
+
if (!isProduction && !warnedFormats.has(key)) {
|
|
427
|
+
warnedFormats.add(key);
|
|
428
|
+
console.warn(describeDateFormatIssues(format, analysis));
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
return analysis.format;
|
|
432
|
+
}
|
|
433
|
+
function parseDateFormat(format) {
|
|
349
434
|
const tokens = [];
|
|
350
435
|
let i = 0;
|
|
351
|
-
while (i <
|
|
352
|
-
const c =
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
type: c === "d" ? "day" : c === "M" ? "month" : "year"
|
|
358
|
-
});
|
|
436
|
+
while (i < format.length) {
|
|
437
|
+
const c = format[i];
|
|
438
|
+
const type = SEGMENT_LETTER[c];
|
|
439
|
+
if (type) {
|
|
440
|
+
while (i < format.length && format[i] === c) i++;
|
|
441
|
+
tokens.push({ kind: "segment", type });
|
|
359
442
|
} else {
|
|
360
443
|
let text = "";
|
|
361
|
-
while (i <
|
|
362
|
-
text +=
|
|
444
|
+
while (i < format.length && !SEGMENT_LETTER[format[i]]) {
|
|
445
|
+
text += format[i];
|
|
363
446
|
i++;
|
|
364
447
|
}
|
|
365
448
|
tokens.push({ kind: "literal", text });
|
|
@@ -403,8 +486,8 @@ function stepDatePart(type, current, delta, parts) {
|
|
|
403
486
|
const dim = daysInMonth(year, monthIndex);
|
|
404
487
|
return ((current - 1 + delta) % dim + dim) % dim + 1;
|
|
405
488
|
}
|
|
406
|
-
function buildSegmentedValue(
|
|
407
|
-
const tokens = parseDateFormat(
|
|
489
|
+
function buildSegmentedValue(format, parts, active) {
|
|
490
|
+
const tokens = parseDateFormat(format);
|
|
408
491
|
let text = "";
|
|
409
492
|
const ranges = [];
|
|
410
493
|
for (const token of tokens) {
|
|
@@ -445,14 +528,14 @@ function isSegmentComplete(type, buffer) {
|
|
|
445
528
|
function daysInMonth(year, monthIndex) {
|
|
446
529
|
return new Date(year, monthIndex + 1, 0).getDate();
|
|
447
530
|
}
|
|
448
|
-
function getDateSegments(
|
|
531
|
+
function getDateSegments(format, value) {
|
|
449
532
|
const segments = [];
|
|
450
533
|
let fi = 0;
|
|
451
534
|
let vi = 0;
|
|
452
|
-
while (fi <
|
|
453
|
-
const fc =
|
|
535
|
+
while (fi < format.length && vi < value.length) {
|
|
536
|
+
const fc = format[fi];
|
|
454
537
|
if (fc === "d" || fc === "M" || fc === "y") {
|
|
455
|
-
while (fi <
|
|
538
|
+
while (fi < format.length && format[fi] === fc) fi++;
|
|
456
539
|
const start = vi;
|
|
457
540
|
while (vi < value.length && value[vi] >= "0" && value[vi] <= "9") vi++;
|
|
458
541
|
if (vi > start) {
|
|
@@ -497,7 +580,6 @@ function stepDateSegment(date, type, delta) {
|
|
|
497
580
|
// src/DatePicker.tsx
|
|
498
581
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
499
582
|
var PLACEMENT_OFFSET = 4;
|
|
500
|
-
var DEFAULT_DATE_FORMAT = "MM/dd/yyyy";
|
|
501
583
|
var RANGE_SEPARATOR = /\s*[-–—]\s*/;
|
|
502
584
|
var RANGE_DISPLAY_SEP = " - ";
|
|
503
585
|
var CalendarFieldIcon = () => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_xui_icons_base.Calendar, { size: "100%" });
|
|
@@ -599,6 +681,10 @@ var DatePicker = (0, import_react3.forwardRef)(
|
|
|
599
681
|
const editable = !readOnly && !rest.disabled;
|
|
600
682
|
const isDual = variant === "dual";
|
|
601
683
|
const isRange = isDual || selectsRange;
|
|
684
|
+
const resolvedFormat = (0, import_react3.useMemo)(
|
|
685
|
+
() => resolveDateFormat(dateFormat),
|
|
686
|
+
[dateFormat]
|
|
687
|
+
);
|
|
602
688
|
const propGroups = (0, import_react3.useCallback)(
|
|
603
689
|
() => isRange ? [datePartsFromDate(startDate), datePartsFromDate(endDate)] : [datePartsFromDate(selectedDate)],
|
|
604
690
|
[isRange, startDate, endDate, selectedDate]
|
|
@@ -646,7 +732,7 @@ var DatePicker = (0, import_react3.forwardRef)(
|
|
|
646
732
|
};
|
|
647
733
|
const composeField = () => {
|
|
648
734
|
const build = (group) => buildSegmentedValue(
|
|
649
|
-
|
|
735
|
+
resolvedFormat,
|
|
650
736
|
groups[group] ?? { day: null, month: null, year: null },
|
|
651
737
|
edit && edit.group === group ? { type: edit.type, buffer: edit.buffer } : void 0
|
|
652
738
|
);
|
|
@@ -893,8 +979,8 @@ var DatePicker = (0, import_react3.forwardRef)(
|
|
|
893
979
|
const parseOne = (token) => {
|
|
894
980
|
const trimmed = token.trim();
|
|
895
981
|
if (!trimmed) return null;
|
|
896
|
-
const parsed = (0,
|
|
897
|
-
return (0,
|
|
982
|
+
const parsed = (0, import_date_fns.parse)(trimmed, resolvedFormat, /* @__PURE__ */ new Date());
|
|
983
|
+
return (0, import_date_fns.isValid)(parsed) ? parsed : null;
|
|
898
984
|
};
|
|
899
985
|
const handlePaste = (e) => {
|
|
900
986
|
onPasteProp?.(e);
|
|
@@ -910,7 +996,7 @@ var DatePicker = (0, import_react3.forwardRef)(
|
|
|
910
996
|
const end = parseOne(endText ?? "");
|
|
911
997
|
if (!start) {
|
|
912
998
|
setParseError(
|
|
913
|
-
`Enter a valid date range (${
|
|
999
|
+
`Enter a valid date range (${resolvedFormat} - ${resolvedFormat})`
|
|
914
1000
|
);
|
|
915
1001
|
return;
|
|
916
1002
|
}
|
|
@@ -918,7 +1004,7 @@ var DatePicker = (0, import_react3.forwardRef)(
|
|
|
918
1004
|
} else {
|
|
919
1005
|
const date = parseOne(text);
|
|
920
1006
|
if (!date) {
|
|
921
|
-
setParseError(`Enter a valid date (${
|
|
1007
|
+
setParseError(`Enter a valid date (${resolvedFormat})`);
|
|
922
1008
|
return;
|
|
923
1009
|
}
|
|
924
1010
|
next = [datePartsFromDate(date)];
|
|
@@ -1114,7 +1200,7 @@ var DatePicker = (0, import_react3.forwardRef)(
|
|
|
1114
1200
|
...rest,
|
|
1115
1201
|
ref: inputRef,
|
|
1116
1202
|
size,
|
|
1117
|
-
placeholder: placeholder || (isRange ? "
|
|
1203
|
+
placeholder: placeholder || (isRange ? "Choose a date range" : "Choose a date"),
|
|
1118
1204
|
value: inputValue,
|
|
1119
1205
|
readOnly,
|
|
1120
1206
|
onFocus: handleFieldFocus,
|
|
@@ -1162,18 +1248,22 @@ DatePicker.displayName = "DatePicker";
|
|
|
1162
1248
|
CalendarChips,
|
|
1163
1249
|
CalendarGrid,
|
|
1164
1250
|
CalendarHeader,
|
|
1251
|
+
DEFAULT_DATE_FORMAT,
|
|
1165
1252
|
DatePicker,
|
|
1166
1253
|
DualCalendar,
|
|
1254
|
+
analyzeDateFormat,
|
|
1167
1255
|
buildSegmentedValue,
|
|
1168
1256
|
clampDatePart,
|
|
1169
1257
|
datePartsFromDate,
|
|
1170
1258
|
datePartsToDate,
|
|
1171
1259
|
daysInMonth,
|
|
1260
|
+
describeDateFormatIssues,
|
|
1172
1261
|
formatDate,
|
|
1173
1262
|
getDateSegments,
|
|
1174
1263
|
isDatePartsComplete,
|
|
1175
1264
|
isSegmentComplete,
|
|
1176
1265
|
parseDateFormat,
|
|
1266
|
+
resolveDateFormat,
|
|
1177
1267
|
stepDatePart,
|
|
1178
1268
|
stepDateSegment
|
|
1179
1269
|
});
|