@punaro/react-datepicker 0.0.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 ADDED
@@ -0,0 +1,190 @@
1
+ # @punaro/react-datepicker
2
+
3
+ A lightweight, accessible, and fully customizable React date picker component. Mobile-first, no calendar popup, and easy to style or theme.
4
+
5
+ ---
6
+
7
+ ## Features
8
+
9
+ - **Segmented input**: Day, month, year as separate fields (keyboard and mobile friendly)
10
+ - **No calendar popup**: Just type or pick from dropdowns
11
+ - **Dropdowns**: Optional, with custom icon support
12
+ - **Dark/light themes**: Built-in, or use your own
13
+ - **Outlined/flat/plain**: Choose your look
14
+ - **Full style control**: CSS file, slot-level class/style props, or zero-style mode
15
+ - **TypeScript**: Full types for all props and slots
16
+ - **Zero dependencies** (except `date-fns`)
17
+
18
+ ---
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ npm install @punaro/react-datepicker
24
+ ```
25
+
26
+ ## Usage
27
+
28
+ ```tsx
29
+ import { useState } from "react";
30
+ import { DatePicker } from "@punaro/react-datepicker";
31
+ import "@punaro/react-datepicker/styles.css";
32
+
33
+ function App() {
34
+ const [date, setDate] = useState<Date | undefined>();
35
+ return <DatePicker value={date} onChange={setDate} />;
36
+ }
37
+ ```
38
+
39
+ ---
40
+
41
+ ## Props
42
+
43
+ | Prop | Type | Default | Description |
44
+ | ------------- | ------------------------------------------------------- | -------------- | -------------------------------------------------------------------------- |
45
+ | value | `Date \| undefined` | — | The selected date (controlled) |
46
+ | onChange | `(date: Date \| undefined) => void` | — | Called when a valid date is entered/selected, or `undefined` if incomplete |
47
+ | dateFormat | `string` | `'dd/MM/yyyy'` | Format string using `dd`, `MM`, `yyyy` tokens (order/separator flexible) |
48
+ | disabled | `boolean` | `false` | Disables all inputs and dropdowns |
49
+ | showDropdowns | `boolean` | `false` | Show dropdown icon next to each segment (native select for mobile/desktop) |
50
+ | dropdownIcon | `ReactNode \| (segment: 'dd'\|'MM'\|'yyyy')=>ReactNode` | `'▾'` | Custom icon for dropdown trigger (single or per-segment) |
51
+ | maxYear | `number` | current year | Latest year in year dropdown |
52
+ | yearRange | `number` | `100` | How many years to show in year dropdown |
53
+ | theme | `'light' \| 'dark'` | `'light'` | Color theme (CSS variables, only if not `isPlainStyle`) |
54
+ | outlined | `boolean` | `false` | Draw a single border around the whole component |
55
+ | isPlainStyle | `boolean` | `false` | No built-in styles or classes; style from scratch |
56
+ | classNames | `Partial<Record<StyleSlot, string>>` | — | Add/override classes for any slot (see below) |
57
+ | styles | `Partial<Record<StyleSlot, CSSProperties>>` | — | Add/override inline styles for any slot |
58
+
59
+ ### StyleSlot values
60
+
61
+ - `root`, `segment`, `input`, `separator`, `trigger`, `dropdownIcon`, `select`
62
+
63
+ ---
64
+
65
+ ## Examples
66
+
67
+ ### Basic
68
+
69
+ ```tsx
70
+ <DatePicker value={date} onChange={setDate} />
71
+ ```
72
+
73
+ ### Custom format
74
+
75
+ ```tsx
76
+ <DatePicker value={date} onChange={setDate} dateFormat="MM-yyyy-dd" />
77
+ ```
78
+
79
+ ### With dropdowns
80
+
81
+ ```tsx
82
+ <DatePicker value={date} onChange={setDate} showDropdowns />
83
+ ```
84
+
85
+ ### Custom dropdown icon (emoji)
86
+
87
+ ```tsx
88
+ <DatePicker value={date} onChange={setDate} showDropdowns dropdownIcon="📅" />
89
+ ```
90
+
91
+ ### Custom dropdown icon (SVG)
92
+
93
+ ```tsx
94
+ <DatePicker
95
+ value={date}
96
+ onChange={setDate}
97
+ showDropdowns
98
+ dropdownIcon={
99
+ <svg width="10" height="10" viewBox="0 0 10 10">
100
+ <path
101
+ d="M1 3l4 4 4-4"
102
+ stroke="currentColor"
103
+ strokeWidth="1.5"
104
+ strokeLinecap="round"
105
+ strokeLinejoin="round"
106
+ />
107
+ </svg>
108
+ }
109
+ />
110
+ ```
111
+
112
+ ### Per-segment dropdown icon
113
+
114
+ ```tsx
115
+ <DatePicker
116
+ value={date}
117
+ onChange={setDate}
118
+ showDropdowns
119
+ dropdownIcon={(seg) => (seg === "dd" ? "📆" : seg === "MM" ? "🗓️" : "⏳")}
120
+ />
121
+ ```
122
+
123
+ ### Outlined (single-field look)
124
+
125
+ ```tsx
126
+ <DatePicker value={date} onChange={setDate} showDropdowns outlined />
127
+ ```
128
+
129
+ ### Dark theme
130
+
131
+ ```tsx
132
+ <DatePicker
133
+ value={date}
134
+ onChange={setDate}
135
+ showDropdowns
136
+ outlined
137
+ theme="dark"
138
+ />
139
+ ```
140
+
141
+ ### Plain (no styles)
142
+
143
+ ```tsx
144
+ <DatePicker value={date} onChange={setDate} showDropdowns isPlainStyle />
145
+ ```
146
+
147
+ ### Custom styles via classNames/styles
148
+
149
+ ```tsx
150
+ <DatePicker
151
+ value={date}
152
+ onChange={setDate}
153
+ showDropdowns
154
+ outlined
155
+ classNames={{
156
+ root: "custom-root",
157
+ input: "custom-input",
158
+ dropdownIcon: "custom-icon",
159
+ trigger: "custom-trigger",
160
+ segment: "custom-segment",
161
+ separator: "custom-sep",
162
+ select: "custom-select",
163
+ }}
164
+ styles={{
165
+ root: { background: "#f0f9ff", borderColor: "#38bdf8" },
166
+ input: { color: "#0ea5e9", fontWeight: 600 },
167
+ dropdownIcon: { color: "#0ea5e9", fontSize: 16 },
168
+ trigger: { background: "#bae6fd" },
169
+ segment: { marginRight: 2 },
170
+ separator: { color: "#38bdf8" },
171
+ select: { minWidth: 24 },
172
+ }}
173
+ />
174
+ ```
175
+
176
+ ---
177
+
178
+ ## Theming & Styling
179
+
180
+ - Import the CSS file for default styles:
181
+ ```js
182
+ import "@punaro/react-datepicker/styles.css";
183
+ ```
184
+ - Use the `theme` prop for dark/light, or override CSS variables on `.rdp-root` for custom themes.
185
+ - Use `classNames`/`styles` for slot-level overrides.
186
+ - Use `isPlainStyle` for zero-style mode (all classes/inline styles removed).
187
+
188
+ ## License
189
+
190
+ MIT
@@ -0,0 +1,37 @@
1
+ import { CSSProperties, ReactNode } from 'react';
2
+ import { SegmentKey } from './types';
3
+ export interface DatePickerProps {
4
+ value?: Date;
5
+ onChange?: (date: Date | undefined) => void;
6
+ /** Format using `dd`, `MM`, `yyyy` tokens. Defaults to `dd/MM/yyyy`. */
7
+ dateFormat?: string;
8
+ disabled?: boolean;
9
+ name?: string;
10
+ id?: string;
11
+ 'aria-label'?: string;
12
+ /** When true, render a native `<select>` dropdown next to each segment. */
13
+ showDropdowns?: boolean;
14
+ /** Latest selectable year in the year dropdown. Defaults to the current year. */
15
+ maxYear?: number;
16
+ /** How many years to include in the year dropdown (descending from `maxYear`). Defaults to 100. */
17
+ yearRange?: number;
18
+ /**
19
+ * Custom dropdown trigger icon. Either a single node used for every segment,
20
+ * or a render function that receives the segment key. Defaults to a chevron.
21
+ * Only used when `showDropdowns` is true.
22
+ */
23
+ dropdownIcon?: ReactNode | ((segment: SegmentKey) => ReactNode);
24
+ /** Color theme. Defaults to `'light'`. Ignored when `isPlainStyle` is true. */
25
+ theme?: 'light' | 'dark';
26
+ /** When true, draw a single border around the whole component. Defaults to false. */
27
+ outlined?: boolean;
28
+ /** When true, no styles or class names are applied. Use this if you want to style from scratch. */
29
+ isPlainStyle?: boolean;
30
+ /** Per-slot class names. Merged after the built-in `rdp-*` classes (or used alone in plain mode). */
31
+ classNames?: Partial<Record<StyleSlot, string>>;
32
+ /** Per-slot inline styles. Merged on top of the component's own inline styles. */
33
+ styles?: Partial<Record<StyleSlot, CSSProperties>>;
34
+ }
35
+ /** Style slots a consumer can target via `classNames` / `styles` props. */
36
+ export type StyleSlot = 'root' | 'segment' | 'input' | 'separator' | 'trigger' | 'dropdownIcon' | 'select';
37
+ export declare function DatePicker({ value, onChange, dateFormat, disabled, name, id, showDropdowns, maxYear, yearRange, dropdownIcon, theme, outlined, isPlainStyle, classNames, styles: slotStyles, ...rest }: DatePickerProps): import("react/jsx-runtime").JSX.Element;
package/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require(`react`),t=require(`react/jsx-runtime`);function n(e){let t=Object.prototype.toString.call(e);return e instanceof Date||typeof e==`object`&&t===`[object Date]`?new e.constructor(+e):typeof e==`number`||t===`[object Number]`||typeof e==`string`||t===`[object String]`?new Date(e):new Date(NaN)}function r(e,t){return e instanceof Date?new e.constructor(t):new Date(t)}function i(e){return e instanceof Date||typeof e==`object`&&Object.prototype.toString.call(e)===`[object Date]`}function a(e){if(!i(e)&&typeof e!=`number`)return!1;let t=n(e);return!isNaN(Number(t))}function o(e){let t=n(e),i=t.getFullYear(),a=t.getMonth(),o=r(e,0);return o.setFullYear(i,a+1,0),o.setHours(0,0,0,0),o.getDate()}function s(e){let t=/(dd|MM|yyyy)/g,n=[],r=0,i;for(;(i=t.exec(e))!==null;){i.index>r&&n.push({type:`sep`,value:e.slice(r,i.index)});let t=i[1];n.push({type:`field`,key:t,length:t===`yyyy`?4:2}),r=i.index+i[1].length}return r<e.length&&n.push({type:`sep`,value:e.slice(r)}),n}function c(e,t){return String(e).padStart(t,`0`)}var l={dd:`DD`,MM:`MM`,yyyy:`YYYY`},u={dd:`Day`,MM:`Month`,yyyy:`Year`},d={dd:``,MM:``,yyyy:``};function f(e){return!e||!a(e)?d:{dd:c(e.getDate(),2),MM:c(e.getMonth()+1,2),yyyy:c(e.getFullYear(),4)}}function p(e,t){let n=parseInt(e,10);if(!n||n<1||n>12)return 31;let r=t.length===4?parseInt(t,10):2e3;return o(new Date(r,n-1,1))}function m(e){if(e.dd.length!==2||e.MM.length!==2||e.yyyy.length!==4)return;let t=parseInt(e.dd,10),n=parseInt(e.MM,10),r=parseInt(e.yyyy,10),i=new Date(r,n-1,t);if(i.getFullYear()===r&&i.getMonth()===n-1&&i.getDate()===t)return i}function h({value:n,onChange:r,dateFormat:i=`dd/MM/yyyy`,disabled:o=!1,name:d,id:h,showDropdowns:g=!1,maxYear:_,yearRange:v=100,dropdownIcon:y,theme:b=`light`,outlined:x=!1,isPlainStyle:S=!1,classNames:C,styles:w,...T}){let E=_??new Date().getFullYear(),D=(0,e.useMemo)(()=>s(i),[i]),O=(0,e.useMemo)(()=>D.filter(e=>e.type===`field`).map(e=>e.key),[D]),[k,A]=(0,e.useState)(()=>f(n)),j=(0,e.useRef)({dd:null,MM:null,yyyy:null}),M=(0,e.useRef)(n&&a(n)?n.getTime():void 0),N=n&&a(n)?n.getTime():void 0;(0,e.useEffect)(()=>{N!==M.current&&(M.current=N,A(f(n)))},[N]);function P(e){let t=m(e);if(t){M.current=t.getTime(),r?.(t);return}M.current=void 0,r?.(void 0)}function F(e,t){let n=O[O.indexOf(e)+t];if(!n)return;let r=j.current[n];if(r)if(r.focus(),t===-1){let e=r.value.length;try{r.setSelectionRange(e,e)}catch{}}else try{r.setSelectionRange(0,r.value.length)}catch{}}function I(e,t,n){let r=t.replace(/\D/g,``).slice(0,n);A(t=>{let i={...t,[e]:r};if(r.length>0){let a=parseInt(r,10);if(r.length<n&&(e===`dd`&&parseInt(r[0],10)>3||e===`MM`&&parseInt(r[0],10)>1)||r.length===n&&(e===`dd`&&(a<1||a>p(i.MM,i.yyyy))||e===`MM`&&(a<1||a>12)||e===`yyyy`&&a<1))return t}return e!==`dd`&&i.dd.length===2&&parseInt(i.dd,10)>p(i.MM,i.yyyy)&&(i.dd=``),r.length===n&&queueMicrotask(()=>F(e,1)),P(i),i})}function L(e){if(e===`dd`){let e=p(k.MM,k.yyyy);return Array.from({length:e},(e,t)=>c(t+1,2))}return e===`MM`?Array.from({length:12},(e,t)=>c(t+1,2)):Array.from({length:v},(e,t)=>c(E-t,4))}function R(e,t,n){I(e,t,n)}function z(e,t){let n=t.currentTarget,r=n.selectionStart===0&&n.selectionEnd===0,i=n.selectionStart===n.value.length&&n.selectionEnd===n.value.length;if(t.key===`Backspace`&&n.value===``){t.preventDefault(),F(e,-1);return}if(t.key===`ArrowLeft`&&r){t.preventDefault(),F(e,-1);return}t.key===`ArrowRight`&&i&&(t.preventDefault(),F(e,1))}let B=S?{position:`relative`,display:`inline-flex`,alignItems:`center`,justifyContent:`center`,width:`1em`,height:`1em`,cursor:o?`not-allowed`:`pointer`,userSelect:`none`,lineHeight:1}:{position:`relative`},V={position:`absolute`,inset:0,width:`100%`,height:`100%`,opacity:0,cursor:o?`not-allowed`:`pointer`,appearance:`none`,WebkitAppearance:`none`,MozAppearance:`none`,border:0,padding:0,margin:0,background:`transparent`,color:`transparent`,fontSize:`inherit`},H=S?{display:`inline-flex`,alignItems:`center`}:void 0,U=(e,t)=>{let n=C?.[t];return e?n?`${e} ${n}`:e:n},W=(e,t)=>{let n=w?.[t];return e?n?{...e,...n}:e:n};return(0,t.jsx)(`span`,{role:`group`,"aria-label":T[`aria-label`]??`Date`,id:h,className:U(S?void 0:`rdp-root`,`root`),style:W(void 0,`root`),"data-theme":S?void 0:b,"data-outlined":S?void 0:x?`true`:`false`,"data-disabled":S?void 0:o?`true`:`false`,children:D.map((e,n)=>e.type===`sep`?(0,t.jsx)(`span`,{"aria-hidden":`true`,className:U(S?void 0:`rdp-sep`,`separator`),style:W(void 0,`separator`),children:e.value},`s${n}`):(0,t.jsxs)(`span`,{className:U(S?void 0:`rdp-segment`,`segment`),style:W(H,`segment`),children:[(0,t.jsx)(`input`,{ref:t=>{j.current[e.key]=t},type:`text`,inputMode:`numeric`,pattern:`[0-9]*`,autoComplete:`off`,disabled:o,name:d?`${d}-${e.key}`:void 0,maxLength:e.length,size:e.length,placeholder:l[e.key],"aria-label":u[e.key],className:U(S?void 0:`rdp-input`,`input`),style:W(void 0,`input`),value:k[e.key],onChange:t=>I(e.key,t.target.value,e.length),onKeyDown:t=>z(e.key,t),onFocus:e=>e.currentTarget.select()}),g&&(0,t.jsxs)(`span`,{className:U(S?void 0:`rdp-trigger`,`trigger`),style:W(B,`trigger`),children:[(0,t.jsx)(`span`,{"aria-hidden":`true`,className:U(void 0,`dropdownIcon`),style:W(void 0,`dropdownIcon`),children:typeof y==`function`?y(e.key):y??`▾`}),(0,t.jsxs)(`select`,{"aria-label":`Pick ${u[e.key]}`,disabled:o,className:U(S?void 0:`rdp-select`,`select`),value:k[e.key],onChange:t=>R(e.key,t.target.value,e.length),style:W(V,`select`),children:[(0,t.jsx)(`option`,{value:``,disabled:!0,hidden:!0,children:u[e.key]}),L(e.key).map(e=>(0,t.jsx)(`option`,{value:e,children:e},e))]})]})]},e.key))})}exports.DatePicker=h;
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["toDate","toDate","toDate"],"sources":["../node_modules/date-fns/toDate.mjs","../node_modules/date-fns/constructFrom.mjs","../node_modules/date-fns/isDate.mjs","../node_modules/date-fns/isValid.mjs","../node_modules/date-fns/getDaysInMonth.mjs","../src/utils/format.ts","../src/utils/string.ts","../src/utils/constants.ts","../src/utils/time.ts","../src/DatePicker.tsx"],"sourcesContent":["/**\n * @name toDate\n * @category Common Helpers\n * @summary Convert the given argument to an instance of Date.\n *\n * @description\n * Convert the given argument to an instance of Date.\n *\n * If the argument is an instance of Date, the function returns its clone.\n *\n * If the argument is a number, it is treated as a timestamp.\n *\n * If the argument is none of the above, the function returns Invalid Date.\n *\n * **Note**: *all* Date arguments passed to any *date-fns* function is processed by `toDate`.\n *\n * @typeParam DateType - The `Date` type, the function operates on. Gets inferred from passed arguments. Allows to use extensions like [`UTCDate`](https://github.com/date-fns/utc).\n *\n * @param argument - The value to convert\n *\n * @returns The parsed date in the local time zone\n *\n * @example\n * // Clone the date:\n * const result = toDate(new Date(2014, 1, 11, 11, 30, 30))\n * //=> Tue Feb 11 2014 11:30:30\n *\n * @example\n * // Convert the timestamp to date:\n * const result = toDate(1392098430000)\n * //=> Tue Feb 11 2014 11:30:30\n */\nexport function toDate(argument) {\n const argStr = Object.prototype.toString.call(argument);\n\n // Clone the date\n if (\n argument instanceof Date ||\n (typeof argument === \"object\" && argStr === \"[object Date]\")\n ) {\n // Prevent the date to lose the milliseconds when passed to new Date() in IE10\n return new argument.constructor(+argument);\n } else if (\n typeof argument === \"number\" ||\n argStr === \"[object Number]\" ||\n typeof argument === \"string\" ||\n argStr === \"[object String]\"\n ) {\n // TODO: Can we get rid of as?\n return new Date(argument);\n } else {\n // TODO: Can we get rid of as?\n return new Date(NaN);\n }\n}\n\n// Fallback for modularized imports:\nexport default toDate;\n","/**\n * @name constructFrom\n * @category Generic Helpers\n * @summary Constructs a date using the reference date and the value\n *\n * @description\n * The function constructs a new date using the constructor from the reference\n * date and the given value. It helps to build generic functions that accept\n * date extensions.\n *\n * It defaults to `Date` if the passed reference date is a number or a string.\n *\n * @typeParam DateType - The `Date` type, the function operates on. Gets inferred from passed arguments. Allows to use extensions like [`UTCDate`](https://github.com/date-fns/utc).\n *\n * @param date - The reference date to take constructor from\n * @param value - The value to create the date\n *\n * @returns Date initialized using the given date and value\n *\n * @example\n * import { constructFrom } from 'date-fns'\n *\n * // A function that clones a date preserving the original type\n * function cloneDate<DateType extends Date(date: DateType): DateType {\n * return constructFrom(\n * date, // Use contrustor from the given date\n * date.getTime() // Use the date value to create a new date\n * )\n * }\n */\nexport function constructFrom(date, value) {\n if (date instanceof Date) {\n return new date.constructor(value);\n } else {\n return new Date(value);\n }\n}\n\n// Fallback for modularized imports:\nexport default constructFrom;\n","/**\n * @name isDate\n * @category Common Helpers\n * @summary Is the given value a date?\n *\n * @description\n * Returns true if the given value is an instance of Date. The function works for dates transferred across iframes.\n *\n * @param value - The value to check\n *\n * @returns True if the given value is a date\n *\n * @example\n * // For a valid date:\n * const result = isDate(new Date())\n * //=> true\n *\n * @example\n * // For an invalid date:\n * const result = isDate(new Date(NaN))\n * //=> true\n *\n * @example\n * // For some value:\n * const result = isDate('2014-02-31')\n * //=> false\n *\n * @example\n * // For an object:\n * const result = isDate({})\n * //=> false\n */\nexport function isDate(value) {\n return (\n value instanceof Date ||\n (typeof value === \"object\" &&\n Object.prototype.toString.call(value) === \"[object Date]\")\n );\n}\n\n// Fallback for modularized imports:\nexport default isDate;\n","import { isDate } from \"./isDate.mjs\";\nimport { toDate } from \"./toDate.mjs\";\n\n/**\n * @name isValid\n * @category Common Helpers\n * @summary Is the given date valid?\n *\n * @description\n * Returns false if argument is Invalid Date and true otherwise.\n * Argument is converted to Date using `toDate`. See [toDate](https://date-fns.org/docs/toDate)\n * Invalid Date is a Date, whose time value is NaN.\n *\n * Time value of Date: http://es5.github.io/#x15.9.1.1\n *\n * @typeParam DateType - The `Date` type, the function operates on. Gets inferred from passed arguments. Allows to use extensions like [`UTCDate`](https://github.com/date-fns/utc).\n *\n * @param date - The date to check\n *\n * @returns The date is valid\n *\n * @example\n * // For the valid date:\n * const result = isValid(new Date(2014, 1, 31))\n * //=> true\n *\n * @example\n * // For the value, convertable into a date:\n * const result = isValid(1393804800000)\n * //=> true\n *\n * @example\n * // For the invalid date:\n * const result = isValid(new Date(''))\n * //=> false\n */\nexport function isValid(date) {\n if (!isDate(date) && typeof date !== \"number\") {\n return false;\n }\n const _date = toDate(date);\n return !isNaN(Number(_date));\n}\n\n// Fallback for modularized imports:\nexport default isValid;\n","import { toDate } from \"./toDate.mjs\";\nimport { constructFrom } from \"./constructFrom.mjs\";\n\n/**\n * @name getDaysInMonth\n * @category Month Helpers\n * @summary Get the number of days in a month of the given date.\n *\n * @description\n * Get the number of days in a month of the given date.\n *\n * @typeParam DateType - The `Date` type, the function operates on. Gets inferred from passed arguments. Allows to use extensions like [`UTCDate`](https://github.com/date-fns/utc).\n *\n * @param date - The given date\n *\n * @returns The number of days in a month\n *\n * @example\n * // How many days are in February 2000?\n * const result = getDaysInMonth(new Date(2000, 1))\n * //=> 29\n */\nexport function getDaysInMonth(date) {\n const _date = toDate(date);\n const year = _date.getFullYear();\n const monthIndex = _date.getMonth();\n const lastDayOfMonth = constructFrom(date, 0);\n lastDayOfMonth.setFullYear(year, monthIndex + 1, 0);\n lastDayOfMonth.setHours(0, 0, 0, 0);\n return lastDayOfMonth.getDate();\n}\n\n// Fallback for modularized imports:\nexport default getDaysInMonth;\n","import type { SegmentKey, Token } from '../types'\n\n/**\n * Parse a date format string (e.g. `dd/MM/yyyy`) into an ordered list of\n * field and separator tokens. Recognized field tokens: `dd`, `MM`, `yyyy`.\n */\nexport function parseFormat(format: string): Token[] {\n const re = /(dd|MM|yyyy)/g\n const tokens: Token[] = []\n let last = 0\n let m: RegExpExecArray | null\n while ((m = re.exec(format)) !== null) {\n if (m.index > last) {\n tokens.push({ type: 'sep', value: format.slice(last, m.index) })\n }\n const key = m[1] as SegmentKey\n tokens.push({ type: 'field', key, length: key === 'yyyy' ? 4 : 2 })\n last = m.index + m[1].length\n }\n if (last < format.length) {\n tokens.push({ type: 'sep', value: format.slice(last) })\n }\n return tokens\n}\n","/** Left-pad a number to the given length with leading zeroes. */\nexport function pad(n: number, len: number): string {\n return String(n).padStart(len, '0')\n}\n","import type { SegmentKey, Values } from '../types'\n\nexport const PLACEHOLDER: Record<SegmentKey, string> = {\n dd: 'DD',\n MM: 'MM',\n yyyy: 'YYYY',\n}\n\nexport const LABEL: Record<SegmentKey, string> = {\n dd: 'Day',\n MM: 'Month',\n yyyy: 'Year',\n}\n\nexport const EMPTY: Values = { dd: '', MM: '', yyyy: '' }\n","import { getDaysInMonth, isValid } from 'date-fns'\nimport { pad } from './string'\nimport { EMPTY } from './constants'\nimport type { Values } from '../types'\n\n/** Build segment values from a `Date`. Returns empty values for invalid input. */\nexport function fromDate(date: Date | undefined): Values {\n if (!date || !isValid(date)) return EMPTY\n return {\n dd: pad(date.getDate(), 2),\n MM: pad(date.getMonth() + 1, 2),\n yyyy: pad(date.getFullYear(), 4),\n }\n}\n\n/**\n * Maximum allowed day for a given month/year segment context.\n * Falls back to a leap-safe year (2000) when the year is unknown so that\n * Feb 29 is initially permitted and re-validated once the year is provided.\n */\nexport function maxDay(monthStr: string, yearStr: string): number {\n const m = parseInt(monthStr, 10)\n if (!m || m < 1 || m > 12) return 31\n const y = yearStr.length === 4 ? parseInt(yearStr, 10) : 2000\n return getDaysInMonth(new Date(y, m - 1, 1))\n}\n\n/**\n * Build a `Date` from segment values if and only if they form a valid\n * calendar date. Returns `undefined` for partial or invalid values\n * (e.g. 31/02/2024).\n */\nexport function toDate(values: Values): Date | undefined {\n if (values.dd.length !== 2 || values.MM.length !== 2 || values.yyyy.length !== 4) {\n return undefined\n }\n const d = parseInt(values.dd, 10)\n const m = parseInt(values.MM, 10)\n const y = parseInt(values.yyyy, 10)\n const date = new Date(y, m - 1, d)\n if (\n date.getFullYear() === y &&\n date.getMonth() === m - 1 &&\n date.getDate() === d\n ) {\n return date\n }\n return undefined\n}\n","import { isValid } from 'date-fns'\nimport {\n type ChangeEvent,\n type CSSProperties,\n type KeyboardEvent,\n type ReactNode,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport { parseFormat } from './utils/format'\nimport { pad } from './utils/string'\nimport { fromDate, maxDay, toDate } from './utils/time'\nimport { LABEL, PLACEHOLDER } from './utils/constants'\nimport type { SegmentKey, Token, Values } from './types'\n\nexport interface DatePickerProps {\n value?: Date\n onChange?: (date: Date | undefined) => void\n /** Format using `dd`, `MM`, `yyyy` tokens. Defaults to `dd/MM/yyyy`. */\n dateFormat?: string\n disabled?: boolean\n name?: string\n id?: string\n 'aria-label'?: string\n /** When true, render a native `<select>` dropdown next to each segment. */\n showDropdowns?: boolean\n /** Latest selectable year in the year dropdown. Defaults to the current year. */\n maxYear?: number\n /** How many years to include in the year dropdown (descending from `maxYear`). Defaults to 100. */\n yearRange?: number\n /**\n * Custom dropdown trigger icon. Either a single node used for every segment,\n * or a render function that receives the segment key. Defaults to a chevron.\n * Only used when `showDropdowns` is true.\n */\n dropdownIcon?: ReactNode | ((segment: SegmentKey) => ReactNode)\n /** Color theme. Defaults to `'light'`. Ignored when `isPlainStyle` is true. */\n theme?: 'light' | 'dark'\n /** When true, draw a single border around the whole component. Defaults to false. */\n outlined?: boolean\n /** When true, no styles or class names are applied. Use this if you want to style from scratch. */\n isPlainStyle?: boolean\n /** Per-slot class names. Merged after the built-in `rdp-*` classes (or used alone in plain mode). */\n classNames?: Partial<Record<StyleSlot, string>>\n /** Per-slot inline styles. Merged on top of the component's own inline styles. */\n styles?: Partial<Record<StyleSlot, CSSProperties>>\n}\n\n/** Style slots a consumer can target via `classNames` / `styles` props. */\nexport type StyleSlot =\n | 'root'\n | 'segment'\n | 'input'\n | 'separator'\n | 'trigger'\n | 'dropdownIcon'\n | 'select'\n\nexport function DatePicker({\n value,\n onChange,\n dateFormat = 'dd/MM/yyyy',\n disabled = false,\n name,\n id,\n showDropdowns = false,\n maxYear,\n yearRange = 100,\n dropdownIcon,\n theme = 'light',\n outlined = false,\n isPlainStyle = false,\n classNames,\n styles: slotStyles,\n ...rest\n}: DatePickerProps) {\n const resolvedMaxYear = maxYear ?? new Date().getFullYear()\n const tokens = useMemo(() => parseFormat(dateFormat), [dateFormat])\n const fieldKeys = useMemo(\n () =>\n tokens\n .filter((t): t is Extract<Token, { type: 'field' }> => t.type === 'field')\n .map((t) => t.key),\n [tokens],\n )\n\n const [values, setValues] = useState<Values>(() => fromDate(value))\n const refs = useRef<Record<SegmentKey, HTMLInputElement | null>>({\n dd: null,\n MM: null,\n yyyy: null,\n })\n\n // Tracks the last value we emitted to the parent. Used to ignore the\n // controlled-parent's echo (which would otherwise overwrite an in-progress\n // partial edit, e.g. clearing one digit of the year).\n const lastEmittedTime = useRef<number | undefined>(\n value && isValid(value) ? value.getTime() : undefined,\n )\n\n // Sync from external value changes only when the incoming value is\n // genuinely different from what this component last emitted.\n const valueTime = value && isValid(value) ? value.getTime() : undefined\n useEffect(() => {\n if (valueTime === lastEmittedTime.current) return\n lastEmittedTime.current = valueTime\n setValues(fromDate(value))\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [valueTime])\n\n function emit(next: Values) {\n const date = toDate(next)\n if (date) {\n lastEmittedTime.current = date.getTime()\n onChange?.(date)\n return\n }\n lastEmittedTime.current = undefined\n onChange?.(undefined)\n }\n\n function focusSibling(current: SegmentKey, dir: 1 | -1) {\n const idx = fieldKeys.indexOf(current)\n const target = fieldKeys[idx + dir]\n if (!target) return\n const el = refs.current[target]\n if (!el) return\n el.focus()\n if (dir === -1) {\n const len = el.value.length\n try {\n el.setSelectionRange(len, len)\n } catch {\n /* some input types don't support selection */\n }\n } else {\n try {\n el.setSelectionRange(0, el.value.length)\n } catch {\n /* noop */\n }\n }\n }\n\n function handleChange(key: SegmentKey, raw: string, maxLen: number) {\n // Strip non-digits and clamp length\n const digits = raw.replace(/\\D/g, '').slice(0, maxLen)\n\n setValues((prev) => {\n const next: Values = { ...prev, [key]: digits }\n\n if (digits.length > 0) {\n const num = parseInt(digits, 10)\n\n // Reject impossible first digits (so user can't even type 4-9 as first dd digit, etc.)\n if (digits.length < maxLen) {\n if (key === 'dd' && parseInt(digits[0]!, 10) > 3) return prev\n if (key === 'MM' && parseInt(digits[0]!, 10) > 1) return prev\n }\n\n // Reject full segments that exceed allowed range\n if (digits.length === maxLen) {\n if (key === 'dd' && (num < 1 || num > maxDay(next.MM, next.yyyy))) return prev\n if (key === 'MM' && (num < 1 || num > 12)) return prev\n if (key === 'yyyy' && num < 1) return prev\n }\n }\n\n // If month or year changed, ensure existing day is still valid; clear it otherwise.\n if (key !== 'dd' && next.dd.length === 2) {\n const dnum = parseInt(next.dd, 10)\n if (dnum > maxDay(next.MM, next.yyyy)) next.dd = ''\n }\n\n // Auto-advance once segment is full\n if (digits.length === maxLen) {\n queueMicrotask(() => focusSibling(key, 1))\n }\n\n emit(next)\n return next\n })\n }\n\n function getOptions(key: SegmentKey): string[] {\n if (key === 'dd') {\n const max = maxDay(values.MM, values.yyyy)\n return Array.from({ length: max }, (_, i) => pad(i + 1, 2))\n }\n if (key === 'MM') {\n return Array.from({ length: 12 }, (_, i) => pad(i + 1, 2))\n }\n // yyyy: descending from resolvedMaxYear\n return Array.from({ length: yearRange }, (_, i) => pad(resolvedMaxYear - i, 4))\n }\n\n function handleSelect(key: SegmentKey, raw: string, maxLen: 2 | 4) {\n // Route through handleChange so all validation + day re-check + emit happens.\n handleChange(key, raw, maxLen)\n }\n\n function handleKeyDown(key: SegmentKey, e: KeyboardEvent<HTMLInputElement>) {\n const target = e.currentTarget\n const atStart = target.selectionStart === 0 && target.selectionEnd === 0\n const atEnd =\n target.selectionStart === target.value.length &&\n target.selectionEnd === target.value.length\n\n if (e.key === 'Backspace' && target.value === '') {\n e.preventDefault()\n focusSibling(key, -1)\n return\n }\n if (e.key === 'ArrowLeft' && atStart) {\n e.preventDefault()\n focusSibling(key, -1)\n return\n }\n if (e.key === 'ArrowRight' && atEnd) {\n e.preventDefault()\n focusSibling(key, 1)\n }\n }\n\n // Inline styles only used in plain mode. The CSS file (when not plain)\n // handles layout via .rdp-* classes; the absolute positioning of the\n // invisible <select> over the icon is functional, not cosmetic, so it\n // must always be applied.\n const triggerStyle = isPlainStyle\n ? {\n position: 'relative' as const,\n display: 'inline-flex' as const,\n alignItems: 'center' as const,\n justifyContent: 'center' as const,\n width: '1em',\n height: '1em',\n cursor: disabled ? ('not-allowed' as const) : ('pointer' as const),\n userSelect: 'none' as const,\n lineHeight: 1,\n }\n : { position: 'relative' as const }\n\n const selectStyle = {\n position: 'absolute' as const,\n inset: 0,\n width: '100%',\n height: '100%',\n opacity: 0,\n cursor: disabled ? ('not-allowed' as const) : ('pointer' as const),\n appearance: 'none' as const,\n WebkitAppearance: 'none' as const,\n MozAppearance: 'none' as const,\n border: 0,\n padding: 0,\n margin: 0,\n background: 'transparent',\n color: 'transparent',\n fontSize: 'inherit',\n }\n\n const segmentStyle = isPlainStyle\n ? { display: 'inline-flex' as const, alignItems: 'center' as const }\n : undefined\n\n // Helper: combine the built-in class for a slot with any consumer-provided class.\n const cn = (builtin: string | undefined, slot: StyleSlot) => {\n const extra = classNames?.[slot]\n if (!builtin) return extra\n return extra ? `${builtin} ${extra}` : builtin\n }\n\n // Helper: merge built-in inline style with consumer slot style (consumer wins).\n const mergeStyle = (\n base: CSSProperties | undefined,\n slot: StyleSlot,\n ): CSSProperties | undefined => {\n const extra = slotStyles?.[slot]\n if (!base) return extra\n if (!extra) return base\n return { ...base, ...extra }\n }\n\n return (\n <span\n role=\"group\"\n aria-label={rest['aria-label'] ?? 'Date'}\n id={id}\n className={cn(isPlainStyle ? undefined : 'rdp-root', 'root')}\n style={mergeStyle(undefined, 'root')}\n data-theme={isPlainStyle ? undefined : theme}\n data-outlined={isPlainStyle ? undefined : outlined ? 'true' : 'false'}\n data-disabled={isPlainStyle ? undefined : disabled ? 'true' : 'false'}\n >\n {tokens.map((t, i) => {\n if (t.type === 'sep') {\n return (\n <span\n key={`s${i}`}\n aria-hidden=\"true\"\n className={cn(isPlainStyle ? undefined : 'rdp-sep', 'separator')}\n style={mergeStyle(undefined, 'separator')}\n >\n {t.value}\n </span>\n )\n }\n return (\n <span\n key={t.key}\n className={cn(isPlainStyle ? undefined : 'rdp-segment', 'segment')}\n style={mergeStyle(segmentStyle, 'segment')}\n >\n <input\n ref={(el) => {\n refs.current[t.key] = el\n }}\n type=\"text\"\n inputMode=\"numeric\"\n pattern=\"[0-9]*\"\n autoComplete=\"off\"\n disabled={disabled}\n name={name ? `${name}-${t.key}` : undefined}\n maxLength={t.length}\n size={t.length}\n placeholder={PLACEHOLDER[t.key]}\n aria-label={LABEL[t.key]}\n className={cn(isPlainStyle ? undefined : 'rdp-input', 'input')}\n style={mergeStyle(undefined, 'input')}\n value={values[t.key]}\n onChange={(e: ChangeEvent<HTMLInputElement>) =>\n handleChange(t.key, e.target.value, t.length)\n }\n onKeyDown={(e) => handleKeyDown(t.key, e)}\n onFocus={(e) => e.currentTarget.select()}\n />\n {showDropdowns && (\n <span\n className={cn(isPlainStyle ? undefined : 'rdp-trigger', 'trigger')}\n style={mergeStyle(triggerStyle, 'trigger')}\n >\n <span\n aria-hidden=\"true\"\n className={cn(undefined, 'dropdownIcon')}\n style={mergeStyle(undefined, 'dropdownIcon')}\n >\n {typeof dropdownIcon === 'function'\n ? dropdownIcon(t.key)\n : (dropdownIcon ?? '▾')}\n </span>\n <select\n aria-label={`Pick ${LABEL[t.key]}`}\n disabled={disabled}\n className={cn(isPlainStyle ? undefined : 'rdp-select', 'select')}\n value={values[t.key]}\n onChange={(e) => handleSelect(t.key, e.target.value, t.length)}\n style={mergeStyle(selectStyle, 'select')}\n >\n <option value=\"\" disabled hidden>\n {LABEL[t.key]}\n </option>\n {getOptions(t.key).map((opt) => (\n <option key={opt} value={opt}>\n {opt}\n </option>\n ))}\n </select>\n </span>\n )}\n </span>\n )\n })}\n </span>\n )\n}\n"],"x_google_ignoreList":[0,1,2,3,4],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export { DatePicker } from './DatePicker';
2
+ export type { DatePickerProps } from './DatePicker';
package/dist/index.js ADDED
@@ -0,0 +1,268 @@
1
+ import { useEffect as e, useMemo as t, useRef as n, useState as r } from "react";
2
+ import { jsx as i, jsxs as a } from "react/jsx-runtime";
3
+ //#region node_modules/date-fns/toDate.mjs
4
+ function o(e) {
5
+ let t = Object.prototype.toString.call(e);
6
+ return e instanceof Date || typeof e == "object" && t === "[object Date]" ? new e.constructor(+e) : typeof e == "number" || t === "[object Number]" || typeof e == "string" || t === "[object String]" ? new Date(e) : /* @__PURE__ */ new Date(NaN);
7
+ }
8
+ //#endregion
9
+ //#region node_modules/date-fns/constructFrom.mjs
10
+ function s(e, t) {
11
+ return e instanceof Date ? new e.constructor(t) : new Date(t);
12
+ }
13
+ //#endregion
14
+ //#region node_modules/date-fns/isDate.mjs
15
+ function c(e) {
16
+ return e instanceof Date || typeof e == "object" && Object.prototype.toString.call(e) === "[object Date]";
17
+ }
18
+ //#endregion
19
+ //#region node_modules/date-fns/isValid.mjs
20
+ function l(e) {
21
+ if (!c(e) && typeof e != "number") return !1;
22
+ let t = o(e);
23
+ return !isNaN(Number(t));
24
+ }
25
+ //#endregion
26
+ //#region node_modules/date-fns/getDaysInMonth.mjs
27
+ function u(e) {
28
+ let t = o(e), n = t.getFullYear(), r = t.getMonth(), i = s(e, 0);
29
+ return i.setFullYear(n, r + 1, 0), i.setHours(0, 0, 0, 0), i.getDate();
30
+ }
31
+ //#endregion
32
+ //#region src/utils/format.ts
33
+ function d(e) {
34
+ let t = /(dd|MM|yyyy)/g, n = [], r = 0, i;
35
+ for (; (i = t.exec(e)) !== null;) {
36
+ i.index > r && n.push({
37
+ type: "sep",
38
+ value: e.slice(r, i.index)
39
+ });
40
+ let t = i[1];
41
+ n.push({
42
+ type: "field",
43
+ key: t,
44
+ length: t === "yyyy" ? 4 : 2
45
+ }), r = i.index + i[1].length;
46
+ }
47
+ return r < e.length && n.push({
48
+ type: "sep",
49
+ value: e.slice(r)
50
+ }), n;
51
+ }
52
+ //#endregion
53
+ //#region src/utils/string.ts
54
+ function f(e, t) {
55
+ return String(e).padStart(t, "0");
56
+ }
57
+ //#endregion
58
+ //#region src/utils/constants.ts
59
+ var p = {
60
+ dd: "DD",
61
+ MM: "MM",
62
+ yyyy: "YYYY"
63
+ }, m = {
64
+ dd: "Day",
65
+ MM: "Month",
66
+ yyyy: "Year"
67
+ }, h = {
68
+ dd: "",
69
+ MM: "",
70
+ yyyy: ""
71
+ };
72
+ //#endregion
73
+ //#region src/utils/time.ts
74
+ function g(e) {
75
+ return !e || !l(e) ? h : {
76
+ dd: f(e.getDate(), 2),
77
+ MM: f(e.getMonth() + 1, 2),
78
+ yyyy: f(e.getFullYear(), 4)
79
+ };
80
+ }
81
+ function _(e, t) {
82
+ let n = parseInt(e, 10);
83
+ if (!n || n < 1 || n > 12) return 31;
84
+ let r = t.length === 4 ? parseInt(t, 10) : 2e3;
85
+ return u(new Date(r, n - 1, 1));
86
+ }
87
+ function v(e) {
88
+ if (e.dd.length !== 2 || e.MM.length !== 2 || e.yyyy.length !== 4) return;
89
+ let t = parseInt(e.dd, 10), n = parseInt(e.MM, 10), r = parseInt(e.yyyy, 10), i = new Date(r, n - 1, t);
90
+ if (i.getFullYear() === r && i.getMonth() === n - 1 && i.getDate() === t) return i;
91
+ }
92
+ //#endregion
93
+ //#region src/DatePicker.tsx
94
+ function y({ value: o, onChange: s, dateFormat: c = "dd/MM/yyyy", disabled: u = !1, name: h, id: y, showDropdowns: b = !1, maxYear: x, yearRange: S = 100, dropdownIcon: C, theme: w = "light", outlined: T = !1, isPlainStyle: E = !1, classNames: D, styles: O, ...k }) {
95
+ let A = x ?? (/* @__PURE__ */ new Date()).getFullYear(), j = t(() => d(c), [c]), M = t(() => j.filter((e) => e.type === "field").map((e) => e.key), [j]), [N, P] = r(() => g(o)), F = n({
96
+ dd: null,
97
+ MM: null,
98
+ yyyy: null
99
+ }), I = n(o && l(o) ? o.getTime() : void 0), L = o && l(o) ? o.getTime() : void 0;
100
+ e(() => {
101
+ L !== I.current && (I.current = L, P(g(o)));
102
+ }, [L]);
103
+ function R(e) {
104
+ let t = v(e);
105
+ if (t) {
106
+ I.current = t.getTime(), s?.(t);
107
+ return;
108
+ }
109
+ I.current = void 0, s?.(void 0);
110
+ }
111
+ function z(e, t) {
112
+ let n = M[M.indexOf(e) + t];
113
+ if (!n) return;
114
+ let r = F.current[n];
115
+ if (r) if (r.focus(), t === -1) {
116
+ let e = r.value.length;
117
+ try {
118
+ r.setSelectionRange(e, e);
119
+ } catch {}
120
+ } else try {
121
+ r.setSelectionRange(0, r.value.length);
122
+ } catch {}
123
+ }
124
+ function B(e, t, n) {
125
+ let r = t.replace(/\D/g, "").slice(0, n);
126
+ P((t) => {
127
+ let i = {
128
+ ...t,
129
+ [e]: r
130
+ };
131
+ if (r.length > 0) {
132
+ let a = parseInt(r, 10);
133
+ if (r.length < n && (e === "dd" && parseInt(r[0], 10) > 3 || e === "MM" && parseInt(r[0], 10) > 1) || r.length === n && (e === "dd" && (a < 1 || a > _(i.MM, i.yyyy)) || e === "MM" && (a < 1 || a > 12) || e === "yyyy" && a < 1)) return t;
134
+ }
135
+ return e !== "dd" && i.dd.length === 2 && parseInt(i.dd, 10) > _(i.MM, i.yyyy) && (i.dd = ""), r.length === n && queueMicrotask(() => z(e, 1)), R(i), i;
136
+ });
137
+ }
138
+ function V(e) {
139
+ if (e === "dd") {
140
+ let e = _(N.MM, N.yyyy);
141
+ return Array.from({ length: e }, (e, t) => f(t + 1, 2));
142
+ }
143
+ return e === "MM" ? Array.from({ length: 12 }, (e, t) => f(t + 1, 2)) : Array.from({ length: S }, (e, t) => f(A - t, 4));
144
+ }
145
+ function H(e, t, n) {
146
+ B(e, t, n);
147
+ }
148
+ function U(e, t) {
149
+ let n = t.currentTarget, r = n.selectionStart === 0 && n.selectionEnd === 0, i = n.selectionStart === n.value.length && n.selectionEnd === n.value.length;
150
+ if (t.key === "Backspace" && n.value === "") {
151
+ t.preventDefault(), z(e, -1);
152
+ return;
153
+ }
154
+ if (t.key === "ArrowLeft" && r) {
155
+ t.preventDefault(), z(e, -1);
156
+ return;
157
+ }
158
+ t.key === "ArrowRight" && i && (t.preventDefault(), z(e, 1));
159
+ }
160
+ let W = E ? {
161
+ position: "relative",
162
+ display: "inline-flex",
163
+ alignItems: "center",
164
+ justifyContent: "center",
165
+ width: "1em",
166
+ height: "1em",
167
+ cursor: u ? "not-allowed" : "pointer",
168
+ userSelect: "none",
169
+ lineHeight: 1
170
+ } : { position: "relative" }, G = {
171
+ position: "absolute",
172
+ inset: 0,
173
+ width: "100%",
174
+ height: "100%",
175
+ opacity: 0,
176
+ cursor: u ? "not-allowed" : "pointer",
177
+ appearance: "none",
178
+ WebkitAppearance: "none",
179
+ MozAppearance: "none",
180
+ border: 0,
181
+ padding: 0,
182
+ margin: 0,
183
+ background: "transparent",
184
+ color: "transparent",
185
+ fontSize: "inherit"
186
+ }, K = E ? {
187
+ display: "inline-flex",
188
+ alignItems: "center"
189
+ } : void 0, q = (e, t) => {
190
+ let n = D?.[t];
191
+ return e ? n ? `${e} ${n}` : e : n;
192
+ }, J = (e, t) => {
193
+ let n = O?.[t];
194
+ return e ? n ? {
195
+ ...e,
196
+ ...n
197
+ } : e : n;
198
+ };
199
+ return /* @__PURE__ */ i("span", {
200
+ role: "group",
201
+ "aria-label": k["aria-label"] ?? "Date",
202
+ id: y,
203
+ className: q(E ? void 0 : "rdp-root", "root"),
204
+ style: J(void 0, "root"),
205
+ "data-theme": E ? void 0 : w,
206
+ "data-outlined": E ? void 0 : T ? "true" : "false",
207
+ "data-disabled": E ? void 0 : u ? "true" : "false",
208
+ children: j.map((e, t) => e.type === "sep" ? /* @__PURE__ */ i("span", {
209
+ "aria-hidden": "true",
210
+ className: q(E ? void 0 : "rdp-sep", "separator"),
211
+ style: J(void 0, "separator"),
212
+ children: e.value
213
+ }, `s${t}`) : /* @__PURE__ */ a("span", {
214
+ className: q(E ? void 0 : "rdp-segment", "segment"),
215
+ style: J(K, "segment"),
216
+ children: [/* @__PURE__ */ i("input", {
217
+ ref: (t) => {
218
+ F.current[e.key] = t;
219
+ },
220
+ type: "text",
221
+ inputMode: "numeric",
222
+ pattern: "[0-9]*",
223
+ autoComplete: "off",
224
+ disabled: u,
225
+ name: h ? `${h}-${e.key}` : void 0,
226
+ maxLength: e.length,
227
+ size: e.length,
228
+ placeholder: p[e.key],
229
+ "aria-label": m[e.key],
230
+ className: q(E ? void 0 : "rdp-input", "input"),
231
+ style: J(void 0, "input"),
232
+ value: N[e.key],
233
+ onChange: (t) => B(e.key, t.target.value, e.length),
234
+ onKeyDown: (t) => U(e.key, t),
235
+ onFocus: (e) => e.currentTarget.select()
236
+ }), b && /* @__PURE__ */ a("span", {
237
+ className: q(E ? void 0 : "rdp-trigger", "trigger"),
238
+ style: J(W, "trigger"),
239
+ children: [/* @__PURE__ */ i("span", {
240
+ "aria-hidden": "true",
241
+ className: q(void 0, "dropdownIcon"),
242
+ style: J(void 0, "dropdownIcon"),
243
+ children: typeof C == "function" ? C(e.key) : C ?? "▾"
244
+ }), /* @__PURE__ */ a("select", {
245
+ "aria-label": `Pick ${m[e.key]}`,
246
+ disabled: u,
247
+ className: q(E ? void 0 : "rdp-select", "select"),
248
+ value: N[e.key],
249
+ onChange: (t) => H(e.key, t.target.value, e.length),
250
+ style: J(G, "select"),
251
+ children: [/* @__PURE__ */ i("option", {
252
+ value: "",
253
+ disabled: !0,
254
+ hidden: !0,
255
+ children: m[e.key]
256
+ }), V(e.key).map((e) => /* @__PURE__ */ i("option", {
257
+ value: e,
258
+ children: e
259
+ }, e))]
260
+ })]
261
+ })]
262
+ }, e.key))
263
+ });
264
+ }
265
+ //#endregion
266
+ export { y as DatePicker };
267
+
268
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["toDate","toDate","toDate"],"sources":["../node_modules/date-fns/toDate.mjs","../node_modules/date-fns/constructFrom.mjs","../node_modules/date-fns/isDate.mjs","../node_modules/date-fns/isValid.mjs","../node_modules/date-fns/getDaysInMonth.mjs","../src/utils/format.ts","../src/utils/string.ts","../src/utils/constants.ts","../src/utils/time.ts","../src/DatePicker.tsx"],"sourcesContent":["/**\n * @name toDate\n * @category Common Helpers\n * @summary Convert the given argument to an instance of Date.\n *\n * @description\n * Convert the given argument to an instance of Date.\n *\n * If the argument is an instance of Date, the function returns its clone.\n *\n * If the argument is a number, it is treated as a timestamp.\n *\n * If the argument is none of the above, the function returns Invalid Date.\n *\n * **Note**: *all* Date arguments passed to any *date-fns* function is processed by `toDate`.\n *\n * @typeParam DateType - The `Date` type, the function operates on. Gets inferred from passed arguments. Allows to use extensions like [`UTCDate`](https://github.com/date-fns/utc).\n *\n * @param argument - The value to convert\n *\n * @returns The parsed date in the local time zone\n *\n * @example\n * // Clone the date:\n * const result = toDate(new Date(2014, 1, 11, 11, 30, 30))\n * //=> Tue Feb 11 2014 11:30:30\n *\n * @example\n * // Convert the timestamp to date:\n * const result = toDate(1392098430000)\n * //=> Tue Feb 11 2014 11:30:30\n */\nexport function toDate(argument) {\n const argStr = Object.prototype.toString.call(argument);\n\n // Clone the date\n if (\n argument instanceof Date ||\n (typeof argument === \"object\" && argStr === \"[object Date]\")\n ) {\n // Prevent the date to lose the milliseconds when passed to new Date() in IE10\n return new argument.constructor(+argument);\n } else if (\n typeof argument === \"number\" ||\n argStr === \"[object Number]\" ||\n typeof argument === \"string\" ||\n argStr === \"[object String]\"\n ) {\n // TODO: Can we get rid of as?\n return new Date(argument);\n } else {\n // TODO: Can we get rid of as?\n return new Date(NaN);\n }\n}\n\n// Fallback for modularized imports:\nexport default toDate;\n","/**\n * @name constructFrom\n * @category Generic Helpers\n * @summary Constructs a date using the reference date and the value\n *\n * @description\n * The function constructs a new date using the constructor from the reference\n * date and the given value. It helps to build generic functions that accept\n * date extensions.\n *\n * It defaults to `Date` if the passed reference date is a number or a string.\n *\n * @typeParam DateType - The `Date` type, the function operates on. Gets inferred from passed arguments. Allows to use extensions like [`UTCDate`](https://github.com/date-fns/utc).\n *\n * @param date - The reference date to take constructor from\n * @param value - The value to create the date\n *\n * @returns Date initialized using the given date and value\n *\n * @example\n * import { constructFrom } from 'date-fns'\n *\n * // A function that clones a date preserving the original type\n * function cloneDate<DateType extends Date(date: DateType): DateType {\n * return constructFrom(\n * date, // Use contrustor from the given date\n * date.getTime() // Use the date value to create a new date\n * )\n * }\n */\nexport function constructFrom(date, value) {\n if (date instanceof Date) {\n return new date.constructor(value);\n } else {\n return new Date(value);\n }\n}\n\n// Fallback for modularized imports:\nexport default constructFrom;\n","/**\n * @name isDate\n * @category Common Helpers\n * @summary Is the given value a date?\n *\n * @description\n * Returns true if the given value is an instance of Date. The function works for dates transferred across iframes.\n *\n * @param value - The value to check\n *\n * @returns True if the given value is a date\n *\n * @example\n * // For a valid date:\n * const result = isDate(new Date())\n * //=> true\n *\n * @example\n * // For an invalid date:\n * const result = isDate(new Date(NaN))\n * //=> true\n *\n * @example\n * // For some value:\n * const result = isDate('2014-02-31')\n * //=> false\n *\n * @example\n * // For an object:\n * const result = isDate({})\n * //=> false\n */\nexport function isDate(value) {\n return (\n value instanceof Date ||\n (typeof value === \"object\" &&\n Object.prototype.toString.call(value) === \"[object Date]\")\n );\n}\n\n// Fallback for modularized imports:\nexport default isDate;\n","import { isDate } from \"./isDate.mjs\";\nimport { toDate } from \"./toDate.mjs\";\n\n/**\n * @name isValid\n * @category Common Helpers\n * @summary Is the given date valid?\n *\n * @description\n * Returns false if argument is Invalid Date and true otherwise.\n * Argument is converted to Date using `toDate`. See [toDate](https://date-fns.org/docs/toDate)\n * Invalid Date is a Date, whose time value is NaN.\n *\n * Time value of Date: http://es5.github.io/#x15.9.1.1\n *\n * @typeParam DateType - The `Date` type, the function operates on. Gets inferred from passed arguments. Allows to use extensions like [`UTCDate`](https://github.com/date-fns/utc).\n *\n * @param date - The date to check\n *\n * @returns The date is valid\n *\n * @example\n * // For the valid date:\n * const result = isValid(new Date(2014, 1, 31))\n * //=> true\n *\n * @example\n * // For the value, convertable into a date:\n * const result = isValid(1393804800000)\n * //=> true\n *\n * @example\n * // For the invalid date:\n * const result = isValid(new Date(''))\n * //=> false\n */\nexport function isValid(date) {\n if (!isDate(date) && typeof date !== \"number\") {\n return false;\n }\n const _date = toDate(date);\n return !isNaN(Number(_date));\n}\n\n// Fallback for modularized imports:\nexport default isValid;\n","import { toDate } from \"./toDate.mjs\";\nimport { constructFrom } from \"./constructFrom.mjs\";\n\n/**\n * @name getDaysInMonth\n * @category Month Helpers\n * @summary Get the number of days in a month of the given date.\n *\n * @description\n * Get the number of days in a month of the given date.\n *\n * @typeParam DateType - The `Date` type, the function operates on. Gets inferred from passed arguments. Allows to use extensions like [`UTCDate`](https://github.com/date-fns/utc).\n *\n * @param date - The given date\n *\n * @returns The number of days in a month\n *\n * @example\n * // How many days are in February 2000?\n * const result = getDaysInMonth(new Date(2000, 1))\n * //=> 29\n */\nexport function getDaysInMonth(date) {\n const _date = toDate(date);\n const year = _date.getFullYear();\n const monthIndex = _date.getMonth();\n const lastDayOfMonth = constructFrom(date, 0);\n lastDayOfMonth.setFullYear(year, monthIndex + 1, 0);\n lastDayOfMonth.setHours(0, 0, 0, 0);\n return lastDayOfMonth.getDate();\n}\n\n// Fallback for modularized imports:\nexport default getDaysInMonth;\n","import type { SegmentKey, Token } from '../types'\n\n/**\n * Parse a date format string (e.g. `dd/MM/yyyy`) into an ordered list of\n * field and separator tokens. Recognized field tokens: `dd`, `MM`, `yyyy`.\n */\nexport function parseFormat(format: string): Token[] {\n const re = /(dd|MM|yyyy)/g\n const tokens: Token[] = []\n let last = 0\n let m: RegExpExecArray | null\n while ((m = re.exec(format)) !== null) {\n if (m.index > last) {\n tokens.push({ type: 'sep', value: format.slice(last, m.index) })\n }\n const key = m[1] as SegmentKey\n tokens.push({ type: 'field', key, length: key === 'yyyy' ? 4 : 2 })\n last = m.index + m[1].length\n }\n if (last < format.length) {\n tokens.push({ type: 'sep', value: format.slice(last) })\n }\n return tokens\n}\n","/** Left-pad a number to the given length with leading zeroes. */\nexport function pad(n: number, len: number): string {\n return String(n).padStart(len, '0')\n}\n","import type { SegmentKey, Values } from '../types'\n\nexport const PLACEHOLDER: Record<SegmentKey, string> = {\n dd: 'DD',\n MM: 'MM',\n yyyy: 'YYYY',\n}\n\nexport const LABEL: Record<SegmentKey, string> = {\n dd: 'Day',\n MM: 'Month',\n yyyy: 'Year',\n}\n\nexport const EMPTY: Values = { dd: '', MM: '', yyyy: '' }\n","import { getDaysInMonth, isValid } from 'date-fns'\nimport { pad } from './string'\nimport { EMPTY } from './constants'\nimport type { Values } from '../types'\n\n/** Build segment values from a `Date`. Returns empty values for invalid input. */\nexport function fromDate(date: Date | undefined): Values {\n if (!date || !isValid(date)) return EMPTY\n return {\n dd: pad(date.getDate(), 2),\n MM: pad(date.getMonth() + 1, 2),\n yyyy: pad(date.getFullYear(), 4),\n }\n}\n\n/**\n * Maximum allowed day for a given month/year segment context.\n * Falls back to a leap-safe year (2000) when the year is unknown so that\n * Feb 29 is initially permitted and re-validated once the year is provided.\n */\nexport function maxDay(monthStr: string, yearStr: string): number {\n const m = parseInt(monthStr, 10)\n if (!m || m < 1 || m > 12) return 31\n const y = yearStr.length === 4 ? parseInt(yearStr, 10) : 2000\n return getDaysInMonth(new Date(y, m - 1, 1))\n}\n\n/**\n * Build a `Date` from segment values if and only if they form a valid\n * calendar date. Returns `undefined` for partial or invalid values\n * (e.g. 31/02/2024).\n */\nexport function toDate(values: Values): Date | undefined {\n if (values.dd.length !== 2 || values.MM.length !== 2 || values.yyyy.length !== 4) {\n return undefined\n }\n const d = parseInt(values.dd, 10)\n const m = parseInt(values.MM, 10)\n const y = parseInt(values.yyyy, 10)\n const date = new Date(y, m - 1, d)\n if (\n date.getFullYear() === y &&\n date.getMonth() === m - 1 &&\n date.getDate() === d\n ) {\n return date\n }\n return undefined\n}\n","import { isValid } from 'date-fns'\nimport {\n type ChangeEvent,\n type CSSProperties,\n type KeyboardEvent,\n type ReactNode,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport { parseFormat } from './utils/format'\nimport { pad } from './utils/string'\nimport { fromDate, maxDay, toDate } from './utils/time'\nimport { LABEL, PLACEHOLDER } from './utils/constants'\nimport type { SegmentKey, Token, Values } from './types'\n\nexport interface DatePickerProps {\n value?: Date\n onChange?: (date: Date | undefined) => void\n /** Format using `dd`, `MM`, `yyyy` tokens. Defaults to `dd/MM/yyyy`. */\n dateFormat?: string\n disabled?: boolean\n name?: string\n id?: string\n 'aria-label'?: string\n /** When true, render a native `<select>` dropdown next to each segment. */\n showDropdowns?: boolean\n /** Latest selectable year in the year dropdown. Defaults to the current year. */\n maxYear?: number\n /** How many years to include in the year dropdown (descending from `maxYear`). Defaults to 100. */\n yearRange?: number\n /**\n * Custom dropdown trigger icon. Either a single node used for every segment,\n * or a render function that receives the segment key. Defaults to a chevron.\n * Only used when `showDropdowns` is true.\n */\n dropdownIcon?: ReactNode | ((segment: SegmentKey) => ReactNode)\n /** Color theme. Defaults to `'light'`. Ignored when `isPlainStyle` is true. */\n theme?: 'light' | 'dark'\n /** When true, draw a single border around the whole component. Defaults to false. */\n outlined?: boolean\n /** When true, no styles or class names are applied. Use this if you want to style from scratch. */\n isPlainStyle?: boolean\n /** Per-slot class names. Merged after the built-in `rdp-*` classes (or used alone in plain mode). */\n classNames?: Partial<Record<StyleSlot, string>>\n /** Per-slot inline styles. Merged on top of the component's own inline styles. */\n styles?: Partial<Record<StyleSlot, CSSProperties>>\n}\n\n/** Style slots a consumer can target via `classNames` / `styles` props. */\nexport type StyleSlot =\n | 'root'\n | 'segment'\n | 'input'\n | 'separator'\n | 'trigger'\n | 'dropdownIcon'\n | 'select'\n\nexport function DatePicker({\n value,\n onChange,\n dateFormat = 'dd/MM/yyyy',\n disabled = false,\n name,\n id,\n showDropdowns = false,\n maxYear,\n yearRange = 100,\n dropdownIcon,\n theme = 'light',\n outlined = false,\n isPlainStyle = false,\n classNames,\n styles: slotStyles,\n ...rest\n}: DatePickerProps) {\n const resolvedMaxYear = maxYear ?? new Date().getFullYear()\n const tokens = useMemo(() => parseFormat(dateFormat), [dateFormat])\n const fieldKeys = useMemo(\n () =>\n tokens\n .filter((t): t is Extract<Token, { type: 'field' }> => t.type === 'field')\n .map((t) => t.key),\n [tokens],\n )\n\n const [values, setValues] = useState<Values>(() => fromDate(value))\n const refs = useRef<Record<SegmentKey, HTMLInputElement | null>>({\n dd: null,\n MM: null,\n yyyy: null,\n })\n\n // Tracks the last value we emitted to the parent. Used to ignore the\n // controlled-parent's echo (which would otherwise overwrite an in-progress\n // partial edit, e.g. clearing one digit of the year).\n const lastEmittedTime = useRef<number | undefined>(\n value && isValid(value) ? value.getTime() : undefined,\n )\n\n // Sync from external value changes only when the incoming value is\n // genuinely different from what this component last emitted.\n const valueTime = value && isValid(value) ? value.getTime() : undefined\n useEffect(() => {\n if (valueTime === lastEmittedTime.current) return\n lastEmittedTime.current = valueTime\n setValues(fromDate(value))\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [valueTime])\n\n function emit(next: Values) {\n const date = toDate(next)\n if (date) {\n lastEmittedTime.current = date.getTime()\n onChange?.(date)\n return\n }\n lastEmittedTime.current = undefined\n onChange?.(undefined)\n }\n\n function focusSibling(current: SegmentKey, dir: 1 | -1) {\n const idx = fieldKeys.indexOf(current)\n const target = fieldKeys[idx + dir]\n if (!target) return\n const el = refs.current[target]\n if (!el) return\n el.focus()\n if (dir === -1) {\n const len = el.value.length\n try {\n el.setSelectionRange(len, len)\n } catch {\n /* some input types don't support selection */\n }\n } else {\n try {\n el.setSelectionRange(0, el.value.length)\n } catch {\n /* noop */\n }\n }\n }\n\n function handleChange(key: SegmentKey, raw: string, maxLen: number) {\n // Strip non-digits and clamp length\n const digits = raw.replace(/\\D/g, '').slice(0, maxLen)\n\n setValues((prev) => {\n const next: Values = { ...prev, [key]: digits }\n\n if (digits.length > 0) {\n const num = parseInt(digits, 10)\n\n // Reject impossible first digits (so user can't even type 4-9 as first dd digit, etc.)\n if (digits.length < maxLen) {\n if (key === 'dd' && parseInt(digits[0]!, 10) > 3) return prev\n if (key === 'MM' && parseInt(digits[0]!, 10) > 1) return prev\n }\n\n // Reject full segments that exceed allowed range\n if (digits.length === maxLen) {\n if (key === 'dd' && (num < 1 || num > maxDay(next.MM, next.yyyy))) return prev\n if (key === 'MM' && (num < 1 || num > 12)) return prev\n if (key === 'yyyy' && num < 1) return prev\n }\n }\n\n // If month or year changed, ensure existing day is still valid; clear it otherwise.\n if (key !== 'dd' && next.dd.length === 2) {\n const dnum = parseInt(next.dd, 10)\n if (dnum > maxDay(next.MM, next.yyyy)) next.dd = ''\n }\n\n // Auto-advance once segment is full\n if (digits.length === maxLen) {\n queueMicrotask(() => focusSibling(key, 1))\n }\n\n emit(next)\n return next\n })\n }\n\n function getOptions(key: SegmentKey): string[] {\n if (key === 'dd') {\n const max = maxDay(values.MM, values.yyyy)\n return Array.from({ length: max }, (_, i) => pad(i + 1, 2))\n }\n if (key === 'MM') {\n return Array.from({ length: 12 }, (_, i) => pad(i + 1, 2))\n }\n // yyyy: descending from resolvedMaxYear\n return Array.from({ length: yearRange }, (_, i) => pad(resolvedMaxYear - i, 4))\n }\n\n function handleSelect(key: SegmentKey, raw: string, maxLen: 2 | 4) {\n // Route through handleChange so all validation + day re-check + emit happens.\n handleChange(key, raw, maxLen)\n }\n\n function handleKeyDown(key: SegmentKey, e: KeyboardEvent<HTMLInputElement>) {\n const target = e.currentTarget\n const atStart = target.selectionStart === 0 && target.selectionEnd === 0\n const atEnd =\n target.selectionStart === target.value.length &&\n target.selectionEnd === target.value.length\n\n if (e.key === 'Backspace' && target.value === '') {\n e.preventDefault()\n focusSibling(key, -1)\n return\n }\n if (e.key === 'ArrowLeft' && atStart) {\n e.preventDefault()\n focusSibling(key, -1)\n return\n }\n if (e.key === 'ArrowRight' && atEnd) {\n e.preventDefault()\n focusSibling(key, 1)\n }\n }\n\n // Inline styles only used in plain mode. The CSS file (when not plain)\n // handles layout via .rdp-* classes; the absolute positioning of the\n // invisible <select> over the icon is functional, not cosmetic, so it\n // must always be applied.\n const triggerStyle = isPlainStyle\n ? {\n position: 'relative' as const,\n display: 'inline-flex' as const,\n alignItems: 'center' as const,\n justifyContent: 'center' as const,\n width: '1em',\n height: '1em',\n cursor: disabled ? ('not-allowed' as const) : ('pointer' as const),\n userSelect: 'none' as const,\n lineHeight: 1,\n }\n : { position: 'relative' as const }\n\n const selectStyle = {\n position: 'absolute' as const,\n inset: 0,\n width: '100%',\n height: '100%',\n opacity: 0,\n cursor: disabled ? ('not-allowed' as const) : ('pointer' as const),\n appearance: 'none' as const,\n WebkitAppearance: 'none' as const,\n MozAppearance: 'none' as const,\n border: 0,\n padding: 0,\n margin: 0,\n background: 'transparent',\n color: 'transparent',\n fontSize: 'inherit',\n }\n\n const segmentStyle = isPlainStyle\n ? { display: 'inline-flex' as const, alignItems: 'center' as const }\n : undefined\n\n // Helper: combine the built-in class for a slot with any consumer-provided class.\n const cn = (builtin: string | undefined, slot: StyleSlot) => {\n const extra = classNames?.[slot]\n if (!builtin) return extra\n return extra ? `${builtin} ${extra}` : builtin\n }\n\n // Helper: merge built-in inline style with consumer slot style (consumer wins).\n const mergeStyle = (\n base: CSSProperties | undefined,\n slot: StyleSlot,\n ): CSSProperties | undefined => {\n const extra = slotStyles?.[slot]\n if (!base) return extra\n if (!extra) return base\n return { ...base, ...extra }\n }\n\n return (\n <span\n role=\"group\"\n aria-label={rest['aria-label'] ?? 'Date'}\n id={id}\n className={cn(isPlainStyle ? undefined : 'rdp-root', 'root')}\n style={mergeStyle(undefined, 'root')}\n data-theme={isPlainStyle ? undefined : theme}\n data-outlined={isPlainStyle ? undefined : outlined ? 'true' : 'false'}\n data-disabled={isPlainStyle ? undefined : disabled ? 'true' : 'false'}\n >\n {tokens.map((t, i) => {\n if (t.type === 'sep') {\n return (\n <span\n key={`s${i}`}\n aria-hidden=\"true\"\n className={cn(isPlainStyle ? undefined : 'rdp-sep', 'separator')}\n style={mergeStyle(undefined, 'separator')}\n >\n {t.value}\n </span>\n )\n }\n return (\n <span\n key={t.key}\n className={cn(isPlainStyle ? undefined : 'rdp-segment', 'segment')}\n style={mergeStyle(segmentStyle, 'segment')}\n >\n <input\n ref={(el) => {\n refs.current[t.key] = el\n }}\n type=\"text\"\n inputMode=\"numeric\"\n pattern=\"[0-9]*\"\n autoComplete=\"off\"\n disabled={disabled}\n name={name ? `${name}-${t.key}` : undefined}\n maxLength={t.length}\n size={t.length}\n placeholder={PLACEHOLDER[t.key]}\n aria-label={LABEL[t.key]}\n className={cn(isPlainStyle ? undefined : 'rdp-input', 'input')}\n style={mergeStyle(undefined, 'input')}\n value={values[t.key]}\n onChange={(e: ChangeEvent<HTMLInputElement>) =>\n handleChange(t.key, e.target.value, t.length)\n }\n onKeyDown={(e) => handleKeyDown(t.key, e)}\n onFocus={(e) => e.currentTarget.select()}\n />\n {showDropdowns && (\n <span\n className={cn(isPlainStyle ? undefined : 'rdp-trigger', 'trigger')}\n style={mergeStyle(triggerStyle, 'trigger')}\n >\n <span\n aria-hidden=\"true\"\n className={cn(undefined, 'dropdownIcon')}\n style={mergeStyle(undefined, 'dropdownIcon')}\n >\n {typeof dropdownIcon === 'function'\n ? dropdownIcon(t.key)\n : (dropdownIcon ?? '▾')}\n </span>\n <select\n aria-label={`Pick ${LABEL[t.key]}`}\n disabled={disabled}\n className={cn(isPlainStyle ? undefined : 'rdp-select', 'select')}\n value={values[t.key]}\n onChange={(e) => handleSelect(t.key, e.target.value, t.length)}\n style={mergeStyle(selectStyle, 'select')}\n >\n <option value=\"\" disabled hidden>\n {LABEL[t.key]}\n </option>\n {getOptions(t.key).map((opt) => (\n <option key={opt} value={opt}>\n {opt}\n </option>\n ))}\n </select>\n </span>\n )}\n </span>\n )\n })}\n </span>\n )\n}\n"],"x_google_ignoreList":[0,1,2,3,4],"mappings":"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"}
@@ -0,0 +1,137 @@
1
+ /* @punaro/react-datepicker — opt-in stylesheet
2
+ *
3
+ * Consumers: import '@punaro/react-datepicker/styles.css'
4
+ *
5
+ * All selectors are prefixed with `.rdp-` to avoid collisions.
6
+ * Theme via the `theme` prop (data-theme attr) or by overriding the CSS
7
+ * variables on `.rdp-root` from your own stylesheet.
8
+ */
9
+
10
+ .rdp-root {
11
+ --rdp-bg: #ffffff;
12
+ --rdp-fg: #111827;
13
+ --rdp-muted: #6b7280;
14
+ --rdp-border: #d1d5db;
15
+ --rdp-focus: #3b82f6;
16
+ --rdp-hover-bg: #f3f4f6;
17
+ --rdp-radius: 6px;
18
+ --rdp-pad-y: 6px;
19
+ --rdp-pad-x: 10px;
20
+ --rdp-gap: 2px;
21
+
22
+ display: inline-flex;
23
+ align-items: center;
24
+ gap: var(--rdp-gap);
25
+ font: inherit;
26
+ color: var(--rdp-fg);
27
+ background: var(--rdp-bg);
28
+ border-radius: var(--rdp-radius);
29
+ line-height: 1.4;
30
+ }
31
+
32
+ .rdp-root[data-theme="dark"] {
33
+ --rdp-bg: #1f2937;
34
+ --rdp-fg: #f9fafb;
35
+ --rdp-muted: #9ca3af;
36
+ --rdp-border: #374151;
37
+ --rdp-focus: #60a5fa;
38
+ --rdp-hover-bg: #374151;
39
+ }
40
+
41
+ /* Outer "single text field" border — only when outlined */
42
+ .rdp-root[data-outlined="true"] {
43
+ border: 1px solid var(--rdp-border);
44
+ padding: var(--rdp-pad-y) var(--rdp-pad-x);
45
+ transition: border-color 120ms, box-shadow 120ms;
46
+ }
47
+
48
+ .rdp-root[data-outlined="true"]:focus-within {
49
+ border-color: var(--rdp-focus);
50
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--rdp-focus) 25%, transparent);
51
+ }
52
+
53
+ .rdp-root[data-disabled="true"] {
54
+ opacity: 0.6;
55
+ cursor: not-allowed;
56
+ }
57
+
58
+ .rdp-segment {
59
+ display: inline-flex;
60
+ align-items: center;
61
+ }
62
+
63
+ /* Strip native <input> chrome so segments feel like one continuous field */
64
+ .rdp-input {
65
+ font: inherit;
66
+ color: inherit;
67
+ background: transparent;
68
+ border: 0;
69
+ outline: 0;
70
+ padding: 2px 0;
71
+ margin: 0;
72
+ text-align: center;
73
+ caret-color: var(--rdp-focus);
74
+ }
75
+
76
+ .rdp-input::placeholder {
77
+ color: var(--rdp-muted);
78
+ }
79
+
80
+ .rdp-input:focus-visible {
81
+ background: var(--rdp-hover-bg);
82
+ border-radius: 3px;
83
+ }
84
+
85
+ .rdp-input:disabled {
86
+ cursor: not-allowed;
87
+ }
88
+
89
+ .rdp-sep {
90
+ color: var(--rdp-muted);
91
+ user-select: none;
92
+ padding: 0 1px;
93
+ }
94
+
95
+ /* Dropdown trigger (the icon). The native <select> is layered invisibly on top. */
96
+ .rdp-trigger {
97
+ position: relative;
98
+ display: inline-flex;
99
+ align-items: center;
100
+ justify-content: center;
101
+ width: 1em;
102
+ height: 1em;
103
+ margin-left: 2px;
104
+ cursor: pointer;
105
+ user-select: none;
106
+ line-height: 1;
107
+ color: var(--rdp-muted);
108
+ border-radius: 3px;
109
+ transition: background 120ms, color 120ms;
110
+ }
111
+
112
+ .rdp-trigger:hover {
113
+ background: var(--rdp-hover-bg);
114
+ color: var(--rdp-fg);
115
+ }
116
+
117
+ .rdp-trigger:focus-within {
118
+ color: var(--rdp-focus);
119
+ outline: 2px solid color-mix(in srgb, var(--rdp-focus) 35%, transparent);
120
+ outline-offset: 1px;
121
+ }
122
+
123
+ /* Dropdown options. Most browsers (desktop Chromium/Firefox) honor a
124
+ * limited subset of CSS on <option>. iOS/Android use the native OS picker
125
+ * and ignore these — that's expected and good for mobile UX. */
126
+ .rdp-root option {
127
+ background: var(--rdp-bg);
128
+ color: var(--rdp-fg);
129
+ padding: 4px 8px;
130
+ font: inherit;
131
+ }
132
+
133
+ .rdp-root option:checked,
134
+ .rdp-root option:hover {
135
+ background: var(--rdp-focus);
136
+ color: #fff;
137
+ }
@@ -0,0 +1,10 @@
1
+ export type SegmentKey = 'dd' | 'MM' | 'yyyy';
2
+ export type Values = Record<SegmentKey, string>;
3
+ export type Token = {
4
+ type: 'field';
5
+ key: SegmentKey;
6
+ length: 2 | 4;
7
+ } | {
8
+ type: 'sep';
9
+ value: string;
10
+ };
@@ -0,0 +1,4 @@
1
+ import { SegmentKey, Values } from '../types';
2
+ export declare const PLACEHOLDER: Record<SegmentKey, string>;
3
+ export declare const LABEL: Record<SegmentKey, string>;
4
+ export declare const EMPTY: Values;
@@ -0,0 +1,6 @@
1
+ import { Token } from '../types';
2
+ /**
3
+ * Parse a date format string (e.g. `dd/MM/yyyy`) into an ordered list of
4
+ * field and separator tokens. Recognized field tokens: `dd`, `MM`, `yyyy`.
5
+ */
6
+ export declare function parseFormat(format: string): Token[];
@@ -0,0 +1,2 @@
1
+ /** Left-pad a number to the given length with leading zeroes. */
2
+ export declare function pad(n: number, len: number): string;
@@ -0,0 +1,15 @@
1
+ import { Values } from '../types';
2
+ /** Build segment values from a `Date`. Returns empty values for invalid input. */
3
+ export declare function fromDate(date: Date | undefined): Values;
4
+ /**
5
+ * Maximum allowed day for a given month/year segment context.
6
+ * Falls back to a leap-safe year (2000) when the year is unknown so that
7
+ * Feb 29 is initially permitted and re-validated once the year is provided.
8
+ */
9
+ export declare function maxDay(monthStr: string, yearStr: string): number;
10
+ /**
11
+ * Build a `Date` from segment values if and only if they form a valid
12
+ * calendar date. Returns `undefined` for partial or invalid values
13
+ * (e.g. 31/02/2024).
14
+ */
15
+ export declare function toDate(values: Values): Date | undefined;
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@punaro/react-datepicker",
3
+ "version": "0.0.1",
4
+ "description": "A lightweight, accessible React date picker component",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "require": "./dist/index.cjs",
13
+ "types": "./dist/index.d.ts"
14
+ },
15
+ "./styles.css": "./dist/styles.css"
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "sideEffects": [
21
+ "./dist/styles.css"
22
+ ],
23
+ "scripts": {
24
+ "dev": "vite",
25
+ "build": "vite build && cp src/styles.css dist/styles.css",
26
+ "build:watch": "vite build --watch",
27
+ "preview": "vite preview",
28
+ "lint": "eslint src --ext .ts,.tsx"
29
+ },
30
+ "keywords": [
31
+ "react",
32
+ "datepicker",
33
+ "date-picker",
34
+ "calendar",
35
+ "component"
36
+ ],
37
+ "license": "MIT",
38
+ "peerDependencies": {
39
+ "react": ">=17.0.0",
40
+ "react-dom": ">=17.0.0"
41
+ },
42
+ "dependencies": {
43
+ "date-fns": "^3.6.0"
44
+ },
45
+ "devDependencies": {
46
+ "@types/node": "^25.7.0",
47
+ "@types/react": "^18.3.0",
48
+ "@types/react-dom": "^18.3.0",
49
+ "@vitejs/plugin-react": "^5.0.0",
50
+ "react": "^18.3.1",
51
+ "react-dom": "^18.3.1",
52
+ "typescript": "^5.5.0",
53
+ "vite": "^8.0.12",
54
+ "vite-plugin-dts": "^4.0.0"
55
+ }
56
+ }