@xsolla/xui-date-picker 0.184.0 → 0.185.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 CHANGED
@@ -58,6 +58,7 @@ import {
58
58
  } from '@xsolla/xui-date-picker';
59
59
  import type {
60
60
  DatePickerProps,
61
+ DatePickerPlacement,
61
62
  DateRangeType,
62
63
  CalendarProps,
63
64
  DualCalendarProps,
@@ -99,7 +100,8 @@ const [date, setDate] = useState<Date | null>(null);
99
100
  | `size` | `'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl'` | `'md'` | Input size. |
100
101
  | `disabled` | `boolean` | `false` | Disable the input. |
101
102
  | `backgroundColor` | `string` | — | Custom background colour for the input. |
102
- | `dropdownPosition` | `'left' \| 'right'` | `'left'` | Align the calendar dropdown to the left or right edge of the input. |
103
+ | `dropdownPosition` | `'left' \| 'right'` | `'left'` | **Deprecated** — use the alignment suffix on `placement` (`dropdownPosition="right"` → `placement="bottom-end"`). Still honoured as a fallback when `placement` has no suffix. |
104
+ | `placement` | `DatePickerPlacement` | auto | Where the calendar opens, as `{side}[-alignment]` — same `{side}-{start\|center\|end}` convention as `ContextMenuPlacement`. A bare side aligns to `start`. When omitted, the calendar measures viewport space on open and auto-flips, preferring `bottom` → `top` → `left` → `right`, also flipping the cross-axis edge to stay on screen. When set, it always opens at that placement. Web only. |
103
105
  | `locale` | `CalendarLocaleType` | `'enUS'` | `date-fns` locale identifier. |
104
106
  | `firstDayOfWeek` | `number` | — | First day of the week (0 = Sunday … 6 = Saturday). |
105
107
  | `initialMonth` | `Date` | — | Month shown on first render. |
@@ -150,6 +152,10 @@ Re-exported from `@xsolla/xui-calendar`. Preset chips bar (e.g. "Last 7 days", "
150
152
 
151
153
  ```ts
152
154
  type DateRangeType = [Date | null, Date | null];
155
+
156
+ type DatePickerSide = 'top' | 'bottom' | 'left' | 'right';
157
+ type DatePickerAlignment = 'start' | 'center' | 'end';
158
+ type DatePickerPlacement = DatePickerSide | `${DatePickerSide}-${DatePickerAlignment}`;
153
159
  ```
154
160
 
155
161
  ## Examples
@@ -180,7 +186,7 @@ const [end, setEnd] = useState<Date | null>(null);
180
186
 
181
187
  <DatePicker
182
188
  variant="dual"
183
- dropdownPosition="right"
189
+ placement="bottom-end"
184
190
  startDate={start}
185
191
  endDate={end}
186
192
  onChange={(range) => {
@@ -191,6 +197,29 @@ const [end, setEnd] = useState<Date | null>(null);
191
197
  />;
192
198
  ```
193
199
 
200
+ ### Placement and autoflip
201
+
202
+ ```tsx
203
+ const [date, setDate] = useState<Date | null>(null);
204
+
205
+ // Default: the calendar auto-flips based on available viewport space.
206
+ <DatePicker selectedDate={date} onChange={(d) => setDate(d as Date | null)} />;
207
+
208
+ // Force a side with `placement`.
209
+ <DatePicker
210
+ placement="top"
211
+ selectedDate={date}
212
+ onChange={(d) => setDate(d as Date | null)}
213
+ />;
214
+
215
+ // Force a side and alignment (here: opens upward, right-aligned).
216
+ <DatePicker
217
+ placement="top-end"
218
+ selectedDate={date}
219
+ onChange={(d) => setDate(d as Date | null)}
220
+ />;
221
+ ```
222
+
194
223
  ### Preset chips
195
224
 
196
225
  ```tsx
@@ -1,10 +1,23 @@
1
1
  import { CalendarProps, CalendarLocaleType } from '@xsolla/xui-calendar';
2
2
  export { Calendar, CalendarChipOption, CalendarChips, CalendarChipsProps, CalendarGrid, CalendarGridProps, CalendarHeader, CalendarHeaderProps, CalendarLocaleType, CalendarProps, DualCalendar, DualCalendarProps } from '@xsolla/xui-calendar';
3
3
  import * as react from 'react';
4
+ import { ReactNode, FocusEvent, MouseEvent, KeyboardEvent, ClipboardEvent } from 'react';
4
5
  import { ThemeMode, ProductContext } from '@xsolla/xui-core';
5
6
 
6
7
  type DateRangeType = [Date | null, Date | null];
7
8
  type DatePickerDropdownPosition = "left" | "right";
9
+ /** Side the calendar opens toward, relative to the input. */
10
+ type DatePickerSide = "top" | "bottom" | "left" | "right";
11
+ /** Alignment along the chosen side. `start`/`end` map to the leading/trailing edge. */
12
+ type DatePickerAlignment = "start" | "center" | "end";
13
+ /**
14
+ * Where the calendar opens, following the Floating UI / `ContextMenuPlacement`
15
+ * convention: a {@link DatePickerSide} optionally suffixed with an alignment
16
+ * (`-start` | `-center` | `-end`). A bare side aligns to `start` (top/bottom:
17
+ * left edge; left/right: top edge). For `top`/`bottom` the suffix sets the
18
+ * horizontal edge; for `left`/`right` it sets the vertical edge.
19
+ */
20
+ type DatePickerPlacement = DatePickerSide | `${DatePickerSide}-${DatePickerAlignment}`;
8
21
  interface DatePickerProps extends Omit<CalendarProps, "onChange"> {
9
22
  /**
10
23
  * Calendar variant: "single" shows a single month, "dual" shows two months side-by-side.
@@ -19,6 +32,12 @@ interface DatePickerProps extends Omit<CalendarProps, "onChange"> {
19
32
  * Placeholder text for the input
20
33
  */
21
34
  placeholder?: string;
35
+ /**
36
+ * date-fns format string used to both render and parse the field's text value
37
+ * (e.g. `"dd/MM/yyyy"`). Applies to each date in a range. Defaults to
38
+ * `"MM/dd/yyyy"`.
39
+ */
40
+ dateFormat?: string;
22
41
  /**
23
42
  * Size of the input
24
43
  */
@@ -32,9 +51,64 @@ interface DatePickerProps extends Omit<CalendarProps, "onChange"> {
32
51
  */
33
52
  backgroundColor?: string;
34
53
  /**
35
- * Horizontal alignment of the calendar dropdown relative to the input.
54
+ * Trailing icon shown inside the field. Defaults to a calendar icon
55
+ * (`icon_calendar--line`). Pass `null` to hide it.
56
+ */
57
+ iconRight?: ReactNode;
58
+ /**
59
+ * Optional leading icon shown inside the field.
60
+ */
61
+ iconLeft?: ReactNode;
62
+ /**
63
+ * Highlights the field as invalid (error state).
64
+ */
65
+ error?: boolean;
66
+ /**
67
+ * Error message shown below the field. Also puts the field in the error state.
68
+ */
69
+ errorMessage?: string;
70
+ /**
71
+ * When a chip is selected, show its label in the field (e.g. "Last 30 days").
72
+ * Set to `false` to show the resolved date/range instead. Defaults to `true`.
73
+ */
74
+ showChipLabel?: boolean;
75
+ /** Renders the field read-only: it can open the calendar but not be edited. */
76
+ readOnly?: boolean;
77
+ /** Called on field focus (invoked before the picker's own focus handling). */
78
+ onFocus?: (e: FocusEvent<HTMLInputElement>) => void;
79
+ /** Called on field blur (invoked before the picker's own blur handling). */
80
+ onBlur?: (e: FocusEvent<HTMLInputElement>) => void;
81
+ /** Called on field click (invoked before the picker's own click handling). */
82
+ onClick?: (e: MouseEvent<HTMLInputElement>) => void;
83
+ /**
84
+ * Called on field keydown before the picker's own handling; call
85
+ * `e.preventDefault()` to suppress the built-in segment editing for that key.
86
+ */
87
+ onKeyDown?: (e: KeyboardEvent<HTMLInputElement>) => void;
88
+ /**
89
+ * Called on field paste before the picker's own handling; call
90
+ * `e.preventDefault()` to suppress the built-in parse.
91
+ */
92
+ onPaste?: (e: ClipboardEvent<HTMLInputElement>) => void;
93
+ /**
94
+ * Horizontal alignment of the calendar for `top`/`bottom` placements. Retained
95
+ * as a fallback: applied only when `placement` has no alignment suffix, so
96
+ * existing usage keeps working.
97
+ *
98
+ * @deprecated Use the `placement` alignment suffix instead (e.g. `bottom-end`).
36
99
  */
37
100
  dropdownPosition?: DatePickerDropdownPosition;
101
+ /**
102
+ * Where the calendar opens, e.g. `"bottom"`, `"bottom-start"`, `"top-end"`,
103
+ * `"right"`. Follows the same `{side}[-alignment]` convention as
104
+ * `ContextMenuPlacement`. A bare side aligns to `start`; add `-start`,
105
+ * `-center`, or `-end` to align along that side. When omitted, the calendar
106
+ * measures the available viewport space when it opens and automatically flips
107
+ * to the best direction (prefers `"bottom"`, then `"top"`, then `"left"`,
108
+ * then `"right"`), also flipping the horizontal edge so it stays on screen.
109
+ * When set, the calendar always opens at that placement. Web only.
110
+ */
111
+ placement?: DatePickerPlacement;
38
112
  /** Test ID for testing frameworks */
39
113
  testID?: string;
40
114
  /** Theme mode for the calendar overlay. Defaults to themeMode when not set. */
@@ -46,5 +120,75 @@ interface DatePickerProps extends Omit<CalendarProps, "onChange"> {
46
120
  declare const DatePicker: react.ForwardRefExoticComponent<DatePickerProps & react.RefAttributes<any>>;
47
121
 
48
122
  declare function formatDate(date: Date, formatStr: string, locale?: CalendarLocaleType): string;
123
+ type DateSegmentType = "day" | "month" | "year";
124
+ /** A date broken into editable parts; `null` means "not yet filled". */
125
+ interface DateParts {
126
+ day: number | null;
127
+ month: number | null;
128
+ year: number | null;
129
+ }
130
+ /** A rendered segment's character span within the field text. */
131
+ interface SegmentRange {
132
+ type: DateSegmentType;
133
+ start: number;
134
+ end: number;
135
+ filled: boolean;
136
+ }
137
+ type FormatToken = {
138
+ kind: "segment";
139
+ type: DateSegmentType;
140
+ } | {
141
+ kind: "literal";
142
+ text: string;
143
+ };
144
+ /** Split a date format into ordered segment tokens and literal separators. */
145
+ declare function parseDateFormat(format: string): FormatToken[];
146
+ declare function datePartsFromDate(date: Date | null | undefined): DateParts;
147
+ /** Assemble a `Date`, or null if any part is missing or the date is invalid. */
148
+ declare function datePartsToDate(parts: DateParts): Date | null;
149
+ declare function isDatePartsComplete(parts: DateParts): boolean;
150
+ /** Clamp a raw numeric segment value into its valid range. */
151
+ declare function clampDatePart(type: DateSegmentType, value: number, parts: DateParts): number;
152
+ /** Step a numeric segment value, wrapping within its range (day/month) or ±1 (year). */
153
+ declare function stepDatePart(type: DateSegmentType, current: number, delta: number, parts: DateParts): number;
154
+ /**
155
+ * Render `parts` into field text using `format`, returning each segment's char
156
+ * range. Filled parts are zero-padded; unfilled parts show a token placeholder
157
+ * (`dd`/`mm`/`yyyy`). `active`, when set, renders the raw in-progress buffer for
158
+ * the segment currently being typed.
159
+ */
160
+ declare function buildSegmentedValue(format: string, parts: DateParts, active?: {
161
+ type: DateSegmentType;
162
+ buffer: string;
163
+ }): {
164
+ text: string;
165
+ ranges: SegmentRange[];
166
+ };
167
+ /** Whether a first digit rules out a valid second digit (for early auto-commit). */
168
+ declare function isSegmentComplete(type: DateSegmentType, buffer: string): boolean;
169
+ interface DateSegment {
170
+ type: DateSegmentType;
171
+ /** Inclusive character index of the segment's first digit in `value`. */
172
+ start: number;
173
+ /** Exclusive character index one past the segment's last digit. */
174
+ end: number;
175
+ }
176
+ /** Number of days in the given month (0-indexed) of the given year. */
177
+ declare function daysInMonth(year: number, monthIndex: number): number;
178
+ /**
179
+ * Locate the day/month/year segments within a formatted date `value` by walking
180
+ * the `format` string in parallel: `d`/`M`/`y` token runs consume the matching
181
+ * digit run in `value`, other characters are treated as literal separators.
182
+ * Works for any token order (e.g. `MM/dd/yyyy`, `dd/MM/yyyy`, `yyyy-MM-dd`) and
183
+ * both fixed- and variable-width tokens. Returns segments in display order.
184
+ */
185
+ declare function getDateSegments(format: string, value: string): DateSegment[];
186
+ /**
187
+ * Step a single date segment by `delta`, isolated to that field (no carry):
188
+ * day wraps within the current month, month wraps within the year (day clamped
189
+ * to the new month's length), year increments freely. Mirrors the segment wrap
190
+ * behaviour of the InputTime segments.
191
+ */
192
+ declare function stepDateSegment(date: Date, type: DateSegmentType, delta: number): Date;
49
193
 
50
- export { DatePicker, type DatePickerDropdownPosition, type DatePickerProps, type DateRangeType, formatDate };
194
+ 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 };
package/native/index.d.ts CHANGED
@@ -1,10 +1,23 @@
1
1
  import { CalendarProps, CalendarLocaleType } from '@xsolla/xui-calendar';
2
2
  export { Calendar, CalendarChipOption, CalendarChips, CalendarChipsProps, CalendarGrid, CalendarGridProps, CalendarHeader, CalendarHeaderProps, CalendarLocaleType, CalendarProps, DualCalendar, DualCalendarProps } from '@xsolla/xui-calendar';
3
3
  import * as react from 'react';
4
+ import { ReactNode, FocusEvent, MouseEvent, KeyboardEvent, ClipboardEvent } from 'react';
4
5
  import { ThemeMode, ProductContext } from '@xsolla/xui-core';
5
6
 
6
7
  type DateRangeType = [Date | null, Date | null];
7
8
  type DatePickerDropdownPosition = "left" | "right";
9
+ /** Side the calendar opens toward, relative to the input. */
10
+ type DatePickerSide = "top" | "bottom" | "left" | "right";
11
+ /** Alignment along the chosen side. `start`/`end` map to the leading/trailing edge. */
12
+ type DatePickerAlignment = "start" | "center" | "end";
13
+ /**
14
+ * Where the calendar opens, following the Floating UI / `ContextMenuPlacement`
15
+ * convention: a {@link DatePickerSide} optionally suffixed with an alignment
16
+ * (`-start` | `-center` | `-end`). A bare side aligns to `start` (top/bottom:
17
+ * left edge; left/right: top edge). For `top`/`bottom` the suffix sets the
18
+ * horizontal edge; for `left`/`right` it sets the vertical edge.
19
+ */
20
+ type DatePickerPlacement = DatePickerSide | `${DatePickerSide}-${DatePickerAlignment}`;
8
21
  interface DatePickerProps extends Omit<CalendarProps, "onChange"> {
9
22
  /**
10
23
  * Calendar variant: "single" shows a single month, "dual" shows two months side-by-side.
@@ -19,6 +32,12 @@ interface DatePickerProps extends Omit<CalendarProps, "onChange"> {
19
32
  * Placeholder text for the input
20
33
  */
21
34
  placeholder?: string;
35
+ /**
36
+ * date-fns format string used to both render and parse the field's text value
37
+ * (e.g. `"dd/MM/yyyy"`). Applies to each date in a range. Defaults to
38
+ * `"MM/dd/yyyy"`.
39
+ */
40
+ dateFormat?: string;
22
41
  /**
23
42
  * Size of the input
24
43
  */
@@ -32,9 +51,64 @@ interface DatePickerProps extends Omit<CalendarProps, "onChange"> {
32
51
  */
33
52
  backgroundColor?: string;
34
53
  /**
35
- * Horizontal alignment of the calendar dropdown relative to the input.
54
+ * Trailing icon shown inside the field. Defaults to a calendar icon
55
+ * (`icon_calendar--line`). Pass `null` to hide it.
56
+ */
57
+ iconRight?: ReactNode;
58
+ /**
59
+ * Optional leading icon shown inside the field.
60
+ */
61
+ iconLeft?: ReactNode;
62
+ /**
63
+ * Highlights the field as invalid (error state).
64
+ */
65
+ error?: boolean;
66
+ /**
67
+ * Error message shown below the field. Also puts the field in the error state.
68
+ */
69
+ errorMessage?: string;
70
+ /**
71
+ * When a chip is selected, show its label in the field (e.g. "Last 30 days").
72
+ * Set to `false` to show the resolved date/range instead. Defaults to `true`.
73
+ */
74
+ showChipLabel?: boolean;
75
+ /** Renders the field read-only: it can open the calendar but not be edited. */
76
+ readOnly?: boolean;
77
+ /** Called on field focus (invoked before the picker's own focus handling). */
78
+ onFocus?: (e: FocusEvent<HTMLInputElement>) => void;
79
+ /** Called on field blur (invoked before the picker's own blur handling). */
80
+ onBlur?: (e: FocusEvent<HTMLInputElement>) => void;
81
+ /** Called on field click (invoked before the picker's own click handling). */
82
+ onClick?: (e: MouseEvent<HTMLInputElement>) => void;
83
+ /**
84
+ * Called on field keydown before the picker's own handling; call
85
+ * `e.preventDefault()` to suppress the built-in segment editing for that key.
86
+ */
87
+ onKeyDown?: (e: KeyboardEvent<HTMLInputElement>) => void;
88
+ /**
89
+ * Called on field paste before the picker's own handling; call
90
+ * `e.preventDefault()` to suppress the built-in parse.
91
+ */
92
+ onPaste?: (e: ClipboardEvent<HTMLInputElement>) => void;
93
+ /**
94
+ * Horizontal alignment of the calendar for `top`/`bottom` placements. Retained
95
+ * as a fallback: applied only when `placement` has no alignment suffix, so
96
+ * existing usage keeps working.
97
+ *
98
+ * @deprecated Use the `placement` alignment suffix instead (e.g. `bottom-end`).
36
99
  */
37
100
  dropdownPosition?: DatePickerDropdownPosition;
101
+ /**
102
+ * Where the calendar opens, e.g. `"bottom"`, `"bottom-start"`, `"top-end"`,
103
+ * `"right"`. Follows the same `{side}[-alignment]` convention as
104
+ * `ContextMenuPlacement`. A bare side aligns to `start`; add `-start`,
105
+ * `-center`, or `-end` to align along that side. When omitted, the calendar
106
+ * measures the available viewport space when it opens and automatically flips
107
+ * to the best direction (prefers `"bottom"`, then `"top"`, then `"left"`,
108
+ * then `"right"`), also flipping the horizontal edge so it stays on screen.
109
+ * When set, the calendar always opens at that placement. Web only.
110
+ */
111
+ placement?: DatePickerPlacement;
38
112
  /** Test ID for testing frameworks */
39
113
  testID?: string;
40
114
  /** Theme mode for the calendar overlay. Defaults to themeMode when not set. */
@@ -46,5 +120,75 @@ interface DatePickerProps extends Omit<CalendarProps, "onChange"> {
46
120
  declare const DatePicker: react.ForwardRefExoticComponent<DatePickerProps & react.RefAttributes<any>>;
47
121
 
48
122
  declare function formatDate(date: Date, formatStr: string, locale?: CalendarLocaleType): string;
123
+ type DateSegmentType = "day" | "month" | "year";
124
+ /** A date broken into editable parts; `null` means "not yet filled". */
125
+ interface DateParts {
126
+ day: number | null;
127
+ month: number | null;
128
+ year: number | null;
129
+ }
130
+ /** A rendered segment's character span within the field text. */
131
+ interface SegmentRange {
132
+ type: DateSegmentType;
133
+ start: number;
134
+ end: number;
135
+ filled: boolean;
136
+ }
137
+ type FormatToken = {
138
+ kind: "segment";
139
+ type: DateSegmentType;
140
+ } | {
141
+ kind: "literal";
142
+ text: string;
143
+ };
144
+ /** Split a date format into ordered segment tokens and literal separators. */
145
+ declare function parseDateFormat(format: string): FormatToken[];
146
+ declare function datePartsFromDate(date: Date | null | undefined): DateParts;
147
+ /** Assemble a `Date`, or null if any part is missing or the date is invalid. */
148
+ declare function datePartsToDate(parts: DateParts): Date | null;
149
+ declare function isDatePartsComplete(parts: DateParts): boolean;
150
+ /** Clamp a raw numeric segment value into its valid range. */
151
+ declare function clampDatePart(type: DateSegmentType, value: number, parts: DateParts): number;
152
+ /** Step a numeric segment value, wrapping within its range (day/month) or ±1 (year). */
153
+ declare function stepDatePart(type: DateSegmentType, current: number, delta: number, parts: DateParts): number;
154
+ /**
155
+ * Render `parts` into field text using `format`, returning each segment's char
156
+ * range. Filled parts are zero-padded; unfilled parts show a token placeholder
157
+ * (`dd`/`mm`/`yyyy`). `active`, when set, renders the raw in-progress buffer for
158
+ * the segment currently being typed.
159
+ */
160
+ declare function buildSegmentedValue(format: string, parts: DateParts, active?: {
161
+ type: DateSegmentType;
162
+ buffer: string;
163
+ }): {
164
+ text: string;
165
+ ranges: SegmentRange[];
166
+ };
167
+ /** Whether a first digit rules out a valid second digit (for early auto-commit). */
168
+ declare function isSegmentComplete(type: DateSegmentType, buffer: string): boolean;
169
+ interface DateSegment {
170
+ type: DateSegmentType;
171
+ /** Inclusive character index of the segment's first digit in `value`. */
172
+ start: number;
173
+ /** Exclusive character index one past the segment's last digit. */
174
+ end: number;
175
+ }
176
+ /** Number of days in the given month (0-indexed) of the given year. */
177
+ declare function daysInMonth(year: number, monthIndex: number): number;
178
+ /**
179
+ * Locate the day/month/year segments within a formatted date `value` by walking
180
+ * the `format` string in parallel: `d`/`M`/`y` token runs consume the matching
181
+ * digit run in `value`, other characters are treated as literal separators.
182
+ * Works for any token order (e.g. `MM/dd/yyyy`, `dd/MM/yyyy`, `yyyy-MM-dd`) and
183
+ * both fixed- and variable-width tokens. Returns segments in display order.
184
+ */
185
+ declare function getDateSegments(format: string, value: string): DateSegment[];
186
+ /**
187
+ * Step a single date segment by `delta`, isolated to that field (no carry):
188
+ * day wraps within the current month, month wraps within the year (day clamped
189
+ * to the new month's length), year increments freely. Mirrors the segment wrap
190
+ * behaviour of the InputTime segments.
191
+ */
192
+ declare function stepDateSegment(date: Date, type: DateSegmentType, delta: number): Date;
49
193
 
50
- export { DatePicker, type DatePickerDropdownPosition, type DatePickerProps, type DateRangeType, formatDate };
194
+ 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 };