@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 CHANGED
@@ -13,7 +13,7 @@ It’s a form field that lets users select a single date or date range. It is al
13
13
  - When the date is derived automatically and not user-selected
14
14
 
15
15
  ### Content guidelines
16
- - Placeholder text: use *"Choose a date"* for single mode and *"Enter a date range"* for range mode — these are the defaults.
16
+ - Placeholder text: use *"Choose a date"* for single mode and *"Choose a date range"* for range mode — these are the defaults.
17
17
  - Labels (via Field Group): keep them short and clear — *"Date"*, *"Start date"*, *"Report period"*.
18
18
  - Error messages: explain what went wrong — *"Please select a date"* or *"End date must be after start date"*.
19
19
  - Use standard labels — Today, Yesterday, Last 7 days, Last 30 days, This month, Last month. Chip labels are shown directly in the DatePicker field when a chip is selected, so keep them short and self-explanatory — the label must make sense both in the chip row and as standalone field content.
@@ -96,7 +96,8 @@ const [date, setDate] = useState<Date | null>(null);
96
96
  | `endDate` | `Date \| null` | — | Range end date. |
97
97
  | `selectsRange` | `boolean` | `false` | Enable range selection. |
98
98
  | `onChange` | `(date: Date \| DateRangeType) => void` | — | Fired when the date or range changes. |
99
- | `placeholder` | `string` | `'MM/DD/YYYY'` | Placeholder for the input. |
99
+ | `placeholder` | `string` | `'Choose a date'` / `'Choose a date range'` | Placeholder for the input, shown while it is empty. Not derived from `dateFormat`. |
100
+ | `dateFormat` | `string` | `'MM/dd/yyyy'` | `date-fns` format used to render and parse the field. Numeric tokens only — see [Date formats](#date-formats). |
100
101
  | `size` | `'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl'` | `'md'` | Input size. |
101
102
  | `disabled` | `boolean` | `false` | Disable the input. |
102
103
  | `backgroundColor` | `string` | — | Custom background colour for the input. |
@@ -122,6 +123,28 @@ const [date, setDate] = useState<Date | null>(null);
122
123
 
123
124
  Inherits `ThemeOverrideProps` (`themeMode`, `themeProductContext`).
124
125
 
126
+ ### Date formats
127
+
128
+ The field is edited segment by segment (type digits, arrow-step a segment, tab between them), so `dateFormat` supports **numeric tokens only** — exactly three, in any order, with anything between them treated as a literal separator:
129
+
130
+ | Token | Segment | Rendered as |
131
+ | --- | --- | --- |
132
+ | `d`, `dd` | Day | Always two digits (`05`) |
133
+ | `M`, `MM` | Month | Always two digits (`03`) |
134
+ | `y`, `yyyy` | Year | Always four digits (`2026`) |
135
+
136
+ ```tsx
137
+ <DatePicker dateFormat="dd/MM/yyyy" /> // 15/03/2026
138
+ <DatePicker dateFormat="yyyy-MM-dd" /> // 2026-03-15
139
+ <DatePicker dateFormat="dd.MM.yyyy" /> // 15.03.2026
140
+ ```
141
+
142
+ While the field is focused or partly filled it shows a mask built from the format (`dd/mm/yyyy`), which is separate from `placeholder` — the placeholder shows only while the field is empty and does not track `dateFormat`.
143
+
144
+ **Mind the casing.** `date-fns` tokens are not Moment.js/Day.js tokens: month is `MM` (`mm` is *minutes*), day is `dd` (`DD` is day-of-year) and year is `yyyy` (`YYYY` is week-numbering year). Passing `dd/mm/yyyy`, `DD/MM/YYYY`, `dd/MM/YYYY` or `DD.MM.YYYY` is read as the intended `dd/MM/yyyy` and logs a `console.warn` in development naming the token and its replacement. Fix the format string rather than relying on the repair.
145
+
146
+ **Not supported:** textual and localised tokens (`MMM`, `MMMM`, `P`, `PP`), time tokens (`HH:mm`), escaped literals (`'de'`), and formats missing one of the three segments (`MM/yyyy` — the field could never be completed, so `onChange` would never fire). These warn in development and fall back to `MM/dd/yyyy`, so the field stays completable and never renders raw token text as though it were a value. To display a date as `Mar 15, 2026`, format it yourself with `formatDate` outside the picker.
147
+
125
148
  ### `<Calendar>`
126
149
 
127
150
  Re-exported from `@xsolla/xui-calendar`. Standalone single-month calendar.
@@ -146,7 +169,7 @@ Re-exported from `@xsolla/xui-calendar`. Preset chips bar (e.g. "Last 7 days", "
146
169
 
147
170
  | Export | Type | Description |
148
171
  | --- | --- | --- |
149
- | `formatDate` | `(date: Date, formatStr: string, locale?: CalendarLocaleType) => string` | Format a date with a `date-fns` locale identifier. |
172
+ | `formatDate` | `(date: Date, formatStr: string, locale?: CalendarLocaleType) => string` | Format a date with a `date-fns` locale identifier. Re-exported from `@xsolla/xui-calendar`; the full `date-fns` token set applies, so use it for display text the picker's own field cannot render (`MMM d, yyyy`). |
150
173
 
151
174
  ### Types
152
175
 
@@ -1,5 +1,5 @@
1
- import { CalendarProps, CalendarLocaleType } from '@xsolla/xui-calendar';
2
- export { Calendar, CalendarChipOption, CalendarChips, CalendarChipsProps, CalendarGrid, CalendarGridProps, CalendarHeader, CalendarHeaderProps, CalendarLocaleType, CalendarProps, DualCalendar, DualCalendarProps } from '@xsolla/xui-calendar';
1
+ import { CalendarProps } from '@xsolla/xui-calendar';
2
+ export { Calendar, CalendarChipOption, CalendarChips, CalendarChipsProps, CalendarGrid, CalendarGridProps, CalendarHeader, CalendarHeaderProps, CalendarLocaleType, CalendarProps, DualCalendar, DualCalendarProps, formatDate } from '@xsolla/xui-calendar';
3
3
  import * as react from 'react';
4
4
  import { ReactNode, FocusEvent, MouseEvent, KeyboardEvent, ClipboardEvent } from 'react';
5
5
  import { ThemeMode, ProductContext } from '@xsolla/xui-core';
@@ -44,11 +44,7 @@ interface DatePickerProps extends Omit<CalendarProps, "onChange"> {
44
44
  * Placeholder text for the input
45
45
  */
46
46
  placeholder?: string;
47
- /**
48
- * date-fns format string used to both render and parse the field's text value
49
- * (e.g. `"dd/MM/yyyy"`). Applies to each date in a range. Defaults to
50
- * `"MM/dd/yyyy"`.
51
- */
47
+ /** `date-fns` format for the field, defaulting to `"MM/dd/yyyy"`. Numeric `d`/`dd`, `M`/`MM` and `y`/`yyyy` tokens only, in any order (mind the casing — `mm` is minutes); anything else warns in development and falls back to the default. */
52
48
  dateFormat?: string;
53
49
  /**
54
50
  * Size of the input
@@ -130,7 +126,6 @@ interface DatePickerProps extends Omit<CalendarProps, "onChange"> {
130
126
 
131
127
  declare const DatePicker: react.ForwardRefExoticComponent<DatePickerProps & react.RefAttributes<any>>;
132
128
 
133
- declare function formatDate(date: Date, formatStr: string, locale?: CalendarLocaleType): string;
134
129
  type DateSegmentType = "day" | "month" | "year";
135
130
  /** A date broken into editable parts; `null` means "not yet filled". */
136
131
  interface DateParts {
@@ -152,6 +147,28 @@ type FormatToken = {
152
147
  kind: "literal";
153
148
  text: string;
154
149
  };
150
+ /** Default format for rendering/parsing the field's text value (overridable via `dateFormat`). */
151
+ declare const DEFAULT_DATE_FORMAT = "MM/dd/yyyy";
152
+ /** What is wrong with one token of a `dateFormat`. */
153
+ interface DateFormatIssue {
154
+ /** Offending token as written, or the segment name for `kind: "missing"`. */
155
+ token: string;
156
+ kind: "casing" | "unsupported" | "missing";
157
+ /** The `date-fns` token to use instead (`casing` only). */
158
+ suggestion?: string;
159
+ }
160
+ interface DateFormatAnalysis {
161
+ /** The format the field should actually use (repaired input, or the fallback). */
162
+ format: string;
163
+ usedFallback: boolean;
164
+ issues: DateFormatIssue[];
165
+ }
166
+ /** Repair a caller's `dateFormat` where the case is unambiguously wrong, else fall back when it is unusable (unsupported token, or a missing segment). */
167
+ declare function analyzeDateFormat(format: string, fallback?: string): DateFormatAnalysis;
168
+ /** Human-readable warning for a failed {@link analyzeDateFormat}. */
169
+ declare function describeDateFormatIssues(format: string, analysis: DateFormatAnalysis): string;
170
+ /** Resolve a `dateFormat` to one the field can render, warning once per offending format in development. See {@link analyzeDateFormat}. */
171
+ declare function resolveDateFormat(format: string, fallback?: string): string;
155
172
  /** Split a date format into ordered segment tokens and literal separators. */
156
173
  declare function parseDateFormat(format: string): FormatToken[];
157
174
  declare function datePartsFromDate(date: Date | null | undefined): DateParts;
@@ -202,4 +219,4 @@ declare function getDateSegments(format: string, value: string): DateSegment[];
202
219
  */
203
220
  declare function stepDateSegment(date: Date, type: DateSegmentType, delta: number): Date;
204
221
 
205
- export { type DateParts, DatePicker, type DatePickerAlignment, type DatePickerDropdownPosition, type DatePickerPlacement, type DatePickerProps, type DatePickerSide, type DateRangeType, type DateSegment, type DateSegmentType, type SegmentRange, buildSegmentedValue, clampDatePart, datePartsFromDate, datePartsToDate, daysInMonth, formatDate, getDateSegments, isDatePartsComplete, isSegmentComplete, parseDateFormat, stepDatePart, stepDateSegment };
222
+ export { DEFAULT_DATE_FORMAT, type DateFormatAnalysis, type DateFormatIssue, type DateParts, DatePicker, type DatePickerAlignment, type DatePickerDropdownPosition, type DatePickerPlacement, type DatePickerProps, type DatePickerSide, type DateRangeType, type DateSegment, type DateSegmentType, type SegmentRange, analyzeDateFormat, buildSegmentedValue, clampDatePart, datePartsFromDate, datePartsToDate, daysInMonth, describeDateFormatIssues, getDateSegments, isDatePartsComplete, isSegmentComplete, parseDateFormat, resolveDateFormat, stepDatePart, stepDateSegment };
package/native/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { CalendarProps, CalendarLocaleType } from '@xsolla/xui-calendar';
2
- export { Calendar, CalendarChipOption, CalendarChips, CalendarChipsProps, CalendarGrid, CalendarGridProps, CalendarHeader, CalendarHeaderProps, CalendarLocaleType, CalendarProps, DualCalendar, DualCalendarProps } from '@xsolla/xui-calendar';
1
+ import { CalendarProps } from '@xsolla/xui-calendar';
2
+ export { Calendar, CalendarChipOption, CalendarChips, CalendarChipsProps, CalendarGrid, CalendarGridProps, CalendarHeader, CalendarHeaderProps, CalendarLocaleType, CalendarProps, DualCalendar, DualCalendarProps, formatDate } from '@xsolla/xui-calendar';
3
3
  import * as react from 'react';
4
4
  import { ReactNode, FocusEvent, MouseEvent, KeyboardEvent, ClipboardEvent } from 'react';
5
5
  import { ThemeMode, ProductContext } from '@xsolla/xui-core';
@@ -44,11 +44,7 @@ interface DatePickerProps extends Omit<CalendarProps, "onChange"> {
44
44
  * Placeholder text for the input
45
45
  */
46
46
  placeholder?: string;
47
- /**
48
- * date-fns format string used to both render and parse the field's text value
49
- * (e.g. `"dd/MM/yyyy"`). Applies to each date in a range. Defaults to
50
- * `"MM/dd/yyyy"`.
51
- */
47
+ /** `date-fns` format for the field, defaulting to `"MM/dd/yyyy"`. Numeric `d`/`dd`, `M`/`MM` and `y`/`yyyy` tokens only, in any order (mind the casing — `mm` is minutes); anything else warns in development and falls back to the default. */
52
48
  dateFormat?: string;
53
49
  /**
54
50
  * Size of the input
@@ -130,7 +126,6 @@ interface DatePickerProps extends Omit<CalendarProps, "onChange"> {
130
126
 
131
127
  declare const DatePicker: react.ForwardRefExoticComponent<DatePickerProps & react.RefAttributes<any>>;
132
128
 
133
- declare function formatDate(date: Date, formatStr: string, locale?: CalendarLocaleType): string;
134
129
  type DateSegmentType = "day" | "month" | "year";
135
130
  /** A date broken into editable parts; `null` means "not yet filled". */
136
131
  interface DateParts {
@@ -152,6 +147,28 @@ type FormatToken = {
152
147
  kind: "literal";
153
148
  text: string;
154
149
  };
150
+ /** Default format for rendering/parsing the field's text value (overridable via `dateFormat`). */
151
+ declare const DEFAULT_DATE_FORMAT = "MM/dd/yyyy";
152
+ /** What is wrong with one token of a `dateFormat`. */
153
+ interface DateFormatIssue {
154
+ /** Offending token as written, or the segment name for `kind: "missing"`. */
155
+ token: string;
156
+ kind: "casing" | "unsupported" | "missing";
157
+ /** The `date-fns` token to use instead (`casing` only). */
158
+ suggestion?: string;
159
+ }
160
+ interface DateFormatAnalysis {
161
+ /** The format the field should actually use (repaired input, or the fallback). */
162
+ format: string;
163
+ usedFallback: boolean;
164
+ issues: DateFormatIssue[];
165
+ }
166
+ /** Repair a caller's `dateFormat` where the case is unambiguously wrong, else fall back when it is unusable (unsupported token, or a missing segment). */
167
+ declare function analyzeDateFormat(format: string, fallback?: string): DateFormatAnalysis;
168
+ /** Human-readable warning for a failed {@link analyzeDateFormat}. */
169
+ declare function describeDateFormatIssues(format: string, analysis: DateFormatAnalysis): string;
170
+ /** Resolve a `dateFormat` to one the field can render, warning once per offending format in development. See {@link analyzeDateFormat}. */
171
+ declare function resolveDateFormat(format: string, fallback?: string): string;
155
172
  /** Split a date format into ordered segment tokens and literal separators. */
156
173
  declare function parseDateFormat(format: string): FormatToken[];
157
174
  declare function datePartsFromDate(date: Date | null | undefined): DateParts;
@@ -202,4 +219,4 @@ declare function getDateSegments(format: string, value: string): DateSegment[];
202
219
  */
203
220
  declare function stepDateSegment(date: Date, type: DateSegmentType, delta: number): Date;
204
221
 
205
- export { type DateParts, DatePicker, type DatePickerAlignment, type DatePickerDropdownPosition, type DatePickerPlacement, type DatePickerProps, type DatePickerSide, type DateRangeType, type DateSegment, type DateSegmentType, type SegmentRange, buildSegmentedValue, clampDatePart, datePartsFromDate, datePartsToDate, daysInMonth, formatDate, getDateSegments, isDatePartsComplete, isSegmentComplete, parseDateFormat, stepDatePart, stepDateSegment };
222
+ export { DEFAULT_DATE_FORMAT, type DateFormatAnalysis, type DateFormatIssue, type DateParts, DatePicker, type DatePickerAlignment, type DatePickerDropdownPosition, type DatePickerPlacement, type DatePickerProps, type DatePickerSide, type DateRangeType, type DateSegment, type DateSegmentType, type SegmentRange, analyzeDateFormat, buildSegmentedValue, clampDatePart, datePartsFromDate, datePartsToDate, daysInMonth, describeDateFormatIssues, getDateSegments, isDatePartsComplete, isSegmentComplete, parseDateFormat, resolveDateFormat, stepDatePart, stepDateSegment };
package/native/index.js CHANGED
@@ -1,9 +1,7 @@
1
1
  "use strict";
2
- var __create = Object.create;
3
2
  var __defProp = Object.defineProperty;
4
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
6
  var __export = (target, all) => {
9
7
  for (var name in all)
@@ -17,14 +15,6 @@ var __copyProps = (to, from, except, desc) => {
17
15
  }
18
16
  return to;
19
17
  };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
19
 
30
20
  // src/index.tsx
@@ -34,18 +24,22 @@ __export(index_exports, {
34
24
  CalendarChips: () => import_xui_calendar2.CalendarChips,
35
25
  CalendarGrid: () => import_xui_calendar2.CalendarGrid,
36
26
  CalendarHeader: () => import_xui_calendar2.CalendarHeader,
27
+ DEFAULT_DATE_FORMAT: () => DEFAULT_DATE_FORMAT,
37
28
  DatePicker: () => DatePicker,
38
29
  DualCalendar: () => import_xui_calendar2.DualCalendar,
30
+ analyzeDateFormat: () => analyzeDateFormat,
39
31
  buildSegmentedValue: () => buildSegmentedValue,
40
32
  clampDatePart: () => clampDatePart,
41
33
  datePartsFromDate: () => datePartsFromDate,
42
34
  datePartsToDate: () => datePartsToDate,
43
35
  daysInMonth: () => daysInMonth,
44
- formatDate: () => formatDate,
36
+ describeDateFormatIssues: () => describeDateFormatIssues,
37
+ formatDate: () => import_xui_calendar2.formatDate,
45
38
  getDateSegments: () => getDateSegments,
46
39
  isDatePartsComplete: () => isDatePartsComplete,
47
40
  isSegmentComplete: () => isSegmentComplete,
48
41
  parseDateFormat: () => parseDateFormat,
42
+ resolveDateFormat: () => resolveDateFormat,
49
43
  stepDatePart: () => stepDatePart,
50
44
  stepDateSegment: () => stepDateSegment
51
45
  });
@@ -298,20 +292,11 @@ var isNative = true;
298
292
 
299
293
  // src/DatePicker.tsx
300
294
  var import_xui_input = require("@xsolla/xui-input");
301
- var import_date_fns2 = require("date-fns");
295
+ var import_date_fns = require("date-fns");
302
296
  var import_xui_calendar = require("@xsolla/xui-calendar");
303
297
  var import_xui_icons_base = require("@xsolla/xui-icons-base");
304
298
 
305
299
  // src/utils.ts
306
- var import_date_fns = require("date-fns");
307
- var locales = __toESM(require("date-fns/locale"));
308
- var defaultLocale = "enUS";
309
- function formatDate(date, formatStr, locale = defaultLocale) {
310
- const localeObj = locales[locale] || locales[defaultLocale];
311
- return (0, import_date_fns.format)(date, formatStr, {
312
- locale: localeObj
313
- });
314
- }
315
300
  var SEGMENT_WIDTH = {
316
301
  day: 2,
317
302
  month: 2,
@@ -322,21 +307,109 @@ var SEGMENT_TOKEN = {
322
307
  month: "mm",
323
308
  year: "yyyy"
324
309
  };
325
- function parseDateFormat(format2) {
310
+ var DEFAULT_DATE_FORMAT = "MM/dd/yyyy";
311
+ var SEGMENT_LETTER = {
312
+ d: "day",
313
+ M: "month",
314
+ y: "year"
315
+ };
316
+ var CASING_REPAIRS = { D: "d", Y: "y", m: "M" };
317
+ 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.";
318
+ var ALL_SEGMENTS = ["day", "month", "year"];
319
+ var isLetter = (c) => /[A-Za-z]/.test(c);
320
+ var isSupportedRun = (letter, length) => {
321
+ if (letter === "y") return true;
322
+ if (letter === "d" || letter === "M") return length <= 2;
323
+ return false;
324
+ };
325
+ function analyzeDateFormat(format, fallback = DEFAULT_DATE_FORMAT) {
326
+ const issues = [];
327
+ const used = /* @__PURE__ */ new Set();
328
+ for (let i2 = 0; i2 < format.length; i2++) {
329
+ if (isLetter(format[i2])) used.add(format[i2]);
330
+ }
331
+ let repaired = "";
332
+ let hasUnsupported = false;
333
+ let i = 0;
334
+ while (i < format.length) {
335
+ const c = format[i];
336
+ if (!isLetter(c)) {
337
+ repaired += c;
338
+ i++;
339
+ continue;
340
+ }
341
+ let end = i;
342
+ while (end < format.length && format[end] === c) end++;
343
+ const run = format.slice(i, end);
344
+ i = end;
345
+ if (isSupportedRun(c, run.length)) {
346
+ repaired += run;
347
+ continue;
348
+ }
349
+ const repair = CASING_REPAIRS[c];
350
+ if (repair && !used.has(repair) && isSupportedRun(repair, run.length)) {
351
+ const fixed = repair.repeat(run.length);
352
+ issues.push({ token: run, kind: "casing", suggestion: fixed });
353
+ repaired += fixed;
354
+ continue;
355
+ }
356
+ issues.push({ token: run, kind: "unsupported" });
357
+ hasUnsupported = true;
358
+ repaired += run;
359
+ }
360
+ const present = new Set(
361
+ parseDateFormat(repaired).filter(
362
+ (t) => t.kind === "segment"
363
+ ).map((t) => t.type)
364
+ );
365
+ const missing = ALL_SEGMENTS.filter((type) => !present.has(type));
366
+ for (const type of missing) issues.push({ token: type, kind: "missing" });
367
+ const usable = !hasUnsupported && missing.length === 0;
368
+ return {
369
+ format: usable ? repaired : fallback,
370
+ usedFallback: !usable,
371
+ issues
372
+ };
373
+ }
374
+ function describeDateFormatIssues(format, analysis) {
375
+ const clauses = analysis.issues.map((issue) => {
376
+ if (issue.kind === "missing") {
377
+ return `it has no ${issue.token} segment, so the field can never be completed`;
378
+ }
379
+ if (issue.kind === "casing") {
380
+ return `\`${issue.token}\` is not a \`date-fns\` date token \u2014 use \`${issue.suggestion}\``;
381
+ }
382
+ return `\`${issue.token}\` is not a supported token`;
383
+ });
384
+ const resolution = analysis.usedFallback ? `Falling back to \`${analysis.format}\`.` : `Reading it as \`${analysis.format}\`.`;
385
+ return `[DatePicker] Invalid \`dateFormat\` \`${format}\`: ${clauses.join("; ")}. ${resolution} Note ${SUPPORTED_TOKENS_HINT}`;
386
+ }
387
+ var warnedFormats = /* @__PURE__ */ new Set();
388
+ function resolveDateFormat(format, fallback = DEFAULT_DATE_FORMAT) {
389
+ const analysis = analyzeDateFormat(format, fallback);
390
+ if (analysis.issues.length > 0) {
391
+ const key = `${format}|${fallback}`;
392
+ const isProduction = globalThis.process?.env?.NODE_ENV === "production";
393
+ if (!isProduction && !warnedFormats.has(key)) {
394
+ warnedFormats.add(key);
395
+ console.warn(describeDateFormatIssues(format, analysis));
396
+ }
397
+ }
398
+ return analysis.format;
399
+ }
400
+ function parseDateFormat(format) {
326
401
  const tokens = [];
327
402
  let i = 0;
328
- while (i < format2.length) {
329
- const c = format2[i];
330
- if (c === "d" || c === "M" || c === "y") {
331
- while (i < format2.length && format2[i] === c) i++;
332
- tokens.push({
333
- kind: "segment",
334
- type: c === "d" ? "day" : c === "M" ? "month" : "year"
335
- });
403
+ while (i < format.length) {
404
+ const c = format[i];
405
+ const type = SEGMENT_LETTER[c];
406
+ if (type) {
407
+ while (i < format.length && format[i] === c) i++;
408
+ tokens.push({ kind: "segment", type });
336
409
  } else {
337
410
  let text = "";
338
- while (i < format2.length && !"dMy".includes(format2[i])) {
339
- text += format2[i];
411
+ while (i < format.length && !SEGMENT_LETTER[format[i]]) {
412
+ text += format[i];
340
413
  i++;
341
414
  }
342
415
  tokens.push({ kind: "literal", text });
@@ -380,8 +453,8 @@ function stepDatePart(type, current, delta, parts) {
380
453
  const dim = daysInMonth(year, monthIndex);
381
454
  return ((current - 1 + delta) % dim + dim) % dim + 1;
382
455
  }
383
- function buildSegmentedValue(format2, parts, active) {
384
- const tokens = parseDateFormat(format2);
456
+ function buildSegmentedValue(format, parts, active) {
457
+ const tokens = parseDateFormat(format);
385
458
  let text = "";
386
459
  const ranges = [];
387
460
  for (const token of tokens) {
@@ -422,14 +495,14 @@ function isSegmentComplete(type, buffer) {
422
495
  function daysInMonth(year, monthIndex) {
423
496
  return new Date(year, monthIndex + 1, 0).getDate();
424
497
  }
425
- function getDateSegments(format2, value) {
498
+ function getDateSegments(format, value) {
426
499
  const segments = [];
427
500
  let fi = 0;
428
501
  let vi = 0;
429
- while (fi < format2.length && vi < value.length) {
430
- const fc = format2[fi];
502
+ while (fi < format.length && vi < value.length) {
503
+ const fc = format[fi];
431
504
  if (fc === "d" || fc === "M" || fc === "y") {
432
- while (fi < format2.length && format2[fi] === fc) fi++;
505
+ while (fi < format.length && format[fi] === fc) fi++;
433
506
  const start = vi;
434
507
  while (vi < value.length && value[vi] >= "0" && value[vi] <= "9") vi++;
435
508
  if (vi > start) {
@@ -474,7 +547,6 @@ function stepDateSegment(date, type, delta) {
474
547
  // src/DatePicker.tsx
475
548
  var import_jsx_runtime2 = require("react/jsx-runtime");
476
549
  var PLACEMENT_OFFSET = 4;
477
- var DEFAULT_DATE_FORMAT = "MM/dd/yyyy";
478
550
  var RANGE_SEPARATOR = /\s*[-–—]\s*/;
479
551
  var RANGE_DISPLAY_SEP = " - ";
480
552
  var CalendarFieldIcon = () => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_xui_icons_base.Calendar, { size: "100%" });
@@ -576,6 +648,10 @@ var DatePicker = (0, import_react.forwardRef)(
576
648
  const editable = !readOnly && !rest.disabled;
577
649
  const isDual = variant === "dual";
578
650
  const isRange = isDual || selectsRange;
651
+ const resolvedFormat = (0, import_react.useMemo)(
652
+ () => resolveDateFormat(dateFormat),
653
+ [dateFormat]
654
+ );
579
655
  const propGroups = (0, import_react.useCallback)(
580
656
  () => isRange ? [datePartsFromDate(startDate), datePartsFromDate(endDate)] : [datePartsFromDate(selectedDate)],
581
657
  [isRange, startDate, endDate, selectedDate]
@@ -623,7 +699,7 @@ var DatePicker = (0, import_react.forwardRef)(
623
699
  };
624
700
  const composeField = () => {
625
701
  const build = (group) => buildSegmentedValue(
626
- dateFormat,
702
+ resolvedFormat,
627
703
  groups[group] ?? { day: null, month: null, year: null },
628
704
  edit && edit.group === group ? { type: edit.type, buffer: edit.buffer } : void 0
629
705
  );
@@ -870,8 +946,8 @@ var DatePicker = (0, import_react.forwardRef)(
870
946
  const parseOne = (token) => {
871
947
  const trimmed = token.trim();
872
948
  if (!trimmed) return null;
873
- const parsed = (0, import_date_fns2.parse)(trimmed, dateFormat, /* @__PURE__ */ new Date());
874
- return (0, import_date_fns2.isValid)(parsed) ? parsed : null;
949
+ const parsed = (0, import_date_fns.parse)(trimmed, resolvedFormat, /* @__PURE__ */ new Date());
950
+ return (0, import_date_fns.isValid)(parsed) ? parsed : null;
875
951
  };
876
952
  const handlePaste = (e) => {
877
953
  onPasteProp?.(e);
@@ -887,7 +963,7 @@ var DatePicker = (0, import_react.forwardRef)(
887
963
  const end = parseOne(endText ?? "");
888
964
  if (!start) {
889
965
  setParseError(
890
- `Enter a valid date range (${dateFormat} - ${dateFormat})`
966
+ `Enter a valid date range (${resolvedFormat} - ${resolvedFormat})`
891
967
  );
892
968
  return;
893
969
  }
@@ -895,7 +971,7 @@ var DatePicker = (0, import_react.forwardRef)(
895
971
  } else {
896
972
  const date = parseOne(text);
897
973
  if (!date) {
898
- setParseError(`Enter a valid date (${dateFormat})`);
974
+ setParseError(`Enter a valid date (${resolvedFormat})`);
899
975
  return;
900
976
  }
901
977
  next = [datePartsFromDate(date)];
@@ -1091,7 +1167,7 @@ var DatePicker = (0, import_react.forwardRef)(
1091
1167
  ...rest,
1092
1168
  ref: inputRef,
1093
1169
  size,
1094
- placeholder: placeholder || (isRange ? "Enter a date range" : "Choose a date"),
1170
+ placeholder: placeholder || (isRange ? "Choose a date range" : "Choose a date"),
1095
1171
  value: inputValue,
1096
1172
  readOnly,
1097
1173
  onFocus: handleFieldFocus,
@@ -1139,18 +1215,22 @@ DatePicker.displayName = "DatePicker";
1139
1215
  CalendarChips,
1140
1216
  CalendarGrid,
1141
1217
  CalendarHeader,
1218
+ DEFAULT_DATE_FORMAT,
1142
1219
  DatePicker,
1143
1220
  DualCalendar,
1221
+ analyzeDateFormat,
1144
1222
  buildSegmentedValue,
1145
1223
  clampDatePart,
1146
1224
  datePartsFromDate,
1147
1225
  datePartsToDate,
1148
1226
  daysInMonth,
1227
+ describeDateFormatIssues,
1149
1228
  formatDate,
1150
1229
  getDateSegments,
1151
1230
  isDatePartsComplete,
1152
1231
  isSegmentComplete,
1153
1232
  parseDateFormat,
1233
+ resolveDateFormat,
1154
1234
  stepDatePart,
1155
1235
  stepDateSegment
1156
1236
  });