@xsolla/xui-date-picker 0.206.0 → 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/native/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
|
|
@@ -269,15 +270,6 @@ import { Calendar, DualCalendar } from "@xsolla/xui-calendar";
|
|
|
269
270
|
import { Calendar as CalendarIcon } from "@xsolla/xui-icons-base";
|
|
270
271
|
|
|
271
272
|
// src/utils.ts
|
|
272
|
-
import { format } from "date-fns";
|
|
273
|
-
import * as locales from "date-fns/locale";
|
|
274
|
-
var defaultLocale = "enUS";
|
|
275
|
-
function formatDate(date, formatStr, locale = defaultLocale) {
|
|
276
|
-
const localeObj = locales[locale] || locales[defaultLocale];
|
|
277
|
-
return format(date, formatStr, {
|
|
278
|
-
locale: localeObj
|
|
279
|
-
});
|
|
280
|
-
}
|
|
281
273
|
var SEGMENT_WIDTH = {
|
|
282
274
|
day: 2,
|
|
283
275
|
month: 2,
|
|
@@ -288,21 +280,109 @@ var SEGMENT_TOKEN = {
|
|
|
288
280
|
month: "mm",
|
|
289
281
|
year: "yyyy"
|
|
290
282
|
};
|
|
291
|
-
|
|
283
|
+
var DEFAULT_DATE_FORMAT = "MM/dd/yyyy";
|
|
284
|
+
var SEGMENT_LETTER = {
|
|
285
|
+
d: "day",
|
|
286
|
+
M: "month",
|
|
287
|
+
y: "year"
|
|
288
|
+
};
|
|
289
|
+
var CASING_REPAIRS = { D: "d", Y: "y", m: "M" };
|
|
290
|
+
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.";
|
|
291
|
+
var ALL_SEGMENTS = ["day", "month", "year"];
|
|
292
|
+
var isLetter = (c) => /[A-Za-z]/.test(c);
|
|
293
|
+
var isSupportedRun = (letter, length) => {
|
|
294
|
+
if (letter === "y") return true;
|
|
295
|
+
if (letter === "d" || letter === "M") return length <= 2;
|
|
296
|
+
return false;
|
|
297
|
+
};
|
|
298
|
+
function analyzeDateFormat(format, fallback = DEFAULT_DATE_FORMAT) {
|
|
299
|
+
const issues = [];
|
|
300
|
+
const used = /* @__PURE__ */ new Set();
|
|
301
|
+
for (let i2 = 0; i2 < format.length; i2++) {
|
|
302
|
+
if (isLetter(format[i2])) used.add(format[i2]);
|
|
303
|
+
}
|
|
304
|
+
let repaired = "";
|
|
305
|
+
let hasUnsupported = false;
|
|
306
|
+
let i = 0;
|
|
307
|
+
while (i < format.length) {
|
|
308
|
+
const c = format[i];
|
|
309
|
+
if (!isLetter(c)) {
|
|
310
|
+
repaired += c;
|
|
311
|
+
i++;
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
let end = i;
|
|
315
|
+
while (end < format.length && format[end] === c) end++;
|
|
316
|
+
const run = format.slice(i, end);
|
|
317
|
+
i = end;
|
|
318
|
+
if (isSupportedRun(c, run.length)) {
|
|
319
|
+
repaired += run;
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
const repair = CASING_REPAIRS[c];
|
|
323
|
+
if (repair && !used.has(repair) && isSupportedRun(repair, run.length)) {
|
|
324
|
+
const fixed = repair.repeat(run.length);
|
|
325
|
+
issues.push({ token: run, kind: "casing", suggestion: fixed });
|
|
326
|
+
repaired += fixed;
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
issues.push({ token: run, kind: "unsupported" });
|
|
330
|
+
hasUnsupported = true;
|
|
331
|
+
repaired += run;
|
|
332
|
+
}
|
|
333
|
+
const present = new Set(
|
|
334
|
+
parseDateFormat(repaired).filter(
|
|
335
|
+
(t) => t.kind === "segment"
|
|
336
|
+
).map((t) => t.type)
|
|
337
|
+
);
|
|
338
|
+
const missing = ALL_SEGMENTS.filter((type) => !present.has(type));
|
|
339
|
+
for (const type of missing) issues.push({ token: type, kind: "missing" });
|
|
340
|
+
const usable = !hasUnsupported && missing.length === 0;
|
|
341
|
+
return {
|
|
342
|
+
format: usable ? repaired : fallback,
|
|
343
|
+
usedFallback: !usable,
|
|
344
|
+
issues
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
function describeDateFormatIssues(format, analysis) {
|
|
348
|
+
const clauses = analysis.issues.map((issue) => {
|
|
349
|
+
if (issue.kind === "missing") {
|
|
350
|
+
return `it has no ${issue.token} segment, so the field can never be completed`;
|
|
351
|
+
}
|
|
352
|
+
if (issue.kind === "casing") {
|
|
353
|
+
return `\`${issue.token}\` is not a \`date-fns\` date token \u2014 use \`${issue.suggestion}\``;
|
|
354
|
+
}
|
|
355
|
+
return `\`${issue.token}\` is not a supported token`;
|
|
356
|
+
});
|
|
357
|
+
const resolution = analysis.usedFallback ? `Falling back to \`${analysis.format}\`.` : `Reading it as \`${analysis.format}\`.`;
|
|
358
|
+
return `[DatePicker] Invalid \`dateFormat\` \`${format}\`: ${clauses.join("; ")}. ${resolution} Note ${SUPPORTED_TOKENS_HINT}`;
|
|
359
|
+
}
|
|
360
|
+
var warnedFormats = /* @__PURE__ */ new Set();
|
|
361
|
+
function resolveDateFormat(format, fallback = DEFAULT_DATE_FORMAT) {
|
|
362
|
+
const analysis = analyzeDateFormat(format, fallback);
|
|
363
|
+
if (analysis.issues.length > 0) {
|
|
364
|
+
const key = `${format}|${fallback}`;
|
|
365
|
+
const isProduction = globalThis.process?.env?.NODE_ENV === "production";
|
|
366
|
+
if (!isProduction && !warnedFormats.has(key)) {
|
|
367
|
+
warnedFormats.add(key);
|
|
368
|
+
console.warn(describeDateFormatIssues(format, analysis));
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return analysis.format;
|
|
372
|
+
}
|
|
373
|
+
function parseDateFormat(format) {
|
|
292
374
|
const tokens = [];
|
|
293
375
|
let i = 0;
|
|
294
|
-
while (i <
|
|
295
|
-
const c =
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
type: c === "d" ? "day" : c === "M" ? "month" : "year"
|
|
301
|
-
});
|
|
376
|
+
while (i < format.length) {
|
|
377
|
+
const c = format[i];
|
|
378
|
+
const type = SEGMENT_LETTER[c];
|
|
379
|
+
if (type) {
|
|
380
|
+
while (i < format.length && format[i] === c) i++;
|
|
381
|
+
tokens.push({ kind: "segment", type });
|
|
302
382
|
} else {
|
|
303
383
|
let text = "";
|
|
304
|
-
while (i <
|
|
305
|
-
text +=
|
|
384
|
+
while (i < format.length && !SEGMENT_LETTER[format[i]]) {
|
|
385
|
+
text += format[i];
|
|
306
386
|
i++;
|
|
307
387
|
}
|
|
308
388
|
tokens.push({ kind: "literal", text });
|
|
@@ -346,8 +426,8 @@ function stepDatePart(type, current, delta, parts) {
|
|
|
346
426
|
const dim = daysInMonth(year, monthIndex);
|
|
347
427
|
return ((current - 1 + delta) % dim + dim) % dim + 1;
|
|
348
428
|
}
|
|
349
|
-
function buildSegmentedValue(
|
|
350
|
-
const tokens = parseDateFormat(
|
|
429
|
+
function buildSegmentedValue(format, parts, active) {
|
|
430
|
+
const tokens = parseDateFormat(format);
|
|
351
431
|
let text = "";
|
|
352
432
|
const ranges = [];
|
|
353
433
|
for (const token of tokens) {
|
|
@@ -388,14 +468,14 @@ function isSegmentComplete(type, buffer) {
|
|
|
388
468
|
function daysInMonth(year, monthIndex) {
|
|
389
469
|
return new Date(year, monthIndex + 1, 0).getDate();
|
|
390
470
|
}
|
|
391
|
-
function getDateSegments(
|
|
471
|
+
function getDateSegments(format, value) {
|
|
392
472
|
const segments = [];
|
|
393
473
|
let fi = 0;
|
|
394
474
|
let vi = 0;
|
|
395
|
-
while (fi <
|
|
396
|
-
const fc =
|
|
475
|
+
while (fi < format.length && vi < value.length) {
|
|
476
|
+
const fc = format[fi];
|
|
397
477
|
if (fc === "d" || fc === "M" || fc === "y") {
|
|
398
|
-
while (fi <
|
|
478
|
+
while (fi < format.length && format[fi] === fc) fi++;
|
|
399
479
|
const start = vi;
|
|
400
480
|
while (vi < value.length && value[vi] >= "0" && value[vi] <= "9") vi++;
|
|
401
481
|
if (vi > start) {
|
|
@@ -440,7 +520,6 @@ function stepDateSegment(date, type, delta) {
|
|
|
440
520
|
// src/DatePicker.tsx
|
|
441
521
|
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
442
522
|
var PLACEMENT_OFFSET = 4;
|
|
443
|
-
var DEFAULT_DATE_FORMAT = "MM/dd/yyyy";
|
|
444
523
|
var RANGE_SEPARATOR = /\s*[-–—]\s*/;
|
|
445
524
|
var RANGE_DISPLAY_SEP = " - ";
|
|
446
525
|
var CalendarFieldIcon = () => /* @__PURE__ */ jsx2(CalendarIcon, { size: "100%" });
|
|
@@ -542,6 +621,10 @@ var DatePicker = forwardRef(
|
|
|
542
621
|
const editable = !readOnly && !rest.disabled;
|
|
543
622
|
const isDual = variant === "dual";
|
|
544
623
|
const isRange = isDual || selectsRange;
|
|
624
|
+
const resolvedFormat = useMemo(
|
|
625
|
+
() => resolveDateFormat(dateFormat),
|
|
626
|
+
[dateFormat]
|
|
627
|
+
);
|
|
545
628
|
const propGroups = useCallback(
|
|
546
629
|
() => isRange ? [datePartsFromDate(startDate), datePartsFromDate(endDate)] : [datePartsFromDate(selectedDate)],
|
|
547
630
|
[isRange, startDate, endDate, selectedDate]
|
|
@@ -589,7 +672,7 @@ var DatePicker = forwardRef(
|
|
|
589
672
|
};
|
|
590
673
|
const composeField = () => {
|
|
591
674
|
const build = (group) => buildSegmentedValue(
|
|
592
|
-
|
|
675
|
+
resolvedFormat,
|
|
593
676
|
groups[group] ?? { day: null, month: null, year: null },
|
|
594
677
|
edit && edit.group === group ? { type: edit.type, buffer: edit.buffer } : void 0
|
|
595
678
|
);
|
|
@@ -836,7 +919,7 @@ var DatePicker = forwardRef(
|
|
|
836
919
|
const parseOne = (token) => {
|
|
837
920
|
const trimmed = token.trim();
|
|
838
921
|
if (!trimmed) return null;
|
|
839
|
-
const parsed = parse(trimmed,
|
|
922
|
+
const parsed = parse(trimmed, resolvedFormat, /* @__PURE__ */ new Date());
|
|
840
923
|
return isValid(parsed) ? parsed : null;
|
|
841
924
|
};
|
|
842
925
|
const handlePaste = (e) => {
|
|
@@ -853,7 +936,7 @@ var DatePicker = forwardRef(
|
|
|
853
936
|
const end = parseOne(endText ?? "");
|
|
854
937
|
if (!start) {
|
|
855
938
|
setParseError(
|
|
856
|
-
`Enter a valid date range (${
|
|
939
|
+
`Enter a valid date range (${resolvedFormat} - ${resolvedFormat})`
|
|
857
940
|
);
|
|
858
941
|
return;
|
|
859
942
|
}
|
|
@@ -861,7 +944,7 @@ var DatePicker = forwardRef(
|
|
|
861
944
|
} else {
|
|
862
945
|
const date = parseOne(text);
|
|
863
946
|
if (!date) {
|
|
864
|
-
setParseError(`Enter a valid date (${
|
|
947
|
+
setParseError(`Enter a valid date (${resolvedFormat})`);
|
|
865
948
|
return;
|
|
866
949
|
}
|
|
867
950
|
next = [datePartsFromDate(date)];
|
|
@@ -1057,7 +1140,7 @@ var DatePicker = forwardRef(
|
|
|
1057
1140
|
...rest,
|
|
1058
1141
|
ref: inputRef,
|
|
1059
1142
|
size,
|
|
1060
|
-
placeholder: placeholder || (isRange ? "
|
|
1143
|
+
placeholder: placeholder || (isRange ? "Choose a date range" : "Choose a date"),
|
|
1061
1144
|
value: inputValue,
|
|
1062
1145
|
readOnly,
|
|
1063
1146
|
onFocus: handleFieldFocus,
|
|
@@ -1104,18 +1187,22 @@ export {
|
|
|
1104
1187
|
CalendarChips,
|
|
1105
1188
|
CalendarGrid,
|
|
1106
1189
|
CalendarHeader,
|
|
1190
|
+
DEFAULT_DATE_FORMAT,
|
|
1107
1191
|
DatePicker,
|
|
1108
1192
|
DualCalendar2 as DualCalendar,
|
|
1193
|
+
analyzeDateFormat,
|
|
1109
1194
|
buildSegmentedValue,
|
|
1110
1195
|
clampDatePart,
|
|
1111
1196
|
datePartsFromDate,
|
|
1112
1197
|
datePartsToDate,
|
|
1113
1198
|
daysInMonth,
|
|
1199
|
+
describeDateFormatIssues,
|
|
1114
1200
|
formatDate,
|
|
1115
1201
|
getDateSegments,
|
|
1116
1202
|
isDatePartsComplete,
|
|
1117
1203
|
isSegmentComplete,
|
|
1118
1204
|
parseDateFormat,
|
|
1205
|
+
resolveDateFormat,
|
|
1119
1206
|
stepDatePart,
|
|
1120
1207
|
stepDateSegment
|
|
1121
1208
|
};
|