@coopdigital/react 1.30.1 → 1.31.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/Author/Author.d.ts +3 -1
- package/dist/components/Author/Author.js +1 -1
- package/dist/components/Author/index.d.ts +2 -2
- package/dist/components/Author/index.js +2 -2
- package/dist/components/Combobox/Combobox.d.ts +1 -1
- package/dist/components/Combobox/Combobox.js +29 -8
- package/dist/components/Combobox/{combobox-utils.js → combobox-internal.js} +4 -2
- package/dist/components/DateInput/DateInput.context.js +7 -0
- package/dist/components/DateInput/DateInput.d.ts +104 -0
- package/dist/components/DateInput/DateInput.js +128 -0
- package/dist/components/DateInput/dateinput-internal.d.ts +6 -0
- package/dist/components/DateInput/dateinput-internal.js +82 -0
- package/dist/components/DateInput/dateinput-utils.d.ts +66 -0
- package/dist/components/DateInput/dateinput-utils.js +91 -0
- package/dist/components/DateInput/index.d.ts +3 -0
- package/dist/components/DateInput/index.js +6 -0
- package/dist/components/Field/Field.context.js +1 -1
- package/dist/components/Field/Field.js +1 -1
- package/dist/components/FieldMarkers/{fieldmarkers-utils.js → fieldmarkers-internal.js} +1 -1
- package/dist/components/Fieldset/Fieldset.js +1 -1
- package/dist/components/Form/Form.d.ts +7 -1
- package/dist/components/Form/Form.js +2 -1
- package/dist/components/Squircle/Squircle.d.ts +3 -1
- package/dist/components/Squircle/Squircle.js +1 -1
- package/dist/components/Squircle/index.d.ts +2 -2
- package/dist/components/Squircle/index.js +2 -2
- package/dist/components/Textarea/Textarea.js +14 -4
- package/dist/components/ValidationSummary/ValidationSummary.d.ts +1 -1
- package/dist/components/ValidationSummary/ValidationSummary.js +1 -1
- package/dist/components/ValidationSummary/{validationsummary-utils.d.ts → validationsummary-internal.d.ts} +1 -1
- package/dist/components/ValidationSummary/{validationsummary-utils.js → validationsummary-internal.js} +3 -2
- package/dist/components/icons/AccountCircleFilledIcon.d.ts +2 -1
- package/dist/components/icons/AccountCircleIcon.d.ts +2 -1
- package/dist/components/icons/AccountFilledIcon.d.ts +2 -1
- package/dist/components/icons/AccountIcon.d.ts +2 -1
- package/dist/components/icons/AddIcon.d.ts +2 -1
- package/dist/components/icons/AppsFilledIcon.d.ts +2 -1
- package/dist/components/icons/AppsIcon.d.ts +2 -1
- package/dist/components/icons/ArrowDownIcon.d.ts +2 -1
- package/dist/components/icons/ArrowLeftIcon.d.ts +2 -1
- package/dist/components/icons/ArrowRightIcon.d.ts +2 -1
- package/dist/components/icons/ArrowUpIcon.d.ts +2 -1
- package/dist/components/icons/BackspaceFilledIcon.d.ts +2 -1
- package/dist/components/icons/BackspaceIcon.d.ts +2 -1
- package/dist/components/icons/BasketFilledIcon.d.ts +2 -1
- package/dist/components/icons/BasketIcon.d.ts +2 -1
- package/dist/components/icons/BellFilledIcon.d.ts +2 -1
- package/dist/components/icons/BellIcon.d.ts +2 -1
- package/dist/components/icons/BroomIcon.d.ts +2 -1
- package/dist/components/icons/CalendarFilledIcon.d.ts +2 -1
- package/dist/components/icons/CalendarIcon.d.ts +2 -1
- package/dist/components/icons/CashFilledIcon.d.ts +2 -1
- package/dist/components/icons/CashIcon.d.ts +2 -1
- package/dist/components/icons/ChevronDownIcon.d.ts +2 -1
- package/dist/components/icons/ChevronLeftIcon.d.ts +2 -1
- package/dist/components/icons/ChevronRightIcon.d.ts +2 -1
- package/dist/components/icons/ChevronUpIcon.d.ts +2 -1
- package/dist/components/icons/CircleBurgerFilledIcon.d.ts +2 -1
- package/dist/components/icons/CircleBurgerIcon.d.ts +2 -1
- package/dist/components/icons/ClipboardFilledIcon.d.ts +2 -1
- package/dist/components/icons/ClipboardIcon.d.ts +2 -1
- package/dist/components/icons/ClockFilledIcon.d.ts +2 -1
- package/dist/components/icons/ClockIcon.d.ts +2 -1
- package/dist/components/icons/CloseAltFilledIcon.d.ts +2 -1
- package/dist/components/icons/CloseAltIcon.d.ts +2 -1
- package/dist/components/icons/CogIcon.d.ts +2 -1
- package/dist/components/icons/CommunityFilledIcon.d.ts +2 -1
- package/dist/components/icons/CommunityIcon.d.ts +2 -1
- package/dist/components/icons/ComputerIcon.d.ts +2 -1
- package/dist/components/icons/ContactFilledIcon.d.ts +2 -1
- package/dist/components/icons/ContactIcon.d.ts +2 -1
- package/dist/components/icons/ContactlessFilledIcon.d.ts +2 -1
- package/dist/components/icons/ContactlessIcon.d.ts +2 -1
- package/dist/components/icons/CoopLiveIcon.d.ts +2 -1
- package/dist/components/icons/CoopLocationFilledIcon.d.ts +2 -1
- package/dist/components/icons/CoopLogoIcon.d.ts +2 -1
- package/dist/components/icons/CoopMembershipCardFilledIcon.d.ts +2 -1
- package/dist/components/icons/CreditCardFilledIcon.d.ts +2 -1
- package/dist/components/icons/CreditCardIcon.d.ts +2 -1
- package/dist/components/icons/CrossIcon.d.ts +2 -1
- package/dist/components/icons/DocumentIcon.d.ts +2 -1
- package/dist/components/icons/DownloadIcon.d.ts +2 -1
- package/dist/components/icons/EnterStoreIcon.d.ts +2 -1
- package/dist/components/icons/ExitStoreIcon.d.ts +2 -1
- package/dist/components/icons/EyeFilledIcon.d.ts +2 -1
- package/dist/components/icons/EyeIcon.d.ts +2 -1
- package/dist/components/icons/EyeStrikeFilledIcon.d.ts +2 -1
- package/dist/components/icons/EyeStrikeIcon.d.ts +2 -1
- package/dist/components/icons/FeedbackFilledIcon.d.ts +2 -1
- package/dist/components/icons/FeedbackIcon.d.ts +2 -1
- package/dist/components/icons/FilterIcon.d.ts +2 -1
- package/dist/components/icons/GiftCardFilledIcon.d.ts +2 -1
- package/dist/components/icons/GiftCardIcon.d.ts +2 -1
- package/dist/components/icons/GiftFilledIcon.d.ts +2 -1
- package/dist/components/icons/GiftIcon.d.ts +2 -1
- package/dist/components/icons/HelpFilledIcon.d.ts +2 -1
- package/dist/components/icons/HelpIcon.d.ts +2 -1
- package/dist/components/icons/HomeFilledIcon.d.ts +2 -1
- package/dist/components/icons/HomeIcon.d.ts +2 -1
- package/dist/components/icons/HowDoIAssistantIcon.d.ts +2 -1
- package/dist/components/icons/InfoIcon.d.ts +2 -1
- package/dist/components/icons/KebabIcon.d.ts +2 -1
- package/dist/components/icons/LoadingIcon.d.ts +2 -1
- package/dist/components/icons/LocationFilledIcon.d.ts +2 -1
- package/dist/components/icons/LocationIcon.d.ts +2 -1
- package/dist/components/icons/LogoutIcon.d.ts +2 -1
- package/dist/components/icons/MailIcon.d.ts +2 -1
- package/dist/components/icons/MenuIcon.d.ts +2 -1
- package/dist/components/icons/MessageIcon.d.ts +2 -1
- package/dist/components/icons/MinusIcon.d.ts +2 -1
- package/dist/components/icons/NotesFilledIcon.d.ts +2 -1
- package/dist/components/icons/NotesIcon.d.ts +2 -1
- package/dist/components/icons/OpenNewIcon.d.ts +2 -1
- package/dist/components/icons/OwnBagIcon.d.ts +2 -1
- package/dist/components/icons/PadlockIcon.d.ts +2 -1
- package/dist/components/icons/PencilIcon.d.ts +2 -1
- package/dist/components/icons/PhoneIcon.d.ts +2 -1
- package/dist/components/icons/PowerIcon.d.ts +2 -1
- package/dist/components/icons/SaveFilledIcon.d.ts +2 -1
- package/dist/components/icons/SaveIcon.d.ts +2 -1
- package/dist/components/icons/ScooterIcon.d.ts +2 -1
- package/dist/components/icons/SearchFilledIcon.d.ts +2 -1
- package/dist/components/icons/SearchIcon.d.ts +2 -1
- package/dist/components/icons/SendIcon.d.ts +2 -1
- package/dist/components/icons/SpannerIcon.d.ts +2 -1
- package/dist/components/icons/StarFilledIcon.d.ts +2 -1
- package/dist/components/icons/StarIcon.d.ts +2 -1
- package/dist/components/icons/TickAltFilledIcon.d.ts +2 -1
- package/dist/components/icons/TickAltIcon.d.ts +2 -1
- package/dist/components/icons/TickIcon.d.ts +2 -1
- package/dist/components/icons/TruckIcon.d.ts +2 -1
- package/dist/components/icons/UserWipeIcon.d.ts +2 -1
- package/dist/components/icons/VoucherFilledIcon.d.ts +2 -1
- package/dist/components/icons/VoucherIcon.d.ts +2 -1
- package/dist/components/icons/WarningCircleFilledIcon.d.ts +2 -1
- package/dist/components/icons/WarningCircleIcon.d.ts +2 -1
- package/dist/components/icons/WarningFilledIcon.d.ts +2 -1
- package/dist/components/icons/WarningIcon.d.ts +2 -1
- package/dist/components/icons/WifiIcon.d.ts +2 -1
- package/dist/components/icons/WriteIcon.d.ts +2 -1
- package/dist/hooks/index.d.ts +2 -1
- package/dist/hooks/index.js +2 -1
- package/dist/hooks/useFormReset.d.ts +6 -0
- package/dist/hooks/useFormReset.js +19 -0
- package/dist/hooks/useSlots.d.ts +2 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.js +6 -4
- package/dist/types/index.js +4 -0
- package/dist/utils/slots.d.ts +1 -1
- package/dist/utils/slots.js +20 -12
- package/package.json +22 -18
- package/src/components/Author/Author.tsx +1 -1
- package/src/components/Combobox/Combobox.context.ts +3 -1
- package/src/components/Combobox/Combobox.tsx +57 -24
- package/src/components/Combobox/combobox-internal.ts +60 -0
- package/src/components/Combobox/combobox-utils.ts +1 -41
- package/src/components/Combobox/index.ts +1 -0
- package/src/components/DateInput/DateInput.context.ts +25 -0
- package/src/components/DateInput/DateInput.tsx +252 -0
- package/src/components/DateInput/dateinput-internal.ts +176 -0
- package/src/components/DateInput/dateinput-utils.ts +194 -0
- package/src/components/DateInput/index.ts +6 -0
- package/src/components/Field/Field.context.ts +1 -1
- package/src/components/Field/Field.tsx +1 -1
- package/src/components/Fieldset/Fieldset.tsx +5 -1
- package/src/components/Form/Form.tsx +9 -1
- package/src/components/Squircle/Squircle.tsx +1 -1
- package/src/components/Textarea/Textarea.tsx +19 -4
- package/src/components/ValidationSummary/ValidationSummary.context.ts +1 -1
- package/src/components/ValidationSummary/ValidationSummary.tsx +1 -1
- package/src/components/ValidationSummary/{validationsummary-utils.ts → validationsummary-internal.ts} +9 -8
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useFormReset.ts +28 -0
- package/src/index.ts +1 -0
- package/src/utils/slots.ts +37 -23
- /package/src/components/FieldMarkers/{fieldmarkers-utils.ts → fieldmarkers-internal.ts} +0 -0
|
@@ -36,5 +36,7 @@ declare const Author: {
|
|
|
36
36
|
name: string;
|
|
37
37
|
};
|
|
38
38
|
};
|
|
39
|
+
declare const AuthorImage: ({ alt, src, ...props }: AuthorImageProps) => JSX.Element;
|
|
40
|
+
declare const AuthorMeta: ({ date, datePrefix, name, ...props }: AuthorMetaProps) => JSX.Element;
|
|
39
41
|
//#endregion
|
|
40
|
-
export { Author, Author as default };
|
|
42
|
+
export { Author, Author as default, AuthorImage, AuthorMeta };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { Author, AuthorImageProps, AuthorMetaProps, AuthorProps } from "./Author.js";
|
|
2
|
-
export { Author, Author as default, AuthorImageProps, AuthorMetaProps, AuthorProps };
|
|
1
|
+
import { Author, AuthorImage, AuthorImageProps, AuthorMeta, AuthorMetaProps, AuthorProps } from "./Author.js";
|
|
2
|
+
export { Author, Author as default, AuthorImage, AuthorImageProps, AuthorMeta, AuthorMetaProps, AuthorProps };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Author } from "./Author.js";
|
|
1
|
+
import { Author, AuthorImage, AuthorMeta } from "./Author.js";
|
|
2
2
|
//#region src/components/Author/index.ts
|
|
3
3
|
var Author_default = Author;
|
|
4
4
|
//#endregion
|
|
5
|
-
export { Author, Author_default as default };
|
|
5
|
+
export { Author, AuthorImage, AuthorMeta, Author_default as default };
|
|
@@ -181,4 +181,4 @@ declare const ComboboxOptions: {
|
|
|
181
181
|
};
|
|
182
182
|
};
|
|
183
183
|
//#endregion
|
|
184
|
-
export { Combobox, Combobox as default, ComboboxButton, ComboboxControls, ComboboxInput,
|
|
184
|
+
export { Combobox, Combobox as default, ComboboxButton, ComboboxControls, ComboboxInput, ComboboxOptions };
|
|
@@ -4,18 +4,19 @@ import { ChevronDownIcon } from "../icons/ChevronDownIcon.js";
|
|
|
4
4
|
import { getSlotProp } from "../../utils/slots.js";
|
|
5
5
|
import { useSlots } from "../../hooks/useSlots.js";
|
|
6
6
|
import { getFieldError, useField } from "../Field/Field.context.js";
|
|
7
|
-
import { defaultFilter, defaultIsItemDisabled, defaultItemToString, isMatchingText, normalizeOptions, resolveOption } from "./combobox-
|
|
7
|
+
import { defaultFilter, defaultIsItemDisabled, defaultItemToString, defaultMaxResultsText, isMatchingText, normalizeOptions, resolveOption, shouldShowSelectionText } from "./combobox-internal.js";
|
|
8
8
|
import { ComboboxContext, useComboboxContext } from "./Combobox.context.js";
|
|
9
9
|
import { TextInput } from "../TextInput/TextInput.js";
|
|
10
10
|
import { useDebounce } from "../../hooks/useDebounce.js";
|
|
11
|
+
import { useFormReset } from "../../hooks/useFormReset.js";
|
|
11
12
|
import clsx from "clsx";
|
|
12
13
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
13
14
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
14
15
|
import { useCombobox } from "downshift";
|
|
16
|
+
import mergeRefs from "merge-refs";
|
|
15
17
|
//#region src/components/Combobox/Combobox.tsx
|
|
16
18
|
const DEFAULT_MAX_RESULTS = 50;
|
|
17
19
|
const ANNOUNCEMENT_DELAY = 750;
|
|
18
|
-
const defaultMaxResultsText = (shown, total) => `Showing ${shown} of ${total} results.`;
|
|
19
20
|
const componentSlots = {
|
|
20
21
|
ComboboxControls: null,
|
|
21
22
|
ComboboxOptions: null
|
|
@@ -39,7 +40,9 @@ const Combobox = ({ allowCustom = false, children, className, defaultSelected, d
|
|
|
39
40
|
]);
|
|
40
41
|
const visibleItems = useMemo(() => maxResults === false ? matchingItems : matchingItems.slice(0, maxResults), [matchingItems, maxResults]);
|
|
41
42
|
const [initialSelectedItem] = useState(() => resolveOption(defaultSelected, items));
|
|
42
|
-
const
|
|
43
|
+
const defaultSelectedItem = resolveOption(defaultSelected, items);
|
|
44
|
+
const controlled = selected !== void 0;
|
|
45
|
+
const selectedItem = controlled ? resolveOption(selected, items) : void 0;
|
|
43
46
|
const commit = (changes, closeMenu) => {
|
|
44
47
|
const text = (changes.inputValue ?? "").trim();
|
|
45
48
|
if (text === "") return {
|
|
@@ -92,6 +95,10 @@ const Combobox = ({ allowCustom = false, children, className, defaultSelected, d
|
|
|
92
95
|
}
|
|
93
96
|
};
|
|
94
97
|
const downshift = useCombobox({
|
|
98
|
+
...!controlled && {
|
|
99
|
+
defaultInputValue: toString(defaultSelectedItem),
|
|
100
|
+
defaultSelectedItem
|
|
101
|
+
},
|
|
95
102
|
getItemId: (index) => visibleItems[index]?.id ?? `${uid}_item_${index}`,
|
|
96
103
|
id: uid,
|
|
97
104
|
initialSelectedItem,
|
|
@@ -133,25 +140,33 @@ const Combobox = ({ allowCustom = false, children, className, defaultSelected, d
|
|
|
133
140
|
text: selectionText,
|
|
134
141
|
value: selectionValue
|
|
135
142
|
});
|
|
136
|
-
const { inputValue, setInputValue } = downshift;
|
|
143
|
+
const { inputValue, reset, setInputValue } = downshift;
|
|
137
144
|
useEffect(() => {
|
|
138
145
|
const previous = previousSelection.current;
|
|
139
|
-
|
|
146
|
+
const current = {
|
|
140
147
|
text: selectionText,
|
|
141
148
|
value: selectionValue
|
|
142
149
|
};
|
|
143
|
-
|
|
150
|
+
previousSelection.current = current;
|
|
151
|
+
if (shouldShowSelectionText(previous, current, inputValue)) setInputValue(selectionText);
|
|
144
152
|
}, [
|
|
145
153
|
inputValue,
|
|
146
154
|
selectionText,
|
|
147
155
|
selectionValue,
|
|
148
156
|
setInputValue
|
|
149
157
|
]);
|
|
158
|
+
const inputRef = useRef(null);
|
|
159
|
+
useFormReset(inputRef, () => {
|
|
160
|
+
if (controlled) return;
|
|
161
|
+
reset();
|
|
162
|
+
setFilterText("");
|
|
163
|
+
});
|
|
150
164
|
return /* @__PURE__ */ jsx(ComboboxContext.Provider, {
|
|
151
165
|
value: {
|
|
152
166
|
disabled,
|
|
153
167
|
downshift,
|
|
154
168
|
error: hasError,
|
|
169
|
+
inputRef,
|
|
155
170
|
items: visibleItems,
|
|
156
171
|
matchCount: matchingItems.length,
|
|
157
172
|
size
|
|
@@ -177,7 +192,8 @@ const ComboboxInput = ({ error, name, size, ...props }) => {
|
|
|
177
192
|
const disabled = combobox.disabled || props.disabled;
|
|
178
193
|
const inputProps = combobox.downshift.getInputProps({
|
|
179
194
|
...props,
|
|
180
|
-
disabled
|
|
195
|
+
disabled,
|
|
196
|
+
ref: mergeRefs(combobox.inputRef, props.ref)
|
|
181
197
|
});
|
|
182
198
|
return /* @__PURE__ */ jsxs(Fragment, { children: [name && /* @__PURE__ */ jsx("input", {
|
|
183
199
|
disabled,
|
|
@@ -218,10 +234,15 @@ const ComboboxOptions = ({ className, maxResults: _maxResults, maxResultsText =
|
|
|
218
234
|
};
|
|
219
235
|
const message = getMessage();
|
|
220
236
|
const announcement = useDebounce(message, ANNOUNCEMENT_DELAY);
|
|
237
|
+
const onMouseLeave = (event) => {
|
|
238
|
+
props.onMouseLeave?.(event);
|
|
239
|
+
if (!isOpen) Object.assign(event, { preventDownshiftDefault: true });
|
|
240
|
+
};
|
|
221
241
|
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("ul", {
|
|
222
242
|
...getMenuProps({
|
|
223
243
|
...props,
|
|
224
|
-
className: clsx("coop-combobox-options", className)
|
|
244
|
+
className: clsx("coop-combobox-options", className),
|
|
245
|
+
onMouseLeave
|
|
225
246
|
}),
|
|
226
247
|
children: [isOpen && items.map((item, index) => {
|
|
227
248
|
const { children, disabled: _disabled, icon, label, onClick, onMouseDown, onMouseMove, value, ...attributes } = item;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { matchSorter } from "match-sorter";
|
|
2
|
-
//#region src/components/Combobox/combobox-
|
|
2
|
+
//#region src/components/Combobox/combobox-internal.ts
|
|
3
3
|
const defaultFilter = (items, inputValue) => inputValue !== "" ? matchSorter(items, inputValue, { keys: [(item) => [item.label ?? item.value]] }) : items;
|
|
4
4
|
const defaultIsItemDisabled = (item) => item.disabled ?? false;
|
|
5
5
|
const defaultItemToString = (item) => item?.label ?? item?.value ?? "";
|
|
6
|
+
const defaultMaxResultsText = (shown, total) => `Showing ${shown} of ${total} results.`;
|
|
6
7
|
const normalizeOptions = (options) => options.filter((option) => option && typeof option !== "number").map((option) => typeof option === "string" ? { value: option } : option);
|
|
7
8
|
const collator = new Intl.Collator(void 0, { sensitivity: "base" });
|
|
8
9
|
const isMatchingText = (optionText, text) => collator.compare(optionText.trim(), text.trim()) === 0;
|
|
@@ -11,5 +12,6 @@ const resolveOption = (option, items) => {
|
|
|
11
12
|
const item = typeof option === "string" ? { value: option } : option;
|
|
12
13
|
return items.find(({ value }) => value === item.value) ?? item;
|
|
13
14
|
};
|
|
15
|
+
const shouldShowSelectionText = (previous, current, inputValue) => current.value === previous.value && current.text !== previous.text && inputValue === previous.text;
|
|
14
16
|
//#endregion
|
|
15
|
-
export { defaultFilter, defaultIsItemDisabled, defaultItemToString, isMatchingText, normalizeOptions, resolveOption };
|
|
17
|
+
export { defaultFilter, defaultIsItemDisabled, defaultItemToString, defaultMaxResultsText, isMatchingText, normalizeOptions, resolveOption, shouldShowSelectionText };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { createContext, useContext } from "react";
|
|
3
|
+
//#region src/components/DateInput/DateInput.context.ts
|
|
4
|
+
const DateInputContext = createContext(null);
|
|
5
|
+
const useDateInput = () => useContext(DateInputContext);
|
|
6
|
+
//#endregion
|
|
7
|
+
export { DateInputContext, useDateInput };
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { StandardSizes } from "../../types/index.js";
|
|
2
|
+
import { TextInputProps } from "../TextInput/TextInput.js";
|
|
3
|
+
import "../TextInput/index.js";
|
|
4
|
+
import { DateInputMode, DateParts } from "./dateinput-utils.js";
|
|
5
|
+
import { HTMLAttributes, JSX, ReactNode, Ref } from "react";
|
|
6
|
+
//#region src/components/DateInput/DateInput.d.ts
|
|
7
|
+
export interface DateInputChangeEvent {
|
|
8
|
+
/** The parts as typed. */
|
|
9
|
+
parts: DateParts;
|
|
10
|
+
/** The parts as typed, joined from year to day, such as `2026-2-30`.
|
|
11
|
+
*
|
|
12
|
+
* Not checked: use `validateDate` before relying on it. */
|
|
13
|
+
value: string;
|
|
14
|
+
}
|
|
15
|
+
export interface DateInputProps extends Omit<HTMLAttributes<HTMLDivElement>, "defaultValue" | "onBlur" | "onChange"> {
|
|
16
|
+
/** Content inside the component.
|
|
17
|
+
*
|
|
18
|
+
* This should be a set of `<DateInput.Day>`, `<DateInput.Month>` and `<DateInput.Year>`. */
|
|
19
|
+
children?: ReactNode;
|
|
20
|
+
/** Specify additional CSS classes. */
|
|
21
|
+
className?: string;
|
|
22
|
+
/** Specify the initial value, when not controlled. One of:
|
|
23
|
+
*
|
|
24
|
+
* - an ISO date, such as `1990-08-21`
|
|
25
|
+
* - the parts, such as `{ year: "2026" }` */
|
|
26
|
+
defaultValue?: DateParts | string;
|
|
27
|
+
/** Specify the base for the part ids. Will be auto-generated if not set. */
|
|
28
|
+
id?: string;
|
|
29
|
+
/** Specify the DateInput mode. */
|
|
30
|
+
mode?: DateInputMode;
|
|
31
|
+
/** Specify the name.
|
|
32
|
+
*
|
|
33
|
+
* Each part is `{name}-day`, `{name}-month` and`{name}-year`.
|
|
34
|
+
*
|
|
35
|
+
* A hidden input `{name}` holds the value as typed. */
|
|
36
|
+
name?: string;
|
|
37
|
+
/** Called when focus leaves the whole DateInput, not each part. */
|
|
38
|
+
onBlur?: (event: DateInputChangeEvent) => void;
|
|
39
|
+
/** Called when any part changes. */
|
|
40
|
+
onChange?: (event: DateInputChangeEvent) => void;
|
|
41
|
+
/** Specify a custom React ref for this component. */
|
|
42
|
+
ref?: Ref<HTMLDivElement>;
|
|
43
|
+
/** Specify the component size. */
|
|
44
|
+
size?: StandardSizes;
|
|
45
|
+
/** Specify the value, when controlled. One of:
|
|
46
|
+
*
|
|
47
|
+
* - an ISO date, such as `1990-08-21`
|
|
48
|
+
* - the parts, such as `{ year: "2026" }`
|
|
49
|
+
*
|
|
50
|
+
* Update it in `onChange` with the `value` or `parts` of the event. */
|
|
51
|
+
value?: DateParts | string;
|
|
52
|
+
}
|
|
53
|
+
declare const DateInput: {
|
|
54
|
+
({ children, className, defaultValue, id, mode, name, onBlur, onChange, ref, size, value, ...props }: DateInputProps): JSX.Element;
|
|
55
|
+
slot: {
|
|
56
|
+
name: string;
|
|
57
|
+
};
|
|
58
|
+
Day: {
|
|
59
|
+
({ label, ...props }: DateInputPartProps): JSX.Element;
|
|
60
|
+
slot: {
|
|
61
|
+
name: string;
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
Month: {
|
|
65
|
+
({ label, ...props }: DateInputPartProps): JSX.Element;
|
|
66
|
+
slot: {
|
|
67
|
+
name: string;
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
Year: {
|
|
71
|
+
({ label, ...props }: DateInputPartProps): JSX.Element;
|
|
72
|
+
slot: {
|
|
73
|
+
name: string;
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
export interface DateInputPartProps extends Omit<TextInputProps, "defaultValue" | "fit" | "prefix" | "prefixInline" | "suffix" | "suffixInline" | "type" | "value"> {
|
|
78
|
+
/** Specify the part error state.
|
|
79
|
+
*
|
|
80
|
+
* Inherits error state of a `group` Fieldset when not set. */
|
|
81
|
+
error?: boolean;
|
|
82
|
+
/** Specify the label. */
|
|
83
|
+
label?: ReactNode;
|
|
84
|
+
}
|
|
85
|
+
declare const DateInputDay: {
|
|
86
|
+
({ label, ...props }: DateInputPartProps): JSX.Element;
|
|
87
|
+
slot: {
|
|
88
|
+
name: string;
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
declare const DateInputMonth: {
|
|
92
|
+
({ label, ...props }: DateInputPartProps): JSX.Element;
|
|
93
|
+
slot: {
|
|
94
|
+
name: string;
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
declare const DateInputYear: {
|
|
98
|
+
({ label, ...props }: DateInputPartProps): JSX.Element;
|
|
99
|
+
slot: {
|
|
100
|
+
name: string;
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
//#endregion
|
|
104
|
+
export { DateInput, DateInput as default, DateInputDay, DateInputMonth, DateInputYear };
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useId as useId$1 } from "../../hooks/useId.js";
|
|
3
|
+
import { forEachSlot } from "../../utils/slots.js";
|
|
4
|
+
import { TextInput } from "../TextInput/TextInput.js";
|
|
5
|
+
import { useFormReset } from "../../hooks/useFormReset.js";
|
|
6
|
+
import { Field } from "../Field/Field.js";
|
|
7
|
+
import { formatDateValue, toDateParts, toDateValue } from "./dateinput-internal.js";
|
|
8
|
+
import { getAutoComplete } from "./dateinput-utils.js";
|
|
9
|
+
import { DateInputContext, useDateInput } from "./DateInput.context.js";
|
|
10
|
+
import clsx from "clsx";
|
|
11
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
12
|
+
import { useRef, useState } from "react";
|
|
13
|
+
import mergeRefs from "merge-refs";
|
|
14
|
+
//#region src/components/DateInput/DateInput.tsx
|
|
15
|
+
const PART_SLOTS = {
|
|
16
|
+
DateInputDay: "day",
|
|
17
|
+
DateInputMonth: "month",
|
|
18
|
+
DateInputYear: "year"
|
|
19
|
+
};
|
|
20
|
+
const DateInput = ({ children, className, defaultValue, id, mode, name, onBlur, onChange, ref, size, value, ...props }) => {
|
|
21
|
+
const uid = useId$1(id);
|
|
22
|
+
const rendered = [];
|
|
23
|
+
forEachSlot(children, (slot) => {
|
|
24
|
+
const part = PART_SLOTS[slot.name];
|
|
25
|
+
if (part && !rendered.includes(part)) rendered.push(part);
|
|
26
|
+
});
|
|
27
|
+
const defaultParts = toDateParts(defaultValue, rendered);
|
|
28
|
+
const [typed, setTyped] = useState(() => value === void 0 ? defaultParts : toDateParts(value, rendered));
|
|
29
|
+
const controlled = value !== void 0;
|
|
30
|
+
const parts = controlled && toDateValue(value, rendered) !== toDateValue(typed, rendered) ? toDateParts(value, rendered) : typed;
|
|
31
|
+
const controlRef = useRef(null);
|
|
32
|
+
useFormReset(controlRef, () => {
|
|
33
|
+
if (!controlled) setTyped(defaultParts);
|
|
34
|
+
});
|
|
35
|
+
const getEvent = (next) => ({
|
|
36
|
+
parts: next,
|
|
37
|
+
value: formatDateValue(next, rendered)
|
|
38
|
+
});
|
|
39
|
+
const changePart = (part, partValue) => {
|
|
40
|
+
const next = {
|
|
41
|
+
...parts,
|
|
42
|
+
[part]: partValue
|
|
43
|
+
};
|
|
44
|
+
setTyped(next);
|
|
45
|
+
onChange?.(getEvent(next));
|
|
46
|
+
};
|
|
47
|
+
const handleBlur = (event) => {
|
|
48
|
+
if (!event.currentTarget.contains(event.relatedTarget)) onBlur?.(getEvent(parts));
|
|
49
|
+
};
|
|
50
|
+
const componentProps = {
|
|
51
|
+
...props,
|
|
52
|
+
className: clsx("coop-date-input", className),
|
|
53
|
+
onBlur: handleBlur
|
|
54
|
+
};
|
|
55
|
+
return /* @__PURE__ */ jsx(DateInputContext.Provider, {
|
|
56
|
+
value: {
|
|
57
|
+
changePart,
|
|
58
|
+
controlled,
|
|
59
|
+
controlPart: rendered[0],
|
|
60
|
+
controlRef,
|
|
61
|
+
defaultValue: defaultParts,
|
|
62
|
+
id: uid,
|
|
63
|
+
mode,
|
|
64
|
+
name,
|
|
65
|
+
parts,
|
|
66
|
+
size
|
|
67
|
+
},
|
|
68
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
69
|
+
...componentProps,
|
|
70
|
+
ref,
|
|
71
|
+
children: [name && /* @__PURE__ */ jsx("input", {
|
|
72
|
+
name,
|
|
73
|
+
type: "hidden",
|
|
74
|
+
value: formatDateValue(parts, rendered)
|
|
75
|
+
}), children]
|
|
76
|
+
})
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
const DateInputPart = ({ autoComplete, className, datePart: part, error, id, label, name, onChange, size, ...props }) => {
|
|
80
|
+
const dateInput = useDateInput();
|
|
81
|
+
const inputProps = {
|
|
82
|
+
...dateInput?.controlled ? { value: dateInput.parts[part] ?? "" } : { defaultValue: dateInput?.defaultValue?.[part] },
|
|
83
|
+
autoComplete: autoComplete ?? getAutoComplete(dateInput?.mode, part),
|
|
84
|
+
className: clsx("coop-date-input--input", className),
|
|
85
|
+
"data-part": part,
|
|
86
|
+
id: id ?? (dateInput ? `${dateInput.id}-${part}` : void 0),
|
|
87
|
+
inputMode: "numeric",
|
|
88
|
+
name: name ?? (dateInput?.name ? `${dateInput.name}-${part}` : void 0),
|
|
89
|
+
onChange: (event) => {
|
|
90
|
+
dateInput?.changePart(part, event.target.value);
|
|
91
|
+
onChange?.(event);
|
|
92
|
+
},
|
|
93
|
+
pattern: "[0-9]*",
|
|
94
|
+
size: size ?? dateInput?.size,
|
|
95
|
+
spellCheck: false,
|
|
96
|
+
...props,
|
|
97
|
+
ref: dateInput?.controlPart === part ? mergeRefs(dateInput.controlRef, props.ref) : props.ref
|
|
98
|
+
};
|
|
99
|
+
return /* @__PURE__ */ jsxs(Field, {
|
|
100
|
+
error,
|
|
101
|
+
hideErrorBar: true,
|
|
102
|
+
children: [/* @__PURE__ */ jsx(Field.Label, { children: label }), /* @__PURE__ */ jsx(TextInput, { ...inputProps })]
|
|
103
|
+
});
|
|
104
|
+
};
|
|
105
|
+
const DateInputDay = ({ label = "Day", ...props }) => /* @__PURE__ */ jsx(DateInputPart, {
|
|
106
|
+
...props,
|
|
107
|
+
datePart: "day",
|
|
108
|
+
label
|
|
109
|
+
});
|
|
110
|
+
const DateInputMonth = ({ label = "Month", ...props }) => /* @__PURE__ */ jsx(DateInputPart, {
|
|
111
|
+
...props,
|
|
112
|
+
datePart: "month",
|
|
113
|
+
label
|
|
114
|
+
});
|
|
115
|
+
const DateInputYear = ({ label = "Year", ...props }) => /* @__PURE__ */ jsx(DateInputPart, {
|
|
116
|
+
...props,
|
|
117
|
+
datePart: "year",
|
|
118
|
+
label
|
|
119
|
+
});
|
|
120
|
+
DateInput.slot = { name: "DateInput" };
|
|
121
|
+
DateInputDay.slot = { name: "DateInputDay" };
|
|
122
|
+
DateInputMonth.slot = { name: "DateInputMonth" };
|
|
123
|
+
DateInputYear.slot = { name: "DateInputYear" };
|
|
124
|
+
DateInput.Day = DateInputDay;
|
|
125
|
+
DateInput.Month = DateInputMonth;
|
|
126
|
+
DateInput.Year = DateInputYear;
|
|
127
|
+
//#endregion
|
|
128
|
+
export { DateInput, DateInput as default, DateInputDay, DateInputMonth, DateInputYear };
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { format, formatISO, isExists, isValid, parseISO } from "date-fns";
|
|
2
|
+
//#region src/components/DateInput/dateinput-internal.ts
|
|
3
|
+
const VALUE_ORDER = [
|
|
4
|
+
"year",
|
|
5
|
+
"month",
|
|
6
|
+
"day"
|
|
7
|
+
];
|
|
8
|
+
const WHOLE_DATE_ERRORS = /* @__PURE__ */ new Set([
|
|
9
|
+
"after-max",
|
|
10
|
+
"before-min",
|
|
11
|
+
"impossible-date",
|
|
12
|
+
"missing-date",
|
|
13
|
+
"unsupported-parts"
|
|
14
|
+
]);
|
|
15
|
+
const valueParts = (parts) => VALUE_ORDER.filter((part) => parts.includes(part));
|
|
16
|
+
const formatDateValue = (date, parts = VALUE_ORDER) => {
|
|
17
|
+
const values = valueParts(parts).map((part) => date[part] ?? "");
|
|
18
|
+
return values.every((value) => value === "") ? "" : values.join("-");
|
|
19
|
+
};
|
|
20
|
+
const splitValue = (value, used) => {
|
|
21
|
+
const pieces = value.split("-");
|
|
22
|
+
return !used.includes("year") && value.startsWith("--") && pieces.length === used.length + 2 ? pieces.slice(2) : pieces;
|
|
23
|
+
};
|
|
24
|
+
const parseDateValue = (value, parts = VALUE_ORDER) => {
|
|
25
|
+
if (value === "") return {};
|
|
26
|
+
const used = valueParts(parts);
|
|
27
|
+
const pieces = splitValue(value, used);
|
|
28
|
+
if (pieces.length !== used.length) return void 0;
|
|
29
|
+
return Object.fromEntries(used.map((part, index) => [part, pieces[index]]));
|
|
30
|
+
};
|
|
31
|
+
const toDateValue = (input = "", parts) => typeof input === "string" ? input : formatDateValue(input, parts);
|
|
32
|
+
const toDateParts = (input = "", parts) => typeof input === "string" ? parseDateValue(input, parts) ?? {} : input;
|
|
33
|
+
const toNumber = (value) => /^\d+$/.test(value) ? Number(value) : null;
|
|
34
|
+
const isValidPart = (part, value) => {
|
|
35
|
+
if (part === "year") return /^\d{4}$/.test(value);
|
|
36
|
+
const number = toNumber(value);
|
|
37
|
+
return number !== null && number >= 1 && number <= (part === "day" ? 31 : 12);
|
|
38
|
+
};
|
|
39
|
+
const trimParts = (date) => ({
|
|
40
|
+
day: date.day?.trim() ?? "",
|
|
41
|
+
month: date.month?.trim() ?? "",
|
|
42
|
+
year: date.year?.trim() ?? ""
|
|
43
|
+
});
|
|
44
|
+
const getDateErrors = (values, parts) => {
|
|
45
|
+
if (parts.every((part) => values[part] === "")) return ["missing-date"];
|
|
46
|
+
const errors = [];
|
|
47
|
+
for (const part of parts) if (values[part] === "") errors.push(`missing-${part}`);
|
|
48
|
+
else if (!isValidPart(part, values[part])) errors.push(`invalid-${part}`);
|
|
49
|
+
if (errors.length > 0 || !parts.includes("day") || !parts.includes("month")) return errors;
|
|
50
|
+
const year = parts.includes("year") ? Number(values.year) : 2e3;
|
|
51
|
+
return isExists(year, Number(values.month) - 1, Number(values.day)) ? [] : ["impossible-date"];
|
|
52
|
+
};
|
|
53
|
+
const getInvalidParts = (errors, parts) => parts.filter((part) => errors.some((code) => WHOLE_DATE_ERRORS.has(code) || code.endsWith(part)));
|
|
54
|
+
const pad = (value) => String(Number(value)).padStart(2, "0");
|
|
55
|
+
const toISODate = ({ day, month, year }, parts) => {
|
|
56
|
+
const has = (part) => parts.includes(part);
|
|
57
|
+
if (has("day") && has("month") && has("year")) return `${year}-${pad(month)}-${pad(day)}`;
|
|
58
|
+
if (has("month") && has("year") && !has("day")) return `${year}-${pad(month)}`;
|
|
59
|
+
if (has("day") && has("month")) return `--${pad(month)}-${pad(day)}`;
|
|
60
|
+
if (parts.length === 1 && has("year")) return year;
|
|
61
|
+
return "";
|
|
62
|
+
};
|
|
63
|
+
const toLimit = (limit, name) => {
|
|
64
|
+
const date = typeof limit === "string" ? parseISO(limit) : limit;
|
|
65
|
+
if (!isValid(date)) throw new Error(`${name} must be a valid Date or an ISO date, such as 2020-01-31`);
|
|
66
|
+
return date;
|
|
67
|
+
};
|
|
68
|
+
const formatLimit = (limit, length) => format(limit, length === 4 ? "yyyy" : length === 7 ? "MMMM yyyy" : "d MMMM yyyy");
|
|
69
|
+
const getRangeError = (value, min, max) => {
|
|
70
|
+
if (value.startsWith("--")) return void 0;
|
|
71
|
+
const cut = (limit) => formatISO(limit, { representation: "date" }).slice(0, value.length);
|
|
72
|
+
if (min && value < cut(min)) return "before-min";
|
|
73
|
+
if (max && value > cut(max)) return "after-max";
|
|
74
|
+
};
|
|
75
|
+
const getMessage = (code, label, messages = {}, limits, defaults) => {
|
|
76
|
+
const custom = messages[code];
|
|
77
|
+
if (custom !== void 0) return typeof custom === "function" ? custom(label) : custom;
|
|
78
|
+
const fallback = defaults[code];
|
|
79
|
+
return typeof fallback === "function" ? fallback(label, limits) : fallback;
|
|
80
|
+
};
|
|
81
|
+
//#endregion
|
|
82
|
+
export { WHOLE_DATE_ERRORS, formatDateValue, formatLimit, getDateErrors, getInvalidParts, getMessage, getRangeError, isValidPart, pad, parseDateValue, toDateParts, toDateValue, toISODate, toLimit, toNumber, trimParts };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Limits } from "./dateinput-internal.js";
|
|
2
|
+
//#region src/components/DateInput/dateinput-utils.d.ts
|
|
3
|
+
export type DatePart = "day" | "month" | "year";
|
|
4
|
+
export interface DateParts {
|
|
5
|
+
day?: string;
|
|
6
|
+
month?: string;
|
|
7
|
+
year?: string;
|
|
8
|
+
}
|
|
9
|
+
export type DateInputMode = "dob" | undefined;
|
|
10
|
+
export type DateErrorCode = "missing-date" | "missing-day" | "missing-month" | "missing-year" | "invalid-day" | "invalid-month" | "invalid-year" | "impossible-date" | "unsupported-parts" | "before-min" | "after-max";
|
|
11
|
+
/** A message, or a function that builds one from the label. */
|
|
12
|
+
export type DateErrorMessage = string | ((label?: string) => string);
|
|
13
|
+
export interface DateValidationOptions {
|
|
14
|
+
/** The name of the date, as it would start a sentence, such as "Date
|
|
15
|
+
* of birth".
|
|
16
|
+
*
|
|
17
|
+
* Default: `Date`. */
|
|
18
|
+
label?: string;
|
|
19
|
+
/** The latest date allowed, as a `Date` or an ISO date such as `2026-12-31`.
|
|
20
|
+
*
|
|
21
|
+
* Only the parts used are compared, such as the month and year. */
|
|
22
|
+
max?: Date | string;
|
|
23
|
+
/** Messages to use instead of the defaults. */
|
|
24
|
+
messages?: Partial<Record<DateErrorCode, DateErrorMessage>>;
|
|
25
|
+
/** The earliest date allowed, as a `Date` or an ISO date such as `2020-01-01`.
|
|
26
|
+
*
|
|
27
|
+
* Only the parts used are compared, such as the month and year. */
|
|
28
|
+
min?: Date | string;
|
|
29
|
+
/** The parts used, in the order they are entered.
|
|
30
|
+
*
|
|
31
|
+
* Default: `day`, `month`, `year`. */
|
|
32
|
+
parts?: DatePart[];
|
|
33
|
+
}
|
|
34
|
+
export interface DateValidationSuccess {
|
|
35
|
+
/** Whether the date is valid. */
|
|
36
|
+
valid: true;
|
|
37
|
+
/** The date as an ISO 8601 string, such as 1990-08-21. */
|
|
38
|
+
value: string;
|
|
39
|
+
}
|
|
40
|
+
export interface DateValidationError {
|
|
41
|
+
/** The code for the first problem, in the order the parts are entered. */
|
|
42
|
+
error: DateErrorCode;
|
|
43
|
+
/** The parts with an error. Every part is invalid when the date is
|
|
44
|
+
* empty, impossible, has no ISO format or is outside `min` and `max`. */
|
|
45
|
+
invalid: DatePart[];
|
|
46
|
+
/** The message for the error. */
|
|
47
|
+
message: string;
|
|
48
|
+
/** Whether the date is valid. */
|
|
49
|
+
valid: false;
|
|
50
|
+
}
|
|
51
|
+
export type DateValidation = DateValidationSuccess | DateValidationError;
|
|
52
|
+
export declare const DATE_PARTS: DatePart[];
|
|
53
|
+
export declare const DATE_ERROR_MESSAGES: Record<DateErrorCode, string | ((label?: string, limits?: Limits) => string)>;
|
|
54
|
+
export declare const validateDateParts: (date: DateParts, { label, max, messages, min, parts }?: DateValidationOptions) => DateValidation;
|
|
55
|
+
/** Check a value, such as the DateInput value or the value a form
|
|
56
|
+
* submits, the same way as `validateDateParts`. Use the same `parts` as
|
|
57
|
+
* the DateInput. */
|
|
58
|
+
export declare const validateDate: (value: string, options?: DateValidationOptions) => DateValidation;
|
|
59
|
+
/** As this is a more specific function for a birth of date validation, we can make some assumptions.
|
|
60
|
+
* Such as, date needs to be today or in the past and error message should reflect date of birth.
|
|
61
|
+
* Check a value, such as the DateInput value or the value a form
|
|
62
|
+
* submits, the same way as `validateDateParts`. Use the same `parts` as
|
|
63
|
+
* the DateInput. */
|
|
64
|
+
export declare const validateDob: (value: string, options?: DateValidationOptions) => DateValidation;
|
|
65
|
+
export declare const getAutoComplete: (mode: DateInputMode, part: DatePart) => string | undefined;
|
|
66
|
+
//#endregion
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { formatLimit, getDateErrors, getInvalidParts, getMessage, getRangeError, parseDateValue, toISODate, toLimit, trimParts } from "./dateinput-internal.js";
|
|
2
|
+
//#region src/components/DateInput/dateinput-utils.ts
|
|
3
|
+
const DATE_PARTS = [
|
|
4
|
+
"day",
|
|
5
|
+
"month",
|
|
6
|
+
"year"
|
|
7
|
+
];
|
|
8
|
+
const realDate = (label = "Date") => `${label} must be a real date`;
|
|
9
|
+
const DATE_ERROR_MESSAGES = {
|
|
10
|
+
"after-max": (label = "Date", limits) => `${label} must be on or before ${limits?.max}`,
|
|
11
|
+
"before-min": (label = "Date", limits) => `${label} must be on or after ${limits?.min}`,
|
|
12
|
+
"impossible-date": realDate,
|
|
13
|
+
"invalid-day": realDate,
|
|
14
|
+
"invalid-month": realDate,
|
|
15
|
+
"invalid-year": "Year must include 4 numbers",
|
|
16
|
+
"missing-date": "Enter a date",
|
|
17
|
+
"missing-day": (label = "Date") => `${label} must include a day`,
|
|
18
|
+
"missing-month": (label = "Date") => `${label} must include a month`,
|
|
19
|
+
"missing-year": (label = "Date") => `${label} must include a year`,
|
|
20
|
+
"unsupported-parts": realDate
|
|
21
|
+
};
|
|
22
|
+
const validateDateParts = (date, { label, max, messages = {}, min, parts = DATE_PARTS } = {}) => {
|
|
23
|
+
const minDate = min === void 0 ? void 0 : toLimit(min, "min");
|
|
24
|
+
const maxDate = max === void 0 ? void 0 : toLimit(max, "max");
|
|
25
|
+
const values = trimParts(date);
|
|
26
|
+
const errors = getDateErrors(values, parts);
|
|
27
|
+
let value = "";
|
|
28
|
+
if (errors.length === 0) {
|
|
29
|
+
value = toISODate(values, parts);
|
|
30
|
+
const rangeError = value ? getRangeError(value, minDate, maxDate) : "unsupported-parts";
|
|
31
|
+
if (!rangeError) return {
|
|
32
|
+
valid: true,
|
|
33
|
+
value
|
|
34
|
+
};
|
|
35
|
+
errors.push(rangeError);
|
|
36
|
+
}
|
|
37
|
+
const [error] = errors;
|
|
38
|
+
const limits = {
|
|
39
|
+
max: maxDate && value ? formatLimit(maxDate, value.length) : void 0,
|
|
40
|
+
min: minDate && value ? formatLimit(minDate, value.length) : void 0
|
|
41
|
+
};
|
|
42
|
+
return {
|
|
43
|
+
error,
|
|
44
|
+
invalid: getInvalidParts(errors, parts),
|
|
45
|
+
message: getMessage(error, label, messages, limits, DATE_ERROR_MESSAGES),
|
|
46
|
+
valid: false
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
/** Check a value, such as the DateInput value or the value a form
|
|
50
|
+
* submits, the same way as `validateDateParts`. Use the same `parts` as
|
|
51
|
+
* the DateInput. */
|
|
52
|
+
const validateDate = (value, options = {}) => {
|
|
53
|
+
const date = parseDateValue(value, options.parts);
|
|
54
|
+
if (date) return validateDateParts(date, options);
|
|
55
|
+
const { label, messages, parts = DATE_PARTS } = options;
|
|
56
|
+
return {
|
|
57
|
+
error: "impossible-date",
|
|
58
|
+
invalid: parts,
|
|
59
|
+
message: getMessage("impossible-date", label, messages, {}, DATE_ERROR_MESSAGES),
|
|
60
|
+
valid: false
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
/** As this is a more specific function for a birth of date validation, we can make some assumptions.
|
|
64
|
+
* Such as, date needs to be today or in the past and error message should reflect date of birth.
|
|
65
|
+
* Check a value, such as the DateInput value or the value a form
|
|
66
|
+
* submits, the same way as `validateDateParts`. Use the same `parts` as
|
|
67
|
+
* the DateInput. */
|
|
68
|
+
const validateDob = (value, options = {}) => {
|
|
69
|
+
const { messages, ...rest } = options;
|
|
70
|
+
return validateDate(value, {
|
|
71
|
+
label: "Date of birth",
|
|
72
|
+
max: /* @__PURE__ */ new Date(),
|
|
73
|
+
messages: {
|
|
74
|
+
"after-max": "Date of birth must be today or in the past",
|
|
75
|
+
"missing-date": "Enter your date of birth",
|
|
76
|
+
...messages
|
|
77
|
+
},
|
|
78
|
+
parts: DATE_PARTS,
|
|
79
|
+
...rest
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
const getAutoComplete = (mode, part) => {
|
|
83
|
+
if (!mode) return void 0;
|
|
84
|
+
if (mode === "dob") {
|
|
85
|
+
if (part === "day") return "bday-day";
|
|
86
|
+
if (part === "month") return "bday-month";
|
|
87
|
+
if (part === "year") return "bday-year";
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
//#endregion
|
|
91
|
+
export { DATE_ERROR_MESSAGES, DATE_PARTS, getAutoComplete, validateDate, validateDateParts, validateDob };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { DATE_ERROR_MESSAGES, DATE_PARTS, DateErrorCode, DateErrorMessage, DateInputMode, DatePart, DateParts, DateValidation, DateValidationError, DateValidationOptions, DateValidationSuccess, getAutoComplete, validateDate, validateDateParts, validateDob } from "./dateinput-utils.js";
|
|
2
|
+
import { DateInput, DateInputChangeEvent, DateInputDay, DateInputMonth, DateInputPartProps, DateInputProps, DateInputYear } from "./DateInput.js";
|
|
3
|
+
export { DATE_ERROR_MESSAGES, DATE_PARTS, DateErrorCode, DateErrorMessage, DateInput, DateInput as default, DateInputChangeEvent, DateInputDay, DateInputMode, DateInputMonth, DateInputPartProps, DateInputProps, DateInputYear, DatePart, DateParts, DateValidation, DateValidationError, DateValidationOptions, DateValidationSuccess, getAutoComplete, validateDate, validateDateParts, validateDob };
|