periplo-ui 4.4.2 → 4.5.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.
@@ -1,27 +1,25 @@
1
- import { DateInputFormat } from './manualDateFormat';
1
+ import { DateField, DateValue } from 'react-aria-components';
2
2
  import * as React from 'react';
3
3
  export type InputDateProps = {
4
4
  className?: string;
5
- endContent?: React.ReactElement;
6
5
  disabled?: boolean;
7
6
  error?: boolean | string;
8
7
  errorMessage?: string;
9
8
  inputClassName?: string;
10
- inputFormat?: DateInputFormat;
9
+ locale?: string;
11
10
  onClear?: boolean | (() => void);
12
- value?: string;
13
- onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
14
- } & Omit<React.InputHTMLAttributes<HTMLInputElement>, 'value' | 'onChange' | 'type'>;
11
+ value?: DateValue | null;
12
+ onChange?: (date: DateValue | null) => void;
13
+ } & Omit<React.ComponentPropsWithoutRef<typeof DateField<DateValue>>, 'value' | 'onChange'>;
15
14
  declare const InputDate: React.ForwardRefExoticComponent<{
16
15
  className?: string;
17
- endContent?: React.ReactElement;
18
16
  disabled?: boolean;
19
17
  error?: boolean | string;
20
18
  errorMessage?: string;
21
19
  inputClassName?: string;
22
- inputFormat?: DateInputFormat;
20
+ locale?: string;
23
21
  onClear?: boolean | (() => void);
24
- value?: string;
25
- onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
26
- } & Omit<React.InputHTMLAttributes<HTMLInputElement>, "onChange" | "value" | "type"> & React.RefAttributes<HTMLInputElement>>;
22
+ value?: DateValue | null;
23
+ onChange?: (date: DateValue | null) => void;
24
+ } & Omit<Omit<import('react-aria-components').DateFieldProps<DateValue> & React.RefAttributes<HTMLDivElement>, "ref">, "onChange" | "value"> & React.RefAttributes<HTMLDivElement>>;
27
25
  export { InputDate };
@@ -2,25 +2,14 @@ import { jsxs, jsx } from 'react/jsx-runtime';
2
2
  import { CalendarBlank } from '@phosphor-icons/react/dist/ssr/CalendarBlank';
3
3
  import { WarningCircle } from '@phosphor-icons/react/dist/ssr/WarningCircle';
4
4
  import { X } from '@phosphor-icons/react/dist/ssr/X';
5
+ import { I18nProvider } from 'react-aria';
5
6
  import * as React from 'react';
6
- import { MaskedInput } from './MaskedInput.js';
7
+ import { DateField, Group, DateInput, DateSegment } from 'react-aria-components';
7
8
  import { cn } from '../../lib/utils.js';
8
9
 
