periplo-ui 3.57.0 → 3.59.0
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/dist/components/InputDateMask/InputDate.d.ts +27 -0
- package/dist/components/InputDateMask/InputDate.js +130 -0
- package/dist/components/InputDateMask/InputDate.js.map +1 -0
- package/dist/components/InputDateMask/MaskedInput.d.ts +18 -0
- package/dist/components/InputDateMask/MaskedInput.js +227 -0
- package/dist/components/InputDateMask/MaskedInput.js.map +1 -0
- package/dist/components/InputDateMask/index.d.ts +1 -0
- package/dist/components/InputDateMask/index.js +2 -0
- package/dist/components/InputDateMask/index.js.map +1 -0
- package/dist/components/InputDateMask/manualDateFormat.d.ts +11 -0
- package/dist/components/InputDateMask/manualDateFormat.js +60 -0
- package/dist/components/InputDateMask/manualDateFormat.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { DateInputFormat } from './manualDateFormat';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
export type InputDateProps = {
|
|
4
|
+
className?: string;
|
|
5
|
+
endContent?: React.ReactElement;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
error?: boolean | string;
|
|
8
|
+
errorMessage?: string;
|
|
9
|
+
inputClassName?: string;
|
|
10
|
+
inputFormat?: DateInputFormat;
|
|
11
|
+
onClear?: boolean | (() => void);
|
|
12
|
+
value?: string;
|
|
13
|
+
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
|
14
|
+
} & Omit<React.InputHTMLAttributes<HTMLInputElement>, 'value' | 'onChange' | 'type'>;
|
|
15
|
+
declare const InputDate: React.ForwardRefExoticComponent<{
|
|
16
|
+
className?: string;
|
|
17
|
+
endContent?: React.ReactElement;
|
|
18
|
+
disabled?: boolean;
|
|
19
|
+
error?: boolean | string;
|
|
20
|
+
errorMessage?: string;
|
|
21
|
+
inputClassName?: string;
|
|
22
|
+
inputFormat?: DateInputFormat;
|
|
23
|
+
onClear?: boolean | (() => void);
|
|
24
|
+
value?: string;
|
|
25
|
+
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
|
26
|
+
} & Omit<React.InputHTMLAttributes<HTMLInputElement>, "onChange" | "value" | "type"> & React.RefAttributes<HTMLInputElement>>;
|
|
27
|
+
export { InputDate };
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { CalendarBlank } from '@phosphor-icons/react/dist/ssr/CalendarBlank';
|
|
3
|
+
import { WarningCircle } from '@phosphor-icons/react/dist/ssr/WarningCircle';
|
|
4
|
+
import { X } from '@phosphor-icons/react/dist/ssr/X';
|
|
5
|
+
import * as React from 'react';
|
|
6
|
+
import { MaskedInput } from './MaskedInput.js';
|
|
7
|
+
import { cn } from '../../lib/utils.js';
|
|
8
|
+
|
|
9
|
+
const InputDate = React.forwardRef(
|
|
10
|
+
({
|
|
11
|
+
className,
|
|
12
|
+
endContent,
|
|
13
|
+
disabled,
|
|
14
|
+
error,
|
|
15
|
+
errorMessage,
|
|
16
|
+
value: controlledValue,
|
|
17
|
+
onChange: controlledOnChange,
|
|
18
|
+
inputClassName,
|
|
19
|
+
onClear = true,
|
|
20
|
+
inputFormat,
|
|
21
|
+
"aria-invalid": ariaInvalid,
|
|
22
|
+
...props
|
|
23
|
+
}, ref) => {
|
|
24
|
+
const [internalValue, setInternalValue] = React.useState(controlledValue ?? "");
|
|
25
|
+
const [isHovered, setIsHovered] = React.useState(false);
|
|
26
|
+
React.useEffect(() => {
|
|
27
|
+
setInternalValue(controlledValue ?? "");
|
|
28
|
+
}, [controlledValue]);
|
|
29
|
+
const handleChange = (event) => {
|
|
30
|
+
setInternalValue(event.target.value);
|
|
31
|
+
controlledOnChange?.(event);
|
|
32
|
+
};
|
|
33
|
+
const handleMaskedChange = (newValue) => {
|
|
34
|
+
setInternalValue(newValue);
|
|
35
|
+
controlledOnChange?.({
|
|
36
|
+
target: { value: newValue }
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
const handleClear = () => {
|
|
40
|
+
handleChange({ target: { value: "" } });
|
|
41
|
+
if (typeof onClear === "function") onClear();
|
|
42
|
+
};
|
|
43
|
+
const hasValue = Boolean(internalValue && internalValue.trim() !== "");
|
|
44
|
+
const showClearIcon = Boolean(onClear && hasValue && isHovered && !disabled);
|
|
45
|
+
const resolvedErrorMessage = typeof error === "string" ? error : errorMessage;
|
|
46
|
+
const hasError = Boolean(error ?? resolvedErrorMessage);
|
|
47
|
+
const renderCalendarIcon = () => /* @__PURE__ */ jsx(CalendarBlank, { size: 20 });
|
|
48
|
+
const renderErrorIcon = () => {
|
|
49
|
+
if (!hasError) return null;
|
|
50
|
+
return /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
|
|
51
|
+
WarningCircle,
|
|
52
|
+
{
|
|
53
|
+
"data-testid": "exclaim-icon",
|
|
54
|
+
className: cn(
|
|
55
|
+
"text-error-500 transition-opacity duration-150",
|
|
56
|
+
showClearIcon ? "opacity-0" : "opacity-100"
|
|
57
|
+
),
|
|
58
|
+
size: 18
|
|
59
|
+
}
|
|
60
|
+
) });
|
|
61
|
+
};
|
|
62
|
+
const renderClearIcon = () => {
|
|
63
|
+
if (disabled || !onClear) return null;
|
|
64
|
+
return /* @__PURE__ */ jsx(
|
|
65
|
+
"div",
|
|
66
|
+
{
|
|
67
|
+
className: cn(
|
|
68
|
+
"absolute right-3 z-10 flex h-full w-10 items-center justify-end rounded-r-lg",
|
|
69
|
+
endContent || error ? "bg-transparent" : "bg-gradient-to-l from-white from-60% via-white/80 via-80% to-transparent transition-opacity duration-150",
|
|
70
|
+
showClearIcon ? "opacity-100" : "opacity-0"
|
|
71
|
+
),
|
|
72
|
+
children: /* @__PURE__ */ jsx(
|
|
73
|
+
X,
|
|
74
|
+
{
|
|
75
|
+
"data-testid": "clear-button",
|
|
76
|
+
className: cn(
|
|
77
|
+
"h-4 w-4 shrink-0 cursor-pointer transition-opacity duration-150",
|
|
78
|
+
showClearIcon ? "opacity-100 hover:opacity-70" : "opacity-0"
|
|
79
|
+
),
|
|
80
|
+
onClick: handleClear
|
|
81
|
+
}
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
};
|
|
86
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col gap-1", children: [
|
|
87
|
+
/* @__PURE__ */ jsx("div", { className: "flex w-full items-center", children: /* @__PURE__ */ jsxs(
|
|
88
|
+
"label",
|
|
89
|
+
{
|
|
90
|
+
"data-testid": "input-date-mask-control",
|
|
91
|
+
className: cn(
|
|
92
|
+
"relative flex h-12 w-full items-center rounded-lg border border-neutral-200 bg-white px-3 transition-colors focus-within:border-neutral-950",
|
|
93
|
+
disabled && "bg-neutral-50",
|
|
94
|
+
hasError && "border-error-400 focus-within:border-error-700",
|
|
95
|
+
className
|
|
96
|
+
),
|
|
97
|
+
onMouseEnter: () => setIsHovered(true),
|
|
98
|
+
onMouseLeave: () => setIsHovered(false),
|
|
99
|
+
children: [
|
|
100
|
+
renderCalendarIcon(),
|
|
101
|
+
/* @__PURE__ */ jsx(
|
|
102
|
+
MaskedInput,
|
|
103
|
+
{
|
|
104
|
+
ref,
|
|
105
|
+
inputFormat,
|
|
106
|
+
placeholder: inputFormat,
|
|
107
|
+
"aria-invalid": ariaInvalid ?? hasError,
|
|
108
|
+
disabled,
|
|
109
|
+
value: internalValue,
|
|
110
|
+
onChange: handleMaskedChange,
|
|
111
|
+
className: cn(
|
|
112
|
+
"w-full bg-transparent px-2 outline-0 transition-colors placeholder:text-neutral-300 disabled:cursor-not-allowed disabled:opacity-50",
|
|
113
|
+
inputClassName
|
|
114
|
+
),
|
|
115
|
+
...props
|
|
116
|
+
}
|
|
117
|
+
),
|
|
118
|
+
renderErrorIcon(),
|
|
119
|
+
renderClearIcon()
|
|
120
|
+
]
|
|
121
|
+
}
|
|
122
|
+
) }),
|
|
123
|
+
resolvedErrorMessage && /* @__PURE__ */ jsx("span", { className: "text-error-500 text-sm", children: resolvedErrorMessage })
|
|
124
|
+
] });
|
|
125
|
+
}
|
|
126
|
+
);
|
|
127
|
+
InputDate.displayName = "InputDate";
|
|
128
|
+
|
|
129
|
+
export { InputDate };
|
|
130
|
+
//# sourceMappingURL=InputDate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InputDate.js","sources":["../../../src/components/InputDateMask/InputDate.tsx"],"sourcesContent":["import { CalendarBlank } from '@phosphor-icons/react/dist/ssr/CalendarBlank'\nimport { WarningCircle } from '@phosphor-icons/react/dist/ssr/WarningCircle'\nimport { X } from '@phosphor-icons/react/dist/ssr/X'\nimport * as React from 'react'\n\nimport { type DateInputFormat } from './manualDateFormat'\nimport { MaskedInput } from './MaskedInput'\n\nimport { cn } from '@/lib/utils'\n\nexport type InputDateProps = {\n className?: string\n endContent?: React.ReactElement\n disabled?: boolean\n error?: boolean | string\n errorMessage?: string\n inputClassName?: string\n inputFormat?: DateInputFormat\n onClear?: boolean | (() => void)\n value?: string\n onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void\n} & Omit<React.InputHTMLAttributes<HTMLInputElement>, 'value' | 'onChange' | 'type'>\n\nconst InputDate = React.forwardRef<HTMLInputElement, InputDateProps>(\n (\n {\n className,\n endContent,\n disabled,\n error,\n errorMessage,\n value: controlledValue,\n onChange: controlledOnChange,\n inputClassName,\n onClear = true,\n inputFormat,\n 'aria-invalid': ariaInvalid,\n ...props\n },\n ref,\n ) => {\n const [internalValue, setInternalValue] = React.useState(controlledValue ?? '')\n const [isHovered, setIsHovered] = React.useState(false)\n\n React.useEffect(() => {\n setInternalValue(controlledValue ?? '')\n }, [controlledValue])\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setInternalValue(event.target.value)\n controlledOnChange?.(event)\n }\n\n const handleMaskedChange = (newValue: string) => {\n setInternalValue(newValue)\n controlledOnChange?.({\n target: { value: newValue },\n } as React.ChangeEvent<HTMLInputElement>)\n }\n\n const handleClear = () => {\n handleChange({ target: { value: '' } } as React.ChangeEvent<HTMLInputElement>)\n if (typeof onClear === 'function') onClear()\n }\n\n const hasValue = Boolean(internalValue && internalValue.trim() !== '')\n const showClearIcon = Boolean(onClear && hasValue && isHovered && !disabled)\n const resolvedErrorMessage = typeof error === 'string' ? error : errorMessage\n const hasError = Boolean(error ?? resolvedErrorMessage)\n\n const renderCalendarIcon = () => <CalendarBlank size={20} />\n\n const renderErrorIcon = () => {\n if (!hasError) return null\n return (\n <div>\n <WarningCircle\n data-testid=\"exclaim-icon\"\n className={cn(\n 'text-error-500 transition-opacity duration-150',\n showClearIcon ? 'opacity-0' : 'opacity-100',\n )}\n size={18}\n />\n </div>\n )\n }\n\n const renderClearIcon = () => {\n if (disabled || !onClear) return null\n return (\n <div\n className={cn(\n 'absolute right-3 z-10 flex h-full w-10 items-center justify-end rounded-r-lg',\n endContent || error\n ? 'bg-transparent'\n : 'bg-gradient-to-l from-white from-60% via-white/80 via-80% to-transparent transition-opacity duration-150',\n showClearIcon ? 'opacity-100' : 'opacity-0',\n )}\n >\n <X\n data-testid=\"clear-button\"\n className={cn(\n 'h-4 w-4 shrink-0 cursor-pointer transition-opacity duration-150',\n showClearIcon ? 'opacity-100 hover:opacity-70' : 'opacity-0',\n )}\n onClick={handleClear}\n />\n </div>\n )\n }\n\n return (\n <div className=\"flex w-full flex-col gap-1\">\n <div className=\"flex w-full items-center\">\n <label\n data-testid=\"input-date-mask-control\"\n className={cn(\n 'relative flex h-12 w-full items-center rounded-lg border border-neutral-200 bg-white px-3 transition-colors focus-within:border-neutral-950',\n disabled && 'bg-neutral-50',\n hasError && 'border-error-400 focus-within:border-error-700',\n className,\n )}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n >\n {renderCalendarIcon()}\n <MaskedInput\n ref={ref}\n inputFormat={inputFormat}\n placeholder={inputFormat}\n aria-invalid={ariaInvalid ?? hasError}\n disabled={disabled}\n value={internalValue}\n onChange={handleMaskedChange}\n className={cn(\n 'w-full bg-transparent px-2 outline-0 transition-colors placeholder:text-neutral-300 disabled:cursor-not-allowed disabled:opacity-50',\n inputClassName,\n )}\n {...props}\n />\n {renderErrorIcon()}\n {renderClearIcon()}\n </label>\n </div>\n {resolvedErrorMessage && <span className=\"text-error-500 text-sm\">{resolvedErrorMessage}</span>}\n </div>\n )\n },\n)\n\nInputDate.displayName = 'InputDate'\n\nexport { InputDate }\n"],"names":[],"mappings":";;;;;;;;AAuBA,MAAM,YAAY,KAAA,CAAM,UAAA;AAAA,EACtB,CACE;AAAA,IACE,SAAA;AAAA,IACA,UAAA;AAAA,IACA,QAAA;AAAA,IACA,KAAA;AAAA,IACA,YAAA;AAAA,IACA,KAAA,EAAO,eAAA;AAAA,IACP,QAAA,EAAU,kBAAA;AAAA,IACV,cAAA;AAAA,IACA,OAAA,GAAU,IAAA;AAAA,IACV,WAAA;AAAA,IACA,cAAA,EAAgB,WAAA;AAAA,IAChB,GAAG;AAAA,KAEL,GAAA,KACG;AACH,IAAA,MAAM,CAAC,aAAA,EAAe,gBAAgB,IAAI,KAAA,CAAM,QAAA,CAAS,mBAAmB,EAAE,CAAA;AAC9E,IAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAI,KAAA,CAAM,SAAS,KAAK,CAAA;AAEtD,IAAA,KAAA,CAAM,UAAU,MAAM;AACpB,MAAA,gBAAA,CAAiB,mBAAmB,EAAE,CAAA;AAAA,KACxC,EAAG,CAAC,eAAe,CAAC,CAAA;AAEpB,IAAA,MAAM,YAAA,GAAe,CAAC,KAAA,KAA+C;AACnE,MAAA,gBAAA,CAAiB,KAAA,CAAM,OAAO,KAAK,CAAA;AACnC,MAAA,kBAAA,GAAqB,KAAK,CAAA;AAAA,KAC5B;AAEA,IAAA,MAAM,kBAAA,GAAqB,CAAC,QAAA,KAAqB;AAC/C,MAAA,gBAAA,CAAiB,QAAQ,CAAA;AACzB,MAAA,kBAAA,GAAqB;AAAA,QACnB,MAAA,EAAQ,EAAE,KAAA,EAAO,QAAA;AAAS,OACY,CAAA;AAAA,KAC1C;AAEA,IAAA,MAAM,cAAc,MAAM;AACxB,MAAA,YAAA,CAAa,EAAE,MAAA,EAAQ,EAAE,KAAA,EAAO,EAAA,IAA6C,CAAA;AAC7E,MAAA,IAAI,OAAO,OAAA,KAAY,UAAA,EAAY,OAAA,EAAQ;AAAA,KAC7C;AAEA,IAAA,MAAM,WAAW,OAAA,CAAQ,aAAA,IAAiB,aAAA,CAAc,IAAA,OAAW,EAAE,CAAA;AACrE,IAAA,MAAM,gBAAgB,OAAA,CAAQ,OAAA,IAAW,QAAA,IAAY,SAAA,IAAa,CAAC,QAAQ,CAAA;AAC3E,IAAA,MAAM,oBAAA,GAAuB,OAAO,KAAA,KAAU,QAAA,GAAW,KAAA,GAAQ,YAAA;AACjE,IAAA,MAAM,QAAA,GAAW,OAAA,CAAQ,KAAA,IAAS,oBAAoB,CAAA;AAEtD,IAAA,MAAM,kBAAA,GAAqB,sBAAM,GAAA,CAAC,aAAA,EAAA,EAAc,MAAM,EAAA,EAAI,CAAA;AAE1D,IAAA,MAAM,kBAAkB,MAAM;AAC5B,MAAA,IAAI,CAAC,UAAU,OAAO,IAAA;AACtB,MAAA,2BACG,KAAA,EAAA,EACC,QAAA,kBAAA,GAAA;AAAA,QAAC,aAAA;AAAA,QAAA;AAAA,UACC,aAAA,EAAY,cAAA;AAAA,UACZ,SAAA,EAAW,EAAA;AAAA,YACT,gDAAA;AAAA,YACA,gBAAgB,WAAA,GAAc;AAAA,WAChC;AAAA,UACA,IAAA,EAAM;AAAA;AAAA,OACR,EACF,CAAA;AAAA,KAEJ;AAEA,IAAA,MAAM,kBAAkB,MAAM;AAC5B,MAAA,IAAI,QAAA,IAAY,CAAC,OAAA,EAAS,OAAO,IAAA;AACjC,MAAA,uBACE,GAAA;AAAA,QAAC,KAAA;AAAA,QAAA;AAAA,UACC,SAAA,EAAW,EAAA;AAAA,YACT,8EAAA;AAAA,YACA,UAAA,IAAc,QACV,gBAAA,GACA,0GAAA;AAAA,YACJ,gBAAgB,aAAA,GAAgB;AAAA,WAClC;AAAA,UAEA,QAAA,kBAAA,GAAA;AAAA,YAAC,CAAA;AAAA,YAAA;AAAA,cACC,aAAA,EAAY,cAAA;AAAA,cACZ,SAAA,EAAW,EAAA;AAAA,gBACT,iEAAA;AAAA,gBACA,gBAAgB,8BAAA,GAAiC;AAAA,eACnD;AAAA,cACA,OAAA,EAAS;AAAA;AAAA;AACX;AAAA,OACF;AAAA,KAEJ;AAEA,IAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,4BAAA,EACb,QAAA,EAAA;AAAA,sBAAA,GAAA,CAAC,KAAA,EAAA,EAAI,WAAU,0BAAA,EACb,QAAA,kBAAA,IAAA;AAAA,QAAC,OAAA;AAAA,QAAA;AAAA,UACC,aAAA,EAAY,yBAAA;AAAA,UACZ,SAAA,EAAW,EAAA;AAAA,YACT,6IAAA;AAAA,YACA,QAAA,IAAY,eAAA;AAAA,YACZ,QAAA,IAAY,gDAAA;AAAA,YACZ;AAAA,WACF;AAAA,UACA,YAAA,EAAc,MAAM,YAAA,CAAa,IAAI,CAAA;AAAA,UACrC,YAAA,EAAc,MAAM,YAAA,CAAa,KAAK,CAAA;AAAA,UAErC,QAAA,EAAA;AAAA,YAAA,kBAAA,EAAmB;AAAA,4BACpB,GAAA;AAAA,cAAC,WAAA;AAAA,cAAA;AAAA,gBACC,GAAA;AAAA,gBACA,WAAA;AAAA,gBACA,WAAA,EAAa,WAAA;AAAA,gBACb,gBAAc,WAAA,IAAe,QAAA;AAAA,gBAC7B,QAAA;AAAA,gBACA,KAAA,EAAO,aAAA;AAAA,gBACP,QAAA,EAAU,kBAAA;AAAA,gBACV,SAAA,EAAW,EAAA;AAAA,kBACT,qIAAA;AAAA,kBACA;AAAA,iBACF;AAAA,gBACC,GAAG;AAAA;AAAA,aACN;AAAA,YACC,eAAA,EAAgB;AAAA,YAChB,eAAA;AAAgB;AAAA;AAAA,OACnB,EACF,CAAA;AAAA,MACC,oBAAA,oBAAwB,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,0BAA0B,QAAA,EAAA,oBAAA,EAAqB;AAAA,KAAA,EAC1F,CAAA;AAAA;AAGN;AAEA,SAAA,CAAU,WAAA,GAAc,WAAA;;;;"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
export type DateInputFormat = 'DD/MM/YYYY' | 'MM/DD/YYYY' | 'YYYY/MM/DD';
|
|
3
|
+
type Props = {
|
|
4
|
+
value: string;
|
|
5
|
+
onChange: (value: string) => void;
|
|
6
|
+
onBlur?: React.FocusEventHandler<HTMLInputElement>;
|
|
7
|
+
onFocus?: React.FocusEventHandler<HTMLInputElement>;
|
|
8
|
+
onKeyDown?: React.KeyboardEventHandler<HTMLInputElement>;
|
|
9
|
+
placeholder?: string;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
id?: string;
|
|
12
|
+
name?: string;
|
|
13
|
+
className?: string;
|
|
14
|
+
'aria-invalid'?: React.InputHTMLAttributes<HTMLInputElement>['aria-invalid'];
|
|
15
|
+
inputFormat?: DateInputFormat;
|
|
16
|
+
};
|
|
17
|
+
export declare const MaskedInput: React.ForwardRefExoticComponent<Props & React.RefAttributes<HTMLInputElement>>;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
|
+
import React__default from 'react';
|
|
4
|
+
import { normalizeManualInput, parseManualDateString } from './manualDateFormat.js';
|
|
5
|
+
import { cn } from '../../lib/utils.js';
|
|
6
|
+
|
|
7
|
+
const FORMAT_CONFIG = {
|
|
8
|
+
"DD/MM/YYYY": {
|
|
9
|
+
template: "DD/MM/YYYY",
|
|
10
|
+
order: ["day", "month", "year"],
|
|
11
|
+
placeholders: { 0: "D", 1: "D", 3: "M", 4: "M", 6: "Y", 7: "Y", 8: "Y", 9: "Y" },
|
|
12
|
+
separators: [2, 5]
|
|
13
|
+
},
|
|
14
|
+
"MM/DD/YYYY": {
|
|
15
|
+
template: "MM/DD/YYYY",
|
|
16
|
+
order: ["month", "day", "year"],
|
|
17
|
+
placeholders: { 0: "M", 1: "M", 3: "D", 4: "D", 6: "Y", 7: "Y", 8: "Y", 9: "Y" },
|
|
18
|
+
separators: [2, 5]
|
|
19
|
+
},
|
|
20
|
+
"YYYY/MM/DD": {
|
|
21
|
+
template: "YYYY/MM/DD",
|
|
22
|
+
order: ["year", "month", "day"],
|
|
23
|
+
placeholders: { 0: "Y", 1: "Y", 2: "Y", 3: "Y", 5: "M", 6: "M", 8: "D", 9: "D" },
|
|
24
|
+
separators: [4, 7]
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
const getSegmentIndex = (pos, s1, s2) => {
|
|
28
|
+
if (pos < s1) return 0;
|
|
29
|
+
if (pos < s2) return 1;
|
|
30
|
+
return 2;
|
|
31
|
+
};
|
|
32
|
+
const getFirstPosOfSegment = (segmentIndex, separators) => {
|
|
33
|
+
const [sep1, sep2] = separators;
|
|
34
|
+
if (segmentIndex === 0) return 0;
|
|
35
|
+
if (segmentIndex === 1) return sep1 + 1;
|
|
36
|
+
return sep2 + 1;
|
|
37
|
+
};
|
|
38
|
+
const applyFirstDigitAutopad = (newChars, targetPos, digit, segmentType) => {
|
|
39
|
+
const shouldPadDay = segmentType === "day" && Number.parseInt(digit) > 3;
|
|
40
|
+
const shouldPadMonth = segmentType === "month" && Number.parseInt(digit) > 1;
|
|
41
|
+
if (shouldPadDay || shouldPadMonth) {
|
|
42
|
+
newChars[targetPos] = "0";
|
|
43
|
+
newChars[targetPos + 1] = digit;
|
|
44
|
+
return targetPos + 1;
|
|
45
|
+
}
|
|
46
|
+
return targetPos;
|
|
47
|
+
};
|
|
48
|
+
const validateSecondDigit = (newChars, targetPos, segmentType) => {
|
|
49
|
+
const firstPos = targetPos - 1;
|
|
50
|
+
if (segmentType === "day") {
|
|
51
|
+
const day = Number.parseInt(newChars[firstPos] + newChars[targetPos]);
|
|
52
|
+
return newChars[firstPos] === "D" || day <= 31;
|
|
53
|
+
}
|
|
54
|
+
if (segmentType === "month") {
|
|
55
|
+
const month = Number.parseInt(newChars[firstPos] + newChars[targetPos]);
|
|
56
|
+
return newChars[firstPos] === "M" || month <= 12;
|
|
57
|
+
}
|
|
58
|
+
return true;
|
|
59
|
+
};
|
|
60
|
+
const validateYearDigit = (newChars, targetPos, digit, yearFirstPos) => {
|
|
61
|
+
const yearDigitIndex = targetPos - yearFirstPos;
|
|
62
|
+
if (yearDigitIndex === 0) {
|
|
63
|
+
return digit === "1" || digit === "2";
|
|
64
|
+
}
|
|
65
|
+
if (yearDigitIndex === 1) {
|
|
66
|
+
const firstDigit = newChars[yearFirstPos];
|
|
67
|
+
if (firstDigit === "1") return digit === "9";
|
|
68
|
+
if (firstDigit === "2") return digit === "0";
|
|
69
|
+
}
|
|
70
|
+
if (yearDigitIndex === 2) {
|
|
71
|
+
const twoDigits = newChars[yearFirstPos] + newChars[yearFirstPos + 1];
|
|
72
|
+
if (twoDigits === "20") return Number.parseInt(digit) <= 5;
|
|
73
|
+
}
|
|
74
|
+
if (yearDigitIndex === 3) {
|
|
75
|
+
const yearStr = newChars.slice(yearFirstPos, yearFirstPos + 3).join("") + digit;
|
|
76
|
+
const year = Number.parseInt(yearStr, 10);
|
|
77
|
+
return !Number.isNaN(year) && year >= 1922 && year <= 2300;
|
|
78
|
+
}
|
|
79
|
+
return true;
|
|
80
|
+
};
|
|
81
|
+
const MaskedInput = React__default.forwardRef(
|
|
82
|
+
({ value, onChange, placeholder, inputFormat = "DD/MM/YYYY", onBlur, onFocus, onKeyDown, className, ...rest }, ref) => {
|
|
83
|
+
const inputRef = React__default.useRef(null);
|
|
84
|
+
React__default.useImperativeHandle(ref, () => inputRef.current);
|
|
85
|
+
const config = FORMAT_CONFIG[inputFormat];
|
|
86
|
+
const [sep1, sep2] = config.separators;
|
|
87
|
+
const maxPos = 9;
|
|
88
|
+
const getNextPos = (pos) => {
|
|
89
|
+
const next = pos + 1;
|
|
90
|
+
if (next === sep1 || next === sep2) return next + 1;
|
|
91
|
+
return Math.min(next, maxPos + 1);
|
|
92
|
+
};
|
|
93
|
+
const handleDelete = (pos, isDelete) => {
|
|
94
|
+
const input = inputRef.current;
|
|
95
|
+
if (!input) return;
|
|
96
|
+
let targetPos = isDelete ? pos : pos - 1;
|
|
97
|
+
if (targetPos === sep1 || targetPos === sep2) {
|
|
98
|
+
targetPos = isDelete ? targetPos + 1 : targetPos - 1;
|
|
99
|
+
}
|
|
100
|
+
if (targetPos < 0 || targetPos > maxPos) return;
|
|
101
|
+
const chars = (value || config.template).split("");
|
|
102
|
+
chars[targetPos] = config.placeholders[targetPos] ?? chars[targetPos];
|
|
103
|
+
const newValue = chars.join("");
|
|
104
|
+
onChange(newValue === config.template ? "" : newValue);
|
|
105
|
+
requestAnimationFrame(() => {
|
|
106
|
+
input.setSelectionRange(targetPos, targetPos);
|
|
107
|
+
});
|
|
108
|
+
};
|
|
109
|
+
const handleDigit = (pos, digit) => {
|
|
110
|
+
const input = inputRef.current;
|
|
111
|
+
if (!input) return;
|
|
112
|
+
let targetPos = pos;
|
|
113
|
+
if (targetPos === sep1 || targetPos === sep2) targetPos++;
|
|
114
|
+
if (targetPos > maxPos) return;
|
|
115
|
+
const chars = (value || config.template).split("");
|
|
116
|
+
const newChars = [...chars];
|
|
117
|
+
newChars[targetPos] = digit;
|
|
118
|
+
const segmentIndex = getSegmentIndex(targetPos, sep1, sep2);
|
|
119
|
+
const segmentType = config.order[segmentIndex];
|
|
120
|
+
const firstPosOfSegment = getFirstPosOfSegment(segmentIndex, config.separators);
|
|
121
|
+
const isFirstDigit = targetPos === firstPosOfSegment;
|
|
122
|
+
const isSecondDigit = targetPos === firstPosOfSegment + 1;
|
|
123
|
+
if (isFirstDigit) {
|
|
124
|
+
targetPos = applyFirstDigitAutopad(newChars, targetPos, digit, segmentType);
|
|
125
|
+
}
|
|
126
|
+
if (isSecondDigit && !validateSecondDigit(newChars, targetPos, segmentType)) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (segmentType === "year" && !validateYearDigit(newChars, targetPos, digit, firstPosOfSegment)) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
newChars[targetPos] = digit;
|
|
133
|
+
const nextPos = getNextPos(targetPos);
|
|
134
|
+
const result = newChars.join("");
|
|
135
|
+
onChange(result === config.template ? "" : result);
|
|
136
|
+
requestAnimationFrame(() => {
|
|
137
|
+
input.setSelectionRange(nextPos, nextPos);
|
|
138
|
+
});
|
|
139
|
+
};
|
|
140
|
+
const handleBlur = (event) => {
|
|
141
|
+
const trimmed = (value || "").trim();
|
|
142
|
+
if (trimmed) {
|
|
143
|
+
const normalized = normalizeManualInput(trimmed, inputFormat);
|
|
144
|
+
const parsed = parseManualDateString(normalized, inputFormat);
|
|
145
|
+
if (!parsed) {
|
|
146
|
+
onChange("");
|
|
147
|
+
} else if (normalized !== value) {
|
|
148
|
+
onChange(normalized);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
onBlur?.(event);
|
|
152
|
+
};
|
|
153
|
+
const handleKeyDown = (event) => {
|
|
154
|
+
onKeyDown?.(event);
|
|
155
|
+
const input = inputRef.current;
|
|
156
|
+
if (!input) return;
|
|
157
|
+
const pos = input.selectionStart;
|
|
158
|
+
if (pos === null) return;
|
|
159
|
+
if (event.key === "Tab" || event.key === "ArrowLeft" || event.key === "ArrowRight" || event.key === "Enter" || event.key === "ArrowUp" || event.key === "ArrowDown" || event.ctrlKey || event.metaKey) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
event.preventDefault();
|
|
163
|
+
if (event.key === "Backspace" || event.key === "Delete") {
|
|
164
|
+
handleDelete(pos, event.key === "Delete");
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (!/^\d$/.test(event.key)) return;
|
|
168
|
+
handleDigit(pos, event.key);
|
|
169
|
+
};
|
|
170
|
+
const handleClick = () => {
|
|
171
|
+
const input = inputRef.current;
|
|
172
|
+
if (!input) return;
|
|
173
|
+
const pos = input.selectionStart ?? 0;
|
|
174
|
+
const getTargetPos = (position) => {
|
|
175
|
+
if (position === sep1) return position - 1;
|
|
176
|
+
if (position === sep2) return position - 1;
|
|
177
|
+
return position;
|
|
178
|
+
};
|
|
179
|
+
requestAnimationFrame(() => {
|
|
180
|
+
input.setSelectionRange(getTargetPos(pos), getTargetPos(pos));
|
|
181
|
+
});
|
|
182
|
+
};
|
|
183
|
+
const renderOverlay = () => {
|
|
184
|
+
if (!value) return null;
|
|
185
|
+
const display = value.padEnd(10, " ");
|
|
186
|
+
const template = config.template;
|
|
187
|
+
return /* @__PURE__ */ jsx(
|
|
188
|
+
"div",
|
|
189
|
+
{
|
|
190
|
+
"aria-hidden": "true",
|
|
191
|
+
className: "pointer-events-none absolute inset-0 flex items-center px-2 text-base select-none",
|
|
192
|
+
children: display.split("").map((char, index) => {
|
|
193
|
+
const isPlaceholderChar = template[index] !== "/" && char === template[index];
|
|
194
|
+
const stableKey = `${template[index]}-${index}`;
|
|
195
|
+
return /* @__PURE__ */ jsx("span", { className: isPlaceholderChar ? "text-neutral-300" : "text-neutral-900", children: char }, stableKey);
|
|
196
|
+
})
|
|
197
|
+
}
|
|
198
|
+
);
|
|
199
|
+
};
|
|
200
|
+
return /* @__PURE__ */ jsxs("div", { className: "relative flex w-full items-center", children: [
|
|
201
|
+
/* @__PURE__ */ jsx(
|
|
202
|
+
"input",
|
|
203
|
+
{
|
|
204
|
+
ref: inputRef,
|
|
205
|
+
type: "text",
|
|
206
|
+
autoComplete: "off",
|
|
207
|
+
value: value || "",
|
|
208
|
+
placeholder: placeholder ?? inputFormat,
|
|
209
|
+
onKeyDown: handleKeyDown,
|
|
210
|
+
onClick: handleClick,
|
|
211
|
+
onChange: () => {
|
|
212
|
+
},
|
|
213
|
+
onBlur: handleBlur,
|
|
214
|
+
onFocus,
|
|
215
|
+
size: 1,
|
|
216
|
+
className: cn(className, value ? "text-transparent caret-neutral-900" : ""),
|
|
217
|
+
...rest
|
|
218
|
+
}
|
|
219
|
+
),
|
|
220
|
+
renderOverlay()
|
|
221
|
+
] });
|
|
222
|
+
}
|
|
223
|
+
);
|
|
224
|
+
MaskedInput.displayName = "MaskedInput";
|
|
225
|
+
|
|
226
|
+
export { MaskedInput };
|
|
227
|
+
//# sourceMappingURL=MaskedInput.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MaskedInput.js","sources":["../../../src/components/InputDateMask/MaskedInput.tsx"],"sourcesContent":["'use client'\n\nimport React from 'react'\n\nimport { normalizeManualInput, parseManualDateString } from './manualDateFormat'\n\nimport { cn } from '@/lib/utils'\n\nexport type DateInputFormat = 'DD/MM/YYYY' | 'MM/DD/YYYY' | 'YYYY/MM/DD'\n\ntype Props = {\n value: string\n onChange: (value: string) => void\n onBlur?: React.FocusEventHandler<HTMLInputElement>\n onFocus?: React.FocusEventHandler<HTMLInputElement>\n onKeyDown?: React.KeyboardEventHandler<HTMLInputElement>\n placeholder?: string\n disabled?: boolean\n id?: string\n name?: string\n className?: string\n 'aria-invalid'?: React.InputHTMLAttributes<HTMLInputElement>['aria-invalid']\n inputFormat?: DateInputFormat\n}\n\ntype SegmentType = 'day' | 'month' | 'year'\n\ntype FormatConfig = {\n template: string\n order: [SegmentType, SegmentType, SegmentType]\n placeholders: Record<number, string>\n separators: [number, number]\n}\n\nconst FORMAT_CONFIG: Record<DateInputFormat, FormatConfig> = {\n 'DD/MM/YYYY': {\n template: 'DD/MM/YYYY',\n order: ['day', 'month', 'year'],\n placeholders: { 0: 'D', 1: 'D', 3: 'M', 4: 'M', 6: 'Y', 7: 'Y', 8: 'Y', 9: 'Y' },\n separators: [2, 5],\n },\n 'MM/DD/YYYY': {\n template: 'MM/DD/YYYY',\n order: ['month', 'day', 'year'],\n placeholders: { 0: 'M', 1: 'M', 3: 'D', 4: 'D', 6: 'Y', 7: 'Y', 8: 'Y', 9: 'Y' },\n separators: [2, 5],\n },\n 'YYYY/MM/DD': {\n template: 'YYYY/MM/DD',\n order: ['year', 'month', 'day'],\n placeholders: { 0: 'Y', 1: 'Y', 2: 'Y', 3: 'Y', 5: 'M', 6: 'M', 8: 'D', 9: 'D' },\n separators: [4, 7],\n },\n}\n\nconst getSegmentIndex = (pos: number, s1: number, s2: number): number => {\n if (pos < s1) return 0\n if (pos < s2) return 1\n return 2\n}\n\nconst getFirstPosOfSegment = (segmentIndex: number, separators: [number, number]): number => {\n const [sep1, sep2] = separators\n if (segmentIndex === 0) return 0\n if (segmentIndex === 1) return sep1 + 1\n return sep2 + 1\n}\n\nconst applyFirstDigitAutopad = (\n newChars: Array<string>,\n targetPos: number,\n digit: string,\n segmentType: SegmentType,\n): number => {\n const shouldPadDay = segmentType === 'day' && Number.parseInt(digit) > 3\n const shouldPadMonth = segmentType === 'month' && Number.parseInt(digit) > 1\n\n if (shouldPadDay || shouldPadMonth) {\n newChars[targetPos] = '0'\n newChars[targetPos + 1] = digit\n return targetPos + 1\n }\n return targetPos\n}\n\nconst validateSecondDigit = (newChars: Array<string>, targetPos: number, segmentType: SegmentType): boolean => {\n const firstPos = targetPos - 1\n if (segmentType === 'day') {\n const day = Number.parseInt(newChars[firstPos] + newChars[targetPos])\n return newChars[firstPos] === 'D' || day <= 31\n }\n if (segmentType === 'month') {\n const month = Number.parseInt(newChars[firstPos] + newChars[targetPos])\n return newChars[firstPos] === 'M' || month <= 12\n }\n return true\n}\n\nconst validateYearDigit = (\n newChars: Array<string>,\n targetPos: number,\n digit: string,\n yearFirstPos: number,\n): boolean => {\n const yearDigitIndex = targetPos - yearFirstPos\n\n if (yearDigitIndex === 0) {\n return digit === '1' || digit === '2'\n }\n\n if (yearDigitIndex === 1) {\n const firstDigit = newChars[yearFirstPos]\n if (firstDigit === '1') return digit === '9'\n if (firstDigit === '2') return digit === '0'\n }\n\n if (yearDigitIndex === 2) {\n const twoDigits = newChars[yearFirstPos] + newChars[yearFirstPos + 1]\n if (twoDigits === '20') return Number.parseInt(digit) <= 5\n }\n\n if (yearDigitIndex === 3) {\n const yearStr = newChars.slice(yearFirstPos, yearFirstPos + 3).join('') + digit\n const year = Number.parseInt(yearStr, 10)\n return !Number.isNaN(year) && year >= 1922 && year <= 2300\n }\n\n return true\n}\n\nexport const MaskedInput = React.forwardRef<HTMLInputElement, Props>(\n (\n { value, onChange, placeholder, inputFormat = 'DD/MM/YYYY', onBlur, onFocus, onKeyDown, className, ...rest },\n ref,\n ) => {\n const inputRef = React.useRef<HTMLInputElement>(null)\n React.useImperativeHandle(ref, () => inputRef.current!)\n\n const config = FORMAT_CONFIG[inputFormat]\n const [sep1, sep2] = config.separators\n\n const maxPos = 9\n\n const getNextPos = (pos: number): number => {\n const next = pos + 1\n if (next === sep1 || next === sep2) return next + 1\n return Math.min(next, maxPos + 1)\n }\n\n const handleDelete = (pos: number, isDelete: boolean) => {\n const input = inputRef.current\n if (!input) return\n\n let targetPos = isDelete ? pos : pos - 1\n if (targetPos === sep1 || targetPos === sep2) {\n targetPos = isDelete ? targetPos + 1 : targetPos - 1\n }\n if (targetPos < 0 || targetPos > maxPos) return\n\n const chars = (value || config.template).split('')\n chars[targetPos] = config.placeholders[targetPos] ?? chars[targetPos]\n\n const newValue = chars.join('')\n onChange(newValue === config.template ? '' : newValue)\n\n requestAnimationFrame(() => {\n input.setSelectionRange(targetPos, targetPos)\n })\n }\n\n const handleDigit = (pos: number, digit: string) => {\n const input = inputRef.current\n if (!input) return\n\n let targetPos = pos\n if (targetPos === sep1 || targetPos === sep2) targetPos++\n if (targetPos > maxPos) return\n\n const chars = (value || config.template).split('')\n const newChars = [...chars]\n newChars[targetPos] = digit\n\n const segmentIndex = getSegmentIndex(targetPos, sep1, sep2)\n const segmentType = config.order[segmentIndex]\n const firstPosOfSegment = getFirstPosOfSegment(segmentIndex, config.separators)\n const isFirstDigit = targetPos === firstPosOfSegment\n const isSecondDigit = targetPos === firstPosOfSegment + 1\n\n if (isFirstDigit) {\n targetPos = applyFirstDigitAutopad(newChars, targetPos, digit, segmentType)\n }\n\n if (isSecondDigit && !validateSecondDigit(newChars, targetPos, segmentType)) {\n return\n }\n\n if (segmentType === 'year' && !validateYearDigit(newChars, targetPos, digit, firstPosOfSegment)) {\n return\n }\n\n newChars[targetPos] = digit\n const nextPos = getNextPos(targetPos)\n const result = newChars.join('')\n onChange(result === config.template ? '' : result)\n\n requestAnimationFrame(() => {\n input.setSelectionRange(nextPos, nextPos)\n })\n }\n\n const handleBlur = (event: React.FocusEvent<HTMLInputElement>) => {\n const trimmed = (value || '').trim()\n if (trimmed) {\n const normalized = normalizeManualInput(trimmed, inputFormat)\n const parsed = parseManualDateString(normalized, inputFormat)\n if (!parsed) {\n onChange('')\n } else if (normalized !== value) {\n onChange(normalized)\n }\n }\n onBlur?.(event)\n }\n\n const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {\n onKeyDown?.(event)\n\n const input = inputRef.current\n if (!input) return\n\n const pos = input.selectionStart\n if (pos === null) return\n\n if (\n event.key === 'Tab' ||\n event.key === 'ArrowLeft' ||\n event.key === 'ArrowRight' ||\n event.key === 'Enter' ||\n event.key === 'ArrowUp' ||\n event.key === 'ArrowDown' ||\n event.ctrlKey ||\n event.metaKey\n ) {\n return\n }\n\n event.preventDefault()\n\n if (event.key === 'Backspace' || event.key === 'Delete') {\n handleDelete(pos, event.key === 'Delete')\n return\n }\n\n if (!/^\\d$/.test(event.key)) return\n handleDigit(pos, event.key)\n }\n\n const handleClick = () => {\n const input = inputRef.current\n if (!input) return\n\n const pos = input.selectionStart ?? 0\n\n const getTargetPos = (position: number): number => {\n if (position === sep1) return position - 1\n if (position === sep2) return position - 1\n return position\n }\n\n requestAnimationFrame(() => {\n input.setSelectionRange(getTargetPos(pos), getTargetPos(pos))\n })\n }\n const renderOverlay = () => {\n if (!value) return null\n\n const display = value.padEnd(10, ' ')\n const template = config.template\n\n return (\n <div\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute inset-0 flex items-center px-2 text-base select-none\"\n >\n {display.split('').map((char, index) => {\n const isPlaceholderChar = template[index] !== '/' && char === template[index]\n\n const stableKey = `${template[index]}-${index}`\n return (\n <span key={stableKey} className={isPlaceholderChar ? 'text-neutral-300' : 'text-neutral-900'}>\n {char}\n </span>\n )\n })}\n </div>\n )\n }\n\n return (\n <div className=\"relative flex w-full items-center\">\n <input\n ref={inputRef}\n type=\"text\"\n autoComplete=\"off\"\n value={value || ''}\n placeholder={placeholder ?? inputFormat}\n onKeyDown={handleKeyDown}\n onClick={handleClick}\n onChange={() => {}}\n onBlur={handleBlur}\n onFocus={onFocus}\n size={1}\n className={cn(className, value ? 'text-transparent caret-neutral-900' : '')}\n {...rest}\n />\n {renderOverlay()}\n </div>\n )\n },\n)\n\nMaskedInput.displayName = 'MaskedInput'\n"],"names":[],"mappings":";;;;;;AAkCA;AAA6D;AAC7C;AACF;AACoB;AACiD;AAC9D;AACnB;AACc;AACF;AACoB;AACiD;AAC9D;AACnB;AACc;AACF;AACoB;AACiD;AAC9D;AAErB;AAEA;AACE;AACA;AACA;AACF;AAEA;AACE;AACA;AACA;AACA;AACF;AAEA;AAME;AACA;AAEA;AACE;AACA;AACA;AAAmB;AAErB;AACF;AAEA;AACE;AACA;AACE;AACA;AAA4C;AAE9C;AACE;AACA;AAA8C;AAEhD;AACF;AAEA;AAME;AAEA;AACE;AAAkC;AAGpC;AACE;AACA;AACA;AAAyC;AAG3C;AACE;AACA;AAAyD;AAG3D;AACE;AACA;AACA;AAAsD;AAGxD;AACF;AAEO;AAA0B;AAK7B;AACA;AAEA;AACA;AAEA;AAEA;AACE;AACA;AACA;AAAgC;AAGlC;AACE;AACA;AAEA;AACA;AACE;AAAmD;AAErD;AAEA;AACA;AAEA;AACA;AAEA;AACE;AAA4C;AAC7C;AAGH;AACE;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACE;AAA0E;AAG5E;AACE;AAAA;AAGF;AACE;AAAA;AAGF;AACA;AACA;AACA;AAEA;AACE;AAAwC;AACzC;AAGH;AACE;AACA;AACE;AACA;AACA;AACE;AAAW;AAEX;AAAmB;AACrB;AAEF;AAAc;AAGhB;AACE;AAEA;AACA;AAEA;AACA;AAEA;AAUE;AAAA;AAGF;AAEA;AACE;AACA;AAAA;AAGF;AACA;AAA0B;AAG5B;AACE;AACA;AAEA;AAEA;AACE;AACA;AACA;AAAO;AAGT;AACE;AAA4D;AAC7D;AAEH;AACE;AAEA;AACA;AAEA;AACE;AAAC;AAAA;AACa;AACF;AAGR;AAEA;AACA;AAGE;AAEH;AAAA;AACH;AAIJ;AAEI;AAAA;AAAC;AAAA;AACM;AACA;AACQ;AACG;AACY;AACjB;AACF;AACO;AAAC;AACT;AACR;AACM;AACoE;AACtE;AAAA;AACN;AACe;AACjB;AAGN;AAEA;;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './InputDate';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type DateInputFormat = 'DD/MM/YYYY' | 'MM/DD/YYYY' | 'YYYY/MM/DD';
|
|
2
|
+
type DateSegment = 'day' | 'month' | 'year';
|
|
3
|
+
type FormatRow = {
|
|
4
|
+
dateFns: string;
|
|
5
|
+
segmentOrder: [DateSegment, DateSegment, DateSegment];
|
|
6
|
+
};
|
|
7
|
+
export declare const DATE_INPUT_FORMATS: Record<DateInputFormat, FormatRow>;
|
|
8
|
+
export declare const getDateInputDateFns: (format: DateInputFormat) => string;
|
|
9
|
+
export declare function parseManualDateString(raw: string, format: DateInputFormat): Date | undefined;
|
|
10
|
+
export declare function normalizeManualInput(text: string, format: DateInputFormat): string;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { parseISO, isValid } from 'date-fns';
|
|
2
|
+
|
|
3
|
+
const DATE_INPUT_FORMATS = {
|
|
4
|
+
"DD/MM/YYYY": {
|
|
5
|
+
dateFns: "dd/MM/yyyy",
|
|
6
|
+
segmentOrder: ["day", "month", "year"]
|
|
7
|
+
},
|
|
8
|
+
"MM/DD/YYYY": {
|
|
9
|
+
dateFns: "MM/dd/yyyy",
|
|
10
|
+
segmentOrder: ["month", "day", "year"]
|
|
11
|
+
},
|
|
12
|
+
"YYYY/MM/DD": {
|
|
13
|
+
dateFns: "yyyy/MM/dd",
|
|
14
|
+
segmentOrder: ["year", "month", "day"]
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
const getDateInputDateFns = (format) => DATE_INPUT_FORMATS[format].dateFns;
|
|
18
|
+
function parseManualDateString(raw, format) {
|
|
19
|
+
const trimmed = raw.trim();
|
|
20
|
+
if (!trimmed) return void 0;
|
|
21
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(trimmed)) {
|
|
22
|
+
const parsed = parseISO(trimmed);
|
|
23
|
+
return isValid(parsed) ? parsed : void 0;
|
|
24
|
+
}
|
|
25
|
+
const parts = trimmed.split("/");
|
|
26
|
+
if (parts.length !== 3) return void 0;
|
|
27
|
+
const { segmentOrder } = DATE_INPUT_FORMATS[format];
|
|
28
|
+
const dayIdx = segmentOrder.indexOf("day");
|
|
29
|
+
const monthIdx = segmentOrder.indexOf("month");
|
|
30
|
+
const yearIdx = segmentOrder.indexOf("year");
|
|
31
|
+
if (dayIdx === -1 || monthIdx === -1 || yearIdx === -1) return void 0;
|
|
32
|
+
const day = Number.parseInt(parts[dayIdx], 10);
|
|
33
|
+
const month = Number.parseInt(parts[monthIdx], 10);
|
|
34
|
+
const year = Number.parseInt(parts[yearIdx], 10);
|
|
35
|
+
if (Number.isNaN(day) || Number.isNaN(month) || Number.isNaN(year)) return void 0;
|
|
36
|
+
const date = new Date(year, month - 1, day);
|
|
37
|
+
if (date.getFullYear() !== year || date.getMonth() !== month - 1 || date.getDate() !== day) {
|
|
38
|
+
return void 0;
|
|
39
|
+
}
|
|
40
|
+
return date;
|
|
41
|
+
}
|
|
42
|
+
function normalizeManualInput(text, format) {
|
|
43
|
+
const parts = text.split("/");
|
|
44
|
+
if (parts.length !== 3) return "";
|
|
45
|
+
const { segmentOrder } = DATE_INPUT_FORMATS[format];
|
|
46
|
+
const parsed = { day: null, month: null, year: null };
|
|
47
|
+
parts.forEach((part, index) => {
|
|
48
|
+
const type = segmentOrder[index];
|
|
49
|
+
const isYear = type === "year";
|
|
50
|
+
const digits = part.replaceAll(/[DMY]/g, "").trim();
|
|
51
|
+
if (!digits) return;
|
|
52
|
+
if (isYear && digits.length < 4) return;
|
|
53
|
+
parsed[type] = digits.padStart(isYear ? 4 : 2, "0");
|
|
54
|
+
});
|
|
55
|
+
if (parsed.day == null || parsed.month == null || parsed.year == null) return "";
|
|
56
|
+
return segmentOrder.map((type) => parsed[type]).join("/");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export { DATE_INPUT_FORMATS, getDateInputDateFns, normalizeManualInput, parseManualDateString };
|
|
60
|
+
//# sourceMappingURL=manualDateFormat.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manualDateFormat.js","sources":["../../../src/components/InputDateMask/manualDateFormat.ts"],"sourcesContent":["import { isValid, parseISO } from 'date-fns'\n\nexport type DateInputFormat = 'DD/MM/YYYY' | 'MM/DD/YYYY' | 'YYYY/MM/DD'\n\ntype DateSegment = 'day' | 'month' | 'year'\n\ntype FormatRow = {\n dateFns: string\n segmentOrder: [DateSegment, DateSegment, DateSegment]\n}\n\nexport const DATE_INPUT_FORMATS: Record<DateInputFormat, FormatRow> = {\n 'DD/MM/YYYY': {\n dateFns: 'dd/MM/yyyy',\n segmentOrder: ['day', 'month', 'year'],\n },\n 'MM/DD/YYYY': {\n dateFns: 'MM/dd/yyyy',\n segmentOrder: ['month', 'day', 'year'],\n },\n 'YYYY/MM/DD': {\n dateFns: 'yyyy/MM/dd',\n segmentOrder: ['year', 'month', 'day'],\n },\n}\n\nexport const getDateInputDateFns = (format: DateInputFormat): string => DATE_INPUT_FORMATS[format].dateFns\n\nexport function parseManualDateString(raw: string, format: DateInputFormat): Date | undefined {\n const trimmed = raw.trim()\n if (!trimmed) return undefined\n\n if (/^\\d{4}-\\d{2}-\\d{2}$/.test(trimmed)) {\n const parsed = parseISO(trimmed)\n return isValid(parsed) ? parsed : undefined\n }\n\n const parts = trimmed.split('/')\n if (parts.length !== 3) return undefined\n\n const { segmentOrder } = DATE_INPUT_FORMATS[format]\n const dayIdx = segmentOrder.indexOf('day')\n const monthIdx = segmentOrder.indexOf('month')\n const yearIdx = segmentOrder.indexOf('year')\n if (dayIdx === -1 || monthIdx === -1 || yearIdx === -1) return undefined\n\n const day = Number.parseInt(parts[dayIdx], 10)\n const month = Number.parseInt(parts[monthIdx], 10)\n const year = Number.parseInt(parts[yearIdx], 10)\n if (Number.isNaN(day) || Number.isNaN(month) || Number.isNaN(year)) return undefined\n\n const date = new Date(year, month - 1, day)\n if (date.getFullYear() !== year || date.getMonth() !== month - 1 || date.getDate() !== day) {\n return undefined\n }\n return date\n}\n\nexport function normalizeManualInput(text: string, format: DateInputFormat): string {\n const parts = text.split('/')\n if (parts.length !== 3) return ''\n\n const { segmentOrder } = DATE_INPUT_FORMATS[format]\n const parsed: Record<DateSegment, string | null> = { day: null, month: null, year: null }\n\n parts.forEach((part, index) => {\n const type = segmentOrder[index]\n const isYear = type === 'year'\n const digits = part.replaceAll(/[DMY]/g, '').trim()\n if (!digits) return\n if (isYear && digits.length < 4) return\n parsed[type] = digits.padStart(isYear ? 4 : 2, '0')\n })\n\n if (parsed.day == null || parsed.month == null || parsed.year == null) return ''\n\n return segmentOrder.map((type) => parsed[type]).join('/')\n}\n"],"names":[],"mappings":";;AAWO,MAAM,kBAAA,GAAyD;AAAA,EACpE,YAAA,EAAc;AAAA,IACZ,OAAA,EAAS,YAAA;AAAA,IACT,YAAA,EAAc,CAAC,KAAA,EAAO,OAAA,EAAS,MAAM;AAAA,GACvC;AAAA,EACA,YAAA,EAAc;AAAA,IACZ,OAAA,EAAS,YAAA;AAAA,IACT,YAAA,EAAc,CAAC,OAAA,EAAS,KAAA,EAAO,MAAM;AAAA,GACvC;AAAA,EACA,YAAA,EAAc;AAAA,IACZ,OAAA,EAAS,YAAA;AAAA,IACT,YAAA,EAAc,CAAC,MAAA,EAAQ,OAAA,EAAS,KAAK;AAAA;AAEzC;AAEO,MAAM,mBAAA,GAAsB,CAAC,MAAA,KAAoC,kBAAA,CAAmB,MAAM,CAAA,CAAE;AAE5F,SAAS,qBAAA,CAAsB,KAAa,MAAA,EAA2C;AAC5F,EAAA,MAAM,OAAA,GAAU,IAAI,IAAA,EAAK;AACzB,EAAA,IAAI,CAAC,SAAS,OAAO,MAAA;AAErB,EAAA,IAAI,qBAAA,CAAsB,IAAA,CAAK,OAAO,CAAA,EAAG;AACvC,IAAA,MAAM,MAAA,GAAS,SAAS,OAAO,CAAA;AAC/B,IAAA,OAAO,OAAA,CAAQ,MAAM,CAAA,GAAI,MAAA,GAAS,MAAA;AAAA;AAGpC,EAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,KAAA,CAAM,GAAG,CAAA;AAC/B,EAAA,IAAI,KAAA,CAAM,MAAA,KAAW,CAAA,EAAG,OAAO,MAAA;AAE/B,EAAA,MAAM,EAAE,YAAA,EAAa,GAAI,kBAAA,CAAmB,MAAM,CAAA;AAClD,EAAA,MAAM,MAAA,GAAS,YAAA,CAAa,OAAA,CAAQ,KAAK,CAAA;AACzC,EAAA,MAAM,QAAA,GAAW,YAAA,CAAa,OAAA,CAAQ,OAAO,CAAA;AAC7C,EAAA,MAAM,OAAA,GAAU,YAAA,CAAa,OAAA,CAAQ,MAAM,CAAA;AAC3C,EAAA,IAAI,WAAW,EAAA,IAAM,QAAA,KAAa,EAAA,IAAM,OAAA,KAAY,IAAI,OAAO,MAAA;AAE/D,EAAA,MAAM,MAAM,MAAA,CAAO,QAAA,CAAS,KAAA,CAAM,MAAM,GAAG,EAAE,CAAA;AAC7C,EAAA,MAAM,QAAQ,MAAA,CAAO,QAAA,CAAS,KAAA,CAAM,QAAQ,GAAG,EAAE,CAAA;AACjD,EAAA,MAAM,OAAO,MAAA,CAAO,QAAA,CAAS,KAAA,CAAM,OAAO,GAAG,EAAE,CAAA;AAC/C,EAAA,IAAI,MAAA,CAAO,KAAA,CAAM,GAAG,CAAA,IAAK,MAAA,CAAO,KAAA,CAAM,KAAK,CAAA,IAAK,MAAA,CAAO,KAAA,CAAM,IAAI,CAAA,EAAG,OAAO,MAAA;AAE3E,EAAA,MAAM,OAAO,IAAI,IAAA,CAAK,IAAA,EAAM,KAAA,GAAQ,GAAG,GAAG,CAAA;AAC1C,EAAA,IAAI,IAAA,CAAK,WAAA,EAAY,KAAM,IAAA,IAAQ,IAAA,CAAK,QAAA,EAAS,KAAM,KAAA,GAAQ,CAAA,IAAK,IAAA,CAAK,OAAA,EAAQ,KAAM,GAAA,EAAK;AAC1F,IAAA,OAAO,MAAA;AAAA;AAET,EAAA,OAAO,IAAA;AACT;AAEO,SAAS,oBAAA,CAAqB,MAAc,MAAA,EAAiC;AAClF,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA;AAC5B,EAAA,IAAI,KAAA,CAAM,MAAA,KAAW,CAAA,EAAG,OAAO,EAAA;AAE/B,EAAA,MAAM,EAAE,YAAA,EAAa,GAAI,kBAAA,CAAmB,MAAM,CAAA;AAClD,EAAA,MAAM,SAA6C,EAAE,GAAA,EAAK,MAAM,KAAA,EAAO,IAAA,EAAM,MAAM,IAAA,EAAK;AAExF,EAAA,KAAA,CAAM,OAAA,CAAQ,CAAC,IAAA,EAAM,KAAA,KAAU;AAC7B,IAAA,MAAM,IAAA,GAAO,aAAa,KAAK,CAAA;AAC/B,IAAA,MAAM,SAAS,IAAA,KAAS,MAAA;AACxB,IAAA,MAAM,SAAS,IAAA,CAAK,UAAA,CAAW,QAAA,EAAU,EAAE,EAAE,IAAA,EAAK;AAClD,IAAA,IAAI,CAAC,MAAA,EAAQ;AACb,IAAA,IAAI,MAAA,IAAU,MAAA,CAAO,MAAA,GAAS,CAAA,EAAG;AACjC,IAAA,MAAA,CAAO,IAAI,CAAA,GAAI,MAAA,CAAO,SAAS,MAAA,GAAS,CAAA,GAAI,GAAG,GAAG,CAAA;AAAA,GACnD,CAAA;AAED,EAAA,IAAI,MAAA,CAAO,OAAO,IAAA,IAAQ,MAAA,CAAO,SAAS,IAAA,IAAQ,MAAA,CAAO,IAAA,IAAQ,IAAA,EAAM,OAAO,EAAA;AAE9E,EAAA,OAAO,YAAA,CAAa,IAAI,CAAC,IAAA,KAAS,OAAO,IAAI,CAAC,CAAA,CAAE,IAAA,CAAK,GAAG,CAAA;AAC1D;;;;"}
|