9
- const emit = (onChange, value) => {
10
- onChange?.({ target: { value } });
11
- };
12
- const toInternalValue = (external, format) => {
13
- const isoMatch = /^(\d{4})-(\d{2})-(\d{2})$/.exec(external.trim());
14
- if (!isoMatch) return external;
15
- const [, year, month, day] = isoMatch;
16
- if (format === "DD/MM/YYYY") return `${day}/${month}/${year}`;
17
- if (format === "MM/DD/YYYY") return `${month}/${day}/${year}`;
18
- return `${year}/${month}/${day}`;
19
- };
20
10
  const InputDate = React.forwardRef(
21
11
  ({
22
12
  className,
23
- endContent,
24
13
  disabled,
25
14
  error,
26
15
  errorMessage,
@@ -28,129 +17,118 @@ const InputDate = React.forwardRef(
28
17
  onChange: controlledOnChange,
29
18
  inputClassName,
30
19
  onClear = true,
31
- inputFormat = "DD/MM/YYYY",
32
- "aria-invalid": ariaInvalid,
20
+ locale,
33
21
  ...props
34
22
  }, ref) => {
35
- const [internalValue, setInternalValue] = React.useState(() => toInternalValue(controlledValue ?? "", inputFormat));
23
+ const [dateValue, setDateValue] = React.useState(() => controlledValue ?? null);
36
24
  const [isHovered, setIsHovered] = React.useState(false);
25
+ const internalChange = React.useRef(false);
37
26
  React.useEffect(() => {
38
- if (!controlledValue) {
39
- setInternalValue("");
27
+ if (internalChange.current) {
28
+ internalChange.current = false;
40
29
  return;
41
30
  }
42
- if (/^\d{4}-\d{2}-\d{2}$/.test(controlledValue.trim())) {
43
- setInternalValue(toInternalValue(controlledValue, inputFormat));
44
- }
45
- }, [controlledValue, inputFormat]);
46
- const handleMaskedChange = (newValue) => {
47
- setInternalValue(newValue);
48
- if (!controlledOnChange) return;
49
- if (!newValue.trim()) {
50
- emit(controlledOnChange, "");
51
- return;
52
- }
53
- const formatConfigs = {
54
- "DD/MM/YYYY": { regex: /^(\d{2})\/(\d{2})\/(\d{4})$/, d: 1, m: 2, y: 3 },
55
- "MM/DD/YYYY": { regex: /^(\d{2})\/(\d{2})\/(\d{4})$/, d: 2, m: 1, y: 3 },
56
- "YYYY/MM/DD": { regex: /^(\d{4})\/(\d{2})\/(\d{2})$/, d: 3, m: 2, y: 1 }
57
- };
58
- const config = formatConfigs[inputFormat];
59
- const match = config.regex.exec(newValue);
60
- if (match) {
61
- const year = match[config.y];
62
- const month = match[config.m];
63
- const day = match[config.d];
64
- emit(controlledOnChange, `${year}-${month}-${day}`);
65
- }
31
+ setDateValue(controlledValue ?? null);
32
+ }, [controlledValue]);
33
+ const handleRacChange = (date) => {
34
+ internalChange.current = true;
35
+ setDateValue(date);
36
+ controlledOnChange?.(date);
66
37
  };
67
38
  const handleClear = () => {
68
- setInternalValue("");
69
- emit(controlledOnChange, "");
39
+ internalChange.current = true;
40
+ setDateValue(null);
41
+ controlledOnChange?.(null);
70
42
  if (typeof onClear === "function") onClear();
71
43
  };
72
- const hasValue = Boolean(internalValue && internalValue.trim() !== "");
44
+ const hasValue = dateValue !== null;
73
45
  const showClearIcon = Boolean(onClear && hasValue && isHovered && !disabled);
74
46
  const resolvedErrorMessage = typeof error === "string" ? error : errorMessage;
75
47
  const hasError = Boolean(error ?? resolvedErrorMessage);
76
- const renderCalendarIcon = () => /* @__PURE__ */ jsx(CalendarBlank, { size: 20 });
77
- const renderErrorIcon = () => {
78
- if (!hasError) return null;
79
- return /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
80
- WarningCircle,
81
- {
82
- "data-testid": "exclaim-icon",
83
- className: cn(
84
- "text-error-500 transition-opacity duration-150",
85
- showClearIcon ? "opacity-0" : "opacity-100"
86
- ),
87
- size: 18
88
- }
89
- ) });
90
- };
91
- const renderClearIcon = () => {
92
- if (disabled || !onClear) return null;
93
- return /* @__PURE__ */ jsx(
94
- "div",
48
+ const inner = /* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col gap-1", children: [
49
+ /* @__PURE__ */ jsx("div", { className: "flex w-full items-center", children: /* @__PURE__ */ jsx(
50
+ DateField,
95
51
  {
96
- className: cn(
97
- "absolute right-3 z-10 flex h-full w-10 items-center justify-end rounded-r-lg",
98
- endContent || error ? "bg-transparent" : "bg-gradient-to-l from-white from-60% via-white/80 via-80% to-transparent transition-opacity duration-150",
99
- showClearIcon ? "opacity-100" : "opacity-0"
100
- ),
101
- children: /* @__PURE__ */ jsx(
102
- X,
52
+ ref,
53
+ value: dateValue,
54
+ onChange: handleRacChange,
55
+ isDisabled: disabled,
56
+ isInvalid: hasError,
57
+ granularity: "day",
58
+ className: "w-full",
59
+ ...props,
60
+ children: /* @__PURE__ */ jsxs(
61
+ Group,
103
62
  {
104
- "data-testid": "clear-button",
63
+ "data-testid": "input-date-mask-control",
105
64
  className: cn(
106
- "h-4 w-4 shrink-0 cursor-pointer transition-opacity duration-150",
107
- showClearIcon ? "opacity-100 hover:opacity-70" : "opacity-0"
65
+ "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",
66
+ disabled && "bg-neutral-50",
67
+ hasError && "border-error-400 focus-within:border-error-700",
68
+ className
108
69
  ),
109
- onClick: handleClear
70
+ onMouseEnter: () => setIsHovered(true),
71
+ onMouseLeave: () => setIsHovered(false),
72
+ children: [
73
+ /* @__PURE__ */ jsx(CalendarBlank, { size: 20 }),
74
+ /* @__PURE__ */ jsx(DateInput, { className: cn("flex flex-1 items-center justify-center px-2", inputClassName), children: (segment) => /* @__PURE__ */ jsx(
75
+ DateSegment,
76
+ {
77
+ segment,
78
+ className: cn(
79
+ segment.type === "literal" ? "select-none text-neutral-400" : cn(
80
+ "min-w-[1ch] rounded-sm bg-transparent px-0.5 py-px text-center outline-0 transition-colors",
81
+ "caret-neutral-900",
82
+ "placeholder:text-neutral-300",
83
+ "data-focused:bg-neutral-100 data-focused:text-neutral-900",
84
+ "data-disabled:cursor-not-allowed data-disabled:opacity-50"
85
+ )
86
+ )
87
+ }
88
+ ) }),
89
+ hasError && /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
90
+ WarningCircle,
91
+ {
92
+ "data-testid": "exclaim-icon",
93
+ className: cn(
94
+ "text-error-500 transition-opacity duration-150",
95
+ showClearIcon ? "opacity-0" : "opacity-100"
96
+ ),
97
+ size: 18
98
+ }
99
+ ) }),
100
+ onClear && !disabled && /* @__PURE__ */ jsx(
101
+ "div",
102
+ {
103
+ className: cn(
104
+ "absolute right-3 z-10 flex h-full w-10 items-center justify-end rounded-r-lg",
105
+ hasError ? "bg-transparent" : "bg-linear-to-l from-white from-60% via-white/80 via-80% to-transparent transition-opacity duration-150",
106
+ showClearIcon ? "opacity-100" : "opacity-0"
107
+ ),
108
+ children: /* @__PURE__ */ jsx(
109
+ X,
110
+ {
111
+ "data-testid": "clear-button",
112
+ className: cn(
113
+ "h-4 w-4 shrink-0 cursor-pointer transition-opacity duration-150",
114
+ showClearIcon ? "opacity-100 hover:opacity-70" : "opacity-0"
115
+ ),
116
+ onClick: (e) => {
117
+ e.stopPropagation();
118
+ handleClear();
119
+ }
120
+ }
121
+ )
122
+ }
123
+ )
124
+ ]
110
125
  }
111
126
  )
112
127
  }
113
- );
114
- };
115
- return /* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col gap-1", children: [
116
- /* @__PURE__ */ jsx("div", { className: "flex w-full items-center", children: /* @__PURE__ */ jsxs(
117
- "label",
118
- {
119
- "data-testid": "input-date-mask-control",
120
- className: cn(
121
- "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",
122
- disabled && "bg-neutral-50",
123
- hasError && "border-error-400 focus-within:border-error-700",
124
- className
125
- ),
126
- onMouseEnter: () => setIsHovered(true),
127
- onMouseLeave: () => setIsHovered(false),
128
- children: [
129
- renderCalendarIcon(),
130
- /* @__PURE__ */ jsx(
131
- MaskedInput,
132
- {
133
- ref,
134
- inputFormat,
135
- placeholder: inputFormat,
136
- "aria-invalid": ariaInvalid ?? hasError,
137
- disabled,
138
- value: internalValue,
139
- onChange: handleMaskedChange,
140
- className: cn(
141
- "w-full bg-transparent px-2 outline-0 transition-colors placeholder:text-neutral-300 disabled:cursor-not-allowed disabled:opacity-50",
142
- inputClassName
143
- ),
144
- ...props
145
- }
146
- ),
147
- renderErrorIcon(),
148
- renderClearIcon()
149
- ]
150
- }
151
128
  ) }),
152
129
  resolvedErrorMessage && /* @__PURE__ */ jsx("span", { className: "text-error-500 text-sm", children: resolvedErrorMessage })
153
130
  ] });
131
+ return locale ? /* @__PURE__ */ jsx(I18nProvider, { locale, children: inner }) : inner;
154
132
  }
155
133
  );
156
134
  InputDate.displayName = "InputDate";
@@ -1 +1 @@
1
- {"version":3,"file":"InputDate.js","sources":["../../../src/components/InputDate/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 emit = (onChange: ((e: React.ChangeEvent<HTMLInputElement>) => void) | undefined, value: string) => {\n onChange?.({ target: { value } } as React.ChangeEvent<HTMLInputElement>)\n}\n\nconst toInternalValue = (external: string, format: DateInputFormat): string => {\n const isoMatch = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(external.trim())\n if (!isoMatch) return external\n\n const [, year, month, day] = isoMatch\n if (format === 'DD/MM/YYYY') return `${day}/${month}/${year}`\n if (format === 'MM/DD/YYYY') return `${month}/${day}/${year}`\n return `${year}/${month}/${day}`\n}\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 = 'DD/MM/YYYY',\n 'aria-invalid': ariaInvalid,\n ...props\n },\n ref,\n ) => {\n const [internalValue, setInternalValue] = React.useState(() => toInternalValue(controlledValue ?? '', inputFormat))\n const [isHovered, setIsHovered] = React.useState(false)\n\n React.useEffect(() => {\n if (!controlledValue) {\n setInternalValue('')\n return\n }\n if (/^\\d{4}-\\d{2}-\\d{2}$/.test(controlledValue.trim())) {\n setInternalValue(toInternalValue(controlledValue, inputFormat))\n }\n }, [controlledValue, inputFormat])\n\n const handleMaskedChange = (newValue: string) => {\n setInternalValue(newValue)\n\n if (!controlledOnChange) return\n\n if (!newValue.trim()) {\n emit(controlledOnChange, '')\n return\n }\n\n const formatConfigs: Record<DateInputFormat, { regex: RegExp; d: number; m: number; y: number }> = {\n 'DD/MM/YYYY': { regex: /^(\\d{2})\\/(\\d{2})\\/(\\d{4})$/, d: 1, m: 2, y: 3 },\n 'MM/DD/YYYY': { regex: /^(\\d{2})\\/(\\d{2})\\/(\\d{4})$/, d: 2, m: 1, y: 3 },\n 'YYYY/MM/DD': { regex: /^(\\d{4})\\/(\\d{2})\\/(\\d{2})$/, d: 3, m: 2, y: 1 },\n }\n\n const config = formatConfigs[inputFormat]\n const match = config.regex.exec(newValue)\n\n if (match) {\n const year = match[config.y]\n const month = match[config.m]\n const day = match[config.d]\n\n emit(controlledOnChange, `${year}-${month}-${day}`)\n }\n }\n\n const handleClear = () => {\n setInternalValue('')\n emit(controlledOnChange, '')\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":"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"}
1
+ {"version":3,"file":"InputDate.js","sources":["../../../src/components/InputDate/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 { I18nProvider } from 'react-aria'\nimport * as React from 'react'\nimport {\n DateField,\n DateInput,\n DateSegment,\n Group,\n type DateValue,\n} from 'react-aria-components'\nimport { cn } from '@/lib/utils'\n\nexport type InputDateProps = {\n className?: string\n disabled?: boolean\n error?: boolean | string\n errorMessage?: string\n inputClassName?: string\n locale?: string\n onClear?: boolean | (() => void)\n value?: DateValue | null\n onChange?: (date: DateValue | null) => void\n} & Omit<React.ComponentPropsWithoutRef<typeof DateField<DateValue>>, 'value' | 'onChange'>\n\nconst InputDate = React.forwardRef<HTMLDivElement, InputDateProps>(\n (\n {\n className,\n disabled,\n error,\n errorMessage,\n value: controlledValue,\n onChange: controlledOnChange,\n inputClassName,\n onClear = true,\n locale,\n ...props\n },\n ref,\n ) => {\n const [dateValue, setDateValue] = React.useState<DateValue | null>(() => controlledValue ?? null)\n const [isHovered, setIsHovered] = React.useState(false)\n const internalChange = React.useRef(false)\n\n React.useEffect(() => {\n if (internalChange.current) {\n internalChange.current = false\n return\n }\n setDateValue(controlledValue ?? null)\n }, [controlledValue])\n\n const handleRacChange = (date: DateValue | null) => {\n internalChange.current = true\n setDateValue(date)\n controlledOnChange?.(date)\n }\n\n const handleClear = () => {\n internalChange.current = true\n setDateValue(null)\n controlledOnChange?.(null)\n if (typeof onClear === 'function') onClear()\n }\n\n const hasValue = dateValue !== null\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 inner = (\n <div className=\"flex w-full flex-col gap-1\">\n <div className=\"flex w-full items-center\">\n <DateField\n ref={ref}\n value={dateValue}\n onChange={handleRacChange}\n isDisabled={disabled}\n isInvalid={hasError}\n granularity=\"day\"\n className=\"w-full\"\n {...props}\n >\n <Group\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 <CalendarBlank size={20} />\n <DateInput className={cn('flex flex-1 items-center justify-center px-2', inputClassName)}>\n {(segment) => (\n <DateSegment\n segment={segment}\n className={cn(\n segment.type === 'literal'\n ? 'select-none text-neutral-400'\n : cn(\n 'min-w-[1ch] rounded-sm bg-transparent px-0.5 py-px text-center outline-0 transition-colors',\n 'caret-neutral-900',\n 'placeholder:text-neutral-300',\n 'data-focused:bg-neutral-100 data-focused:text-neutral-900',\n 'data-disabled:cursor-not-allowed data-disabled:opacity-50',\n ),\n )}\n />\n )}\n </DateInput>\n {hasError && (\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 {onClear && !disabled && (\n <div\n className={cn(\n 'absolute right-3 z-10 flex h-full w-10 items-center justify-end rounded-r-lg',\n hasError\n ? 'bg-transparent'\n : 'bg-linear-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={(e) => {\n e.stopPropagation()\n handleClear()\n }}\n />\n </div>\n )}\n </Group>\n </DateField>\n </div>\n {resolvedErrorMessage && <span className=\"text-error-500 text-sm\">{resolvedErrorMessage}</span>}\n </div>\n )\n\n return locale ? <I18nProvider locale={locale}>{inner}</I18nProvider> : inner\n },\n)\n\nInputDate.displayName = 'InputDate'\n\nexport { InputDate }\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "periplo-ui",
3
3
  "description": "IATI UI library",
4
4
  "private": false,
5
- "version": "4.4.2",
5
+ "version": "4.5.0",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",
8
8
  "types": "dist/index.d.ts",
@@ -335,6 +335,7 @@
335
335
  "dist"
336
336
  ],
337
337
  "dependencies": {
338
+ "@internationalized/date": "^3.12.2",
338
339
  "@radix-ui/react-accordion": "^1.2.11",
339
340
  "@radix-ui/react-avatar": "^1.1.10",
340
341
  "@radix-ui/react-checkbox": "^1.3.2",
@@ -352,14 +353,16 @@
352
353
  "@radix-ui/react-toggle": "^1.1.9",
353
354
  "@radix-ui/react-toggle-group": "^1.1.10",
354
355
  "@radix-ui/react-tooltip": "^1.2.7",
356
+ "@tanstack/react-virtual": "^3.13.18",
355
357
  "class-variance-authority": "0.7.1",
356
358
  "clsx": "2.1.1",
357
359
  "cmdk": "^1.1.1",
358
360
  "date-fns": "^4.1.0",
359
361
  "embla-carousel-react": "^8.6.0",
362
+ "react-aria": "^3.50.0",
363
+ "react-aria-components": "^1.19.0",
360
364
  "react-day-picker": "^9.7.0",
361
365
  "sonner": "^2.0.5",
362
- "tailwind-merge": "3.3.1",
363
- "@tanstack/react-virtual": "^3.13.18"
366
+ "tailwind-merge": "3.3.1"
364
367
  }
365
368
  }
@@ -1,18 +0,0 @@
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 {};
@@ -1,227 +0,0 @@
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
@@ -1 +0,0 @@
1
- {"version":3,"file":"MaskedInput.js","sources":["../../../src/components/InputDate/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;;"}
@@ -1,11 +0,0 @@
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 {};
@@ -1,60 +0,0 @@
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
@@ -1 +0,0 @@
1
- {"version":3,"file":"manualDateFormat.js","sources":["../../../src/components/InputDate/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":"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"}