@trackunit/react-form-components 1.22.2 → 1.22.4

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.
Files changed (42) hide show
  1. package/index.cjs.js +74 -102
  2. package/index.esm.js +76 -101
  3. package/package.json +2 -2
  4. package/src/components/DateField/DateBaseInput/DateBaseInput.d.ts +2 -1
  5. package/src/components/DateField/DateField.d.ts +7 -1
  6. package/src/components/DateField/dateFieldInputUtils.d.ts +22 -0
  7. package/src/index.d.ts +0 -1
  8. package/translation.cjs.js +0 -1
  9. package/translation.cjs10.js +0 -1
  10. package/translation.cjs11.js +0 -1
  11. package/translation.cjs12.js +0 -1
  12. package/translation.cjs13.js +0 -1
  13. package/translation.cjs14.js +0 -1
  14. package/translation.cjs15.js +0 -1
  15. package/translation.cjs16.js +0 -1
  16. package/translation.cjs17.js +0 -1
  17. package/translation.cjs2.js +0 -1
  18. package/translation.cjs3.js +0 -1
  19. package/translation.cjs4.js +0 -1
  20. package/translation.cjs5.js +0 -1
  21. package/translation.cjs6.js +0 -1
  22. package/translation.cjs7.js +0 -1
  23. package/translation.cjs8.js +0 -1
  24. package/translation.cjs9.js +0 -1
  25. package/translation.esm.js +0 -1
  26. package/translation.esm10.js +0 -1
  27. package/translation.esm11.js +0 -1
  28. package/translation.esm12.js +0 -1
  29. package/translation.esm13.js +0 -1
  30. package/translation.esm14.js +0 -1
  31. package/translation.esm15.js +0 -1
  32. package/translation.esm16.js +0 -1
  33. package/translation.esm17.js +0 -1
  34. package/translation.esm2.js +0 -1
  35. package/translation.esm3.js +0 -1
  36. package/translation.esm4.js +0 -1
  37. package/translation.esm5.js +0 -1
  38. package/translation.esm6.js +0 -1
  39. package/translation.esm7.js +0 -1
  40. package/translation.esm8.js +0 -1
  41. package/translation.esm9.js +0 -1
  42. package/src/utilities/parseDateFieldValue.d.ts +0 -26
package/index.cjs.js CHANGED
@@ -30,7 +30,7 @@ var defaultTranslations = {
30
30
  "dateField.actions.apply": "Apply",
31
31
  "dateField.actions.cancel": "Cancel",
32
32
  "dateField.actions.clear": "Clear",
33
- "dateField.placeholder": "yyyy-mm-dd",
33
+ "dateField.placeholder": "mm dd, yyyy",
34
34
  "dropzone.input.title": "Drag-and-drop file input",
35
35
  "dropzone.label.default": "<clickable>Browse</clickable> or drag files here...",
36
36
  "emailField.error.INVALID_EMAIL": "Please enter a valid email address",
@@ -141,76 +141,6 @@ function createInputChangeEvent(value, sourceInput) {
141
141
  };
142
142
  }
143
143
 
144
- const ISO_DATE_REGEX = /^(\d{4})-(\d{2})-(\d{2})$/;
145
- function parseAndValidateYYYYMMDD(value) {
146
- if (value === "")
147
- return null;
148
- const isoMatch = ISO_DATE_REGEX.exec(value);
149
- if (!isoMatch)
150
- return null;
151
- const [, y, m, d] = isoMatch;
152
- if (y === undefined || m === undefined || d === undefined)
153
- return null;
154
- const year = Number.parseInt(y, 10);
155
- const month = Number.parseInt(m, 10) - 1;
156
- const day = Number.parseInt(d, 10);
157
- if (month < 0 || month > 11 || day < 1 || day > 31)
158
- return null;
159
- const local = new Date(year, month, day);
160
- if (Number.isNaN(local.getTime()))
161
- return null;
162
- if (local.getFullYear() !== year || local.getMonth() !== month || local.getDate() !== day) {
163
- return null;
164
- }
165
- return { year, month: month + 1, day };
166
- }
167
- /**
168
- * Parses the value from a DateField change event (YYYY-MM-DD string) to a Date at local midnight.
169
- * Use this when you need a Date from event.target.value to avoid timezone shifts that occur
170
- * with `new Date(value)` (which interprets the string as UTC).
171
- *
172
- * @param value - The string from event.target.value (e.g. "2025-03-07" or "")
173
- * @returns {Date | null} - Date at local midnight for the given day, or null if value is empty or invalid
174
- */
175
- function parseDateFieldValue(value) {
176
- const parsed = parseAndValidateYYYYMMDD(value);
177
- if (!parsed)
178
- return null;
179
- return new Date(parsed.year, parsed.month - 1, parsed.day);
180
- }
181
- /**
182
- * Converts a YYYY-MM-DD string (e.g. from DateField) to an ISO string at UTC midnight for that calendar day.
183
- * Use this when storing or sending date-only values so that the calendar day is preserved regardless of
184
- * user timezone (unlike calling .toISOString() on a local-midnight Date, which shifts the day for UTC+).
185
- *
186
- * @param value - The string from event.target.value (e.g. "2025-03-07" or "")
187
- * @returns {string | null} - "YYYY-MM-DDT00:00:00.000Z" or null if value is empty or invalid
188
- */
189
- function toISODateStringUTC(value) {
190
- const parsed = parseAndValidateYYYYMMDD(value);
191
- if (!parsed)
192
- return null;
193
- const { year, month, day } = parsed;
194
- const mm = String(month).padStart(2, "0");
195
- const dd = String(day).padStart(2, "0");
196
- return `${year}-${mm}-${dd}T00:00:00.000Z`;
197
- }
198
- /**
199
- * Converts a Date (e.g. at local midnight) to an ISO string at UTC midnight for the same calendar day.
200
- * Use when sending a date-only value to an API that expects UTC midnight (e.g. booking date).
201
- *
202
- * @param date - A Date instance (typically local midnight from a date picker)
203
- * @returns {string} "YYYY-MM-DDT00:00:00.000Z" for that calendar day
204
- */
205
- function dateToISODateUTC(date) {
206
- const year = date.getFullYear();
207
- const month = date.getMonth() + 1;
208
- const day = date.getDate();
209
- const mm = String(month).padStart(2, "0");
210
- const dd = String(day).padStart(2, "0");
211
- return `${year}-${mm}-${dd}T00:00:00.000Z`;
212
- }
213
-
214
144
  const cvaInputBase = cssClassVarianceUtilities.cvaMerge([
215
145
  "component-baseInput-shadow",
216
146
  "component-baseInput-border",
@@ -596,6 +526,57 @@ const BaseInput = ({ className, isInvalid = false, "data-testid": dataTestId, pr
596
526
  };
597
527
  BaseInput.displayName = "BaseInput";
598
528
 
529
+ const normalizeDisplayCompare = (value) => value
530
+ .normalize("NFKC")
531
+ .replace(/[\u202f\u00a0]/g, " ")
532
+ .replace(/\s+/g, " ")
533
+ .trim();
534
+ /**
535
+ * Parses date-field keyboard input to a local-midnight {@link Date}.
536
+ *
537
+ * Strict `YYYY-MM-DD` is accepted first; otherwise the string must round-trip with
538
+ * {@link formatShortDateUtil} for the given `locale` (i.e. equal the canonical localized
539
+ * display the field would render). Arbitrary `new Date(string)`-parseable inputs that don't
540
+ * match the locale's format return `null` to avoid accepting confusable formats like `03/07/2025`.
541
+ *
542
+ * @param value - Raw text from the input.
543
+ * @param locale - Locale to compare against when the input is not `YYYY-MM-DD`.
544
+ */
545
+ const parseDateFieldInputForChange = (value, locale) => {
546
+ const fromIso = dateAndTimeUtils.parseYYYYMMDDUtil(value);
547
+ if (fromIso !== null)
548
+ return fromIso;
549
+ const trimmed = value.trim();
550
+ if (trimmed === "")
551
+ return null;
552
+ const parsed = dateAndTimeUtils.parseValidDate(trimmed);
553
+ if (parsed === null)
554
+ return null;
555
+ const localMidnight = dateAndTimeUtils.toDateUtil(dateAndTimeUtils.startOfDayUtil(parsed));
556
+ const formatted = dateAndTimeUtils.formatShortDateUtil(localMidnight, locale);
557
+ if (normalizeDisplayCompare(formatted) === normalizeDisplayCompare(trimmed)) {
558
+ return localMidnight;
559
+ }
560
+ return null;
561
+ };
562
+ /**
563
+ * Renders the stored date-field value for display:
564
+ * - empty string → empty
565
+ * - full canonical `YYYY-MM-DD` → locale-aware short date
566
+ * - partial input (e.g. `"2025-03"`) → returned as-is so the user can keep typing
567
+ *
568
+ * @param stored - The current canonical value (typically from `event.target.value`).
569
+ * @param locale - Locale for the display format.
570
+ */
571
+ const dateFieldStoredValueToDisplayString = (stored, locale) => {
572
+ if (stored === "")
573
+ return "";
574
+ const parsed = dateAndTimeUtils.parseYYYYMMDDUtil(stored);
575
+ if (parsed !== null)
576
+ return dateAndTimeUtils.formatShortDateUtil(parsed, locale);
577
+ return stored;
578
+ };
579
+
599
580
  function parseToDate(v) {
600
581
  if (v === undefined || v === "")
601
582
  return undefined;
@@ -606,23 +587,12 @@ function parseToDate(v) {
606
587
  return Number.isNaN(d.getTime()) ? undefined : d;
607
588
  }
608
589
  const str = String(v);
609
- const fromField = parseDateFieldValue(str);
590
+ const fromField = dateAndTimeUtils.parseYYYYMMDDUtil(str);
610
591
  if (fromField !== null)
611
592
  return fromField;
612
593
  const fallback = new Date(str);
613
594
  return Number.isNaN(fallback.getTime()) ? undefined : fallback;
614
595
  }
615
- function formatToInputString(d) {
616
- if (!d)
617
- return "";
618
- return dateAndTimeUtils.Temporal.PlainDateTime.from({
619
- year: d.getFullYear(),
620
- month: d.getMonth() + 1,
621
- day: d.getDate(),
622
- })
623
- .toPlainDate()
624
- .toString();
625
- }
626
596
  function startOfDayMs(d) {
627
597
  return dateAndTimeUtils.toDateUtil(dateAndTimeUtils.startOfDayUtil(d)).getTime();
628
598
  }
@@ -640,28 +610,27 @@ function clampToRange(date, minDate, maxDate) {
640
610
  /**
641
611
  * A wrapper around BaseInput with a pop-up day picker using the same calendar UI as DayPicker.
642
612
  *
643
- * The value is formatted to an ISO date string (YYYY-MM-DD)
613
+ * The input shows a locale-aware date (short month, numeric day and year, e.g. "Mar 23, 2026" in English).
614
+ * `onChange` still emits the canonical YYYY-MM-DD string for form parsing.
644
615
  *
645
616
  * NOTE: If shown with a label, please use the `DateField` component instead.
646
617
  */
647
618
  const DateBaseInput = ({ min, max, defaultValue, value, ref, onChange, suffix: suffixProp, "data-testid": dataTestId, ...rest }) => {
648
619
  const isControlled = value !== undefined;
649
- const [internalValue, setInternalValue] = react.useState(() => formatToInputString(parseToDate(defaultValue)));
620
+ const [internalValue, setInternalValue] = react.useState(() => dateAndTimeUtils.dateToYYYYMMDDUtil(parseToDate(defaultValue)));
650
621
  const [pendingDate, setPendingDate] = react.useState(null);
622
+ const [t, i18n] = useTranslation();
623
+ const locale = i18n.resolvedLanguage ?? i18n.language;
651
624
  // For controlled string value, only normalize when it's strict YYYY-MM-DD so partial input (e.g. "2025-03") is preserved.
652
625
  const resolvedValue = isControlled
653
626
  ? typeof value === "string"
654
- ? (() => {
655
- const strict = parseDateFieldValue(value);
656
- return strict !== null ? formatToInputString(strict) : value;
657
- })()
658
- : formatToInputString(parseToDate(value))
659
- : internalValue;
627
+ ? dateFieldStoredValueToDisplayString(value, locale)
628
+ : dateFieldStoredValueToDisplayString(dateAndTimeUtils.dateToYYYYMMDDUtil(parseToDate(value)), locale)
629
+ : dateFieldStoredValueToDisplayString(internalValue, locale);
660
630
  const selectedDate = isControlled && typeof value === "string"
661
- ? (parseDateFieldValue(value) ?? undefined)
631
+ ? (dateAndTimeUtils.parseYYYYMMDDUtil(value) ?? undefined)
662
632
  : parseToDate(isControlled ? value : internalValue);
663
633
  const inputRef = react.useRef(null);
664
- const [t] = useTranslation();
665
634
  react.useImperativeHandle(ref, () => inputRef.current ?? document.createElement("input"), []);
666
635
  const syncPendingFromValue = react.useCallback(() => {
667
636
  setPendingDate(selectedDate ?? null);
@@ -695,7 +664,7 @@ const DateBaseInput = ({ min, max, defaultValue, value, ref, onChange, suffix: s
695
664
  const minDate = parseToDate(min);
696
665
  const maxDate = parseToDate(max);
697
666
  const clamped = clampToRange(pendingDate, minDate, maxDate);
698
- const str = clamped ? formatToInputString(clamped) : "";
667
+ const str = clamped ? dateAndTimeUtils.dateToYYYYMMDDUtil(clamped) : "";
699
668
  if (!isControlled)
700
669
  setInternalValue(str);
701
670
  onChange?.(createInputChangeEvent(str, inputRef.current));
@@ -703,12 +672,12 @@ const DateBaseInput = ({ min, max, defaultValue, value, ref, onChange, suffix: s
703
672
  }, [isControlled, min, max, onChange, pendingDate]);
704
673
  const handleInputChange = react.useCallback((e) => {
705
674
  const raw = e.target.value;
706
- const parsed = parseDateFieldValue(raw);
675
+ const parsed = parseDateFieldInputForChange(raw, locale);
707
676
  const minDate = parseToDate(min);
708
677
  const maxDate = parseToDate(max);
709
678
  if (parsed !== null) {
710
679
  const clamped = clampToRange(parsed, minDate, maxDate);
711
- const str = clamped ? formatToInputString(clamped) : "";
680
+ const str = clamped ? dateAndTimeUtils.dateToYYYYMMDDUtil(clamped) : "";
712
681
  if (!isControlled)
713
682
  setInternalValue(str);
714
683
  onChange?.(createInputChangeEvent(str, inputRef.current));
@@ -718,10 +687,10 @@ const DateBaseInput = ({ min, max, defaultValue, value, ref, onChange, suffix: s
718
687
  setInternalValue(raw);
719
688
  onChange?.(e);
720
689
  }
721
- }, [isControlled, min, max, onChange]);
690
+ }, [isControlled, locale, min, max, onChange]);
722
691
  return (jsxRuntime.jsxs(reactComponents.Popover, { onOpenStateChange: open => open && syncPendingFromValue(), placement: "bottom-start", children: [jsxRuntime.jsx(reactComponents.PopoverTrigger, { children: jsxRuntime.jsx("div", { className: tailwindMerge.twMerge("flex w-full min-w-0 cursor-pointer items-center", (Boolean(rest.disabled) || Boolean(rest.readOnly)) && "pointer-events-none"), children: jsxRuntime.jsx(BaseInput, { ...rest, "aria-readonly": true, className: tailwindMerge.twMerge("w-full min-w-0", rest.className), "data-testid": dataTestId ? `${dataTestId}-input` : undefined, onChange: handleInputChange, placeholder: rest.placeholder ?? t("dateField.placeholder"), ref: inputRef, suffix: suffixProp ?? (jsxRuntime.jsx(reactComponents.Icon, { "aria-label": undefined, className: Boolean(rest.disabled) || Boolean(rest.readOnly) ? "text-neutral-500" : undefined, "data-testid": dataTestId ? `${dataTestId}-calendar` : "calendar", name: "Calendar", size: "medium", type: "solid" })), type: "text", value: resolvedValue }) }) }), jsxRuntime.jsx(reactComponents.PopoverContent, { children: closePopover => {
723
692
  const displayDate = pendingDate ?? selectedDate ?? null;
724
- return (jsxRuntime.jsxs("div", { className: tailwindMerge.twMerge("flex w-min flex-col overflow-hidden rounded-md border border-neutral-300 bg-white p-0"), children: [jsxRuntime.jsx(ReactCalendar, { allowPartialRange: true, className: tailwindMerge.twMerge("custom-day-picker", "range-picker", "p-0"), defaultActiveStartDate: displayDate ?? undefined, defaultView: "month", onChange: val => {
693
+ return (jsxRuntime.jsxs("div", { className: tailwindMerge.twMerge("flex w-min flex-col overflow-hidden rounded-md border border-neutral-300 bg-white p-0"), children: [jsxRuntime.jsx(ReactCalendar, { allowPartialRange: true, className: tailwindMerge.twMerge("custom-day-picker", "range-picker", "p-0"), defaultActiveStartDate: displayDate ?? undefined, defaultView: "month", locale: locale, onChange: val => {
725
694
  const next = val instanceof Date ? val : Array.isArray(val) ? (val[0] instanceof Date ? val[0] : null) : null;
726
695
  handleCalendarChange(next);
727
696
  }, selectRange: false, tileDisabled: tileDisabled, value: displayDate }), jsxRuntime.jsx("hr", {}), jsxRuntime.jsxs("div", { className: "flex w-full justify-between gap-2 px-4 py-3", children: [jsxRuntime.jsx(reactComponents.Button, { className: "mr-auto", "data-testid": dataTestId ? `${dataTestId}-clear-button` : undefined, onClick: () => handleClear(closePopover), size: "small", variant: "secondary", children: t("dateField.actions.clear") }), jsxRuntime.jsxs("div", { className: "flex gap-2", children: [jsxRuntime.jsx(reactComponents.Button, { "data-testid": dataTestId ? `${dataTestId}-cancel-button` : undefined, onClick: () => handleCancel(closePopover), size: "small", variant: "ghost-neutral", children: t("dateField.actions.cancel") }), jsxRuntime.jsx(reactComponents.Button, { "data-testid": dataTestId ? `${dataTestId}-apply-button` : undefined, onClick: () => handleApply(closePopover), size: "small", children: t("dateField.actions.apply") })] })] })] }));
@@ -2504,6 +2473,10 @@ ColorField.displayName = "ColorField";
2504
2473
 
2505
2474
  /**
2506
2475
  * The date field component is used for entering date values with a calendar picker (same UI as DayPicker).
2476
+ * The input shows a localized short date; when the user commits a valid day, `onChange` provides `YYYY-MM-DD`
2477
+ * on `event.target.value` (use `parseYYYYMMDDUtil` from `@trackunit/date-and-time-utils` for a local-midnight
2478
+ * `Date`). Do not parse `event.target.value` from `onBlur` — the visible string may be localized while
2479
+ * `onChange` stays canonical.
2507
2480
  *
2508
2481
  * ### When to use
2509
2482
  * Use DateField for selecting calendar dates such as birthdates, deadlines, or scheduling.
@@ -2531,11 +2504,13 @@ ColorField.displayName = "ColorField";
2531
2504
  * @example Date field with constraints
2532
2505
  * ```tsx
2533
2506
  * import { DateField } from "@trackunit/react-form-components";
2507
+ * import { dateToYYYYMMDDUtil } from "@trackunit/date-and-time-utils";
2534
2508
  * import { useState } from "react";
2535
2509
  *
2536
2510
  * const BookingForm = () => {
2537
2511
  * const [checkIn, setCheckIn] = useState("");
2538
- * const today = new Date().toISOString().split("T")[0];
2512
+ * // Use the local calendar day, not UTC (avoids day-shift in non-UTC zones).
2513
+ * const today = dateToYYYYMMDDUtil(new Date());
2539
2514
  *
2540
2515
  * return (
2541
2516
  * <DateField
@@ -5009,17 +4984,14 @@ exports.cvaSelectPlaceholder = cvaSelectPlaceholder;
5009
4984
  exports.cvaSelectPrefixSuffix = cvaSelectPrefixSuffix;
5010
4985
  exports.cvaSelectSingleValue = cvaSelectSingleValue;
5011
4986
  exports.cvaSelectValueContainer = cvaSelectValueContainer;
5012
- exports.dateToISODateUTC = dateToISODateUTC;
5013
4987
  exports.getCountryAbbreviation = getCountryAbbreviation;
5014
4988
  exports.getPhoneNumberWithPlus = getPhoneNumberWithPlus;
5015
4989
  exports.isInvalidCountryCode = isInvalidCountryCode;
5016
4990
  exports.isInvalidPhoneNumber = isInvalidPhoneNumber;
5017
4991
  exports.isValidHEXColor = isValidHEXColor;
5018
- exports.parseDateFieldValue = parseDateFieldValue;
5019
4992
  exports.parseSchedule = parseSchedule;
5020
4993
  exports.phoneErrorMessage = phoneErrorMessage;
5021
4994
  exports.serializeSchedule = serializeSchedule;
5022
- exports.toISODateStringUTC = toISODateStringUTC;
5023
4995
  exports.useCreatableSelect = useCreatableSelect;
5024
4996
  exports.useCustomComponents = useCustomComponents;
5025
4997
  exports.useGetPhoneValidationRules = useGetPhoneValidationRules;
package/index.esm.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
2
  import { registerTranslations, useNamespaceTranslation, NamespaceTrans } from '@trackunit/i18n-library-translation';
3
- import { Temporal, toDateUtil, startOfDayUtil } from '@trackunit/date-and-time-utils';
3
+ import { parseYYYYMMDDUtil, formatShortDateUtil, parseValidDate, toDateUtil, startOfDayUtil, dateToYYYYMMDDUtil } from '@trackunit/date-and-time-utils';
4
4
  import { IconButton, Icon, Tooltip, Popover, PopoverTrigger, PopoverContent, Button, cvaMenu, cvaMenuList, Tag, useIsTextTruncated, ZStack, MenuItem, useMeasure, useDebounce, useMergeRefs, Spinner, useScrollBlock, Text, Heading, useIsFirstRender } from '@trackunit/react-components';
5
5
  import { useRef, useEffect, useImperativeHandle, useState, useCallback, cloneElement, isValidElement, useLayoutEffect, useMemo, useReducer, createContext, useContext, useId } from 'react';
6
6
  import ReactCalendar from 'react-calendar';
@@ -29,7 +29,7 @@ var defaultTranslations = {
29
29
  "dateField.actions.apply": "Apply",
30
30
  "dateField.actions.cancel": "Cancel",
31
31
  "dateField.actions.clear": "Clear",
32
- "dateField.placeholder": "yyyy-mm-dd",
32
+ "dateField.placeholder": "mm dd, yyyy",
33
33
  "dropzone.input.title": "Drag-and-drop file input",
34
34
  "dropzone.label.default": "<clickable>Browse</clickable> or drag files here...",
35
35
  "emailField.error.INVALID_EMAIL": "Please enter a valid email address",
@@ -140,76 +140,6 @@ function createInputChangeEvent(value, sourceInput) {
140
140
  };
141
141
  }
142
142
 
143
- const ISO_DATE_REGEX = /^(\d{4})-(\d{2})-(\d{2})$/;
144
- function parseAndValidateYYYYMMDD(value) {
145
- if (value === "")
146
- return null;
147
- const isoMatch = ISO_DATE_REGEX.exec(value);
148
- if (!isoMatch)
149
- return null;
150
- const [, y, m, d] = isoMatch;
151
- if (y === undefined || m === undefined || d === undefined)
152
- return null;
153
- const year = Number.parseInt(y, 10);
154
- const month = Number.parseInt(m, 10) - 1;
155
- const day = Number.parseInt(d, 10);
156
- if (month < 0 || month > 11 || day < 1 || day > 31)
157
- return null;
158
- const local = new Date(year, month, day);
159
- if (Number.isNaN(local.getTime()))
160
- return null;
161
- if (local.getFullYear() !== year || local.getMonth() !== month || local.getDate() !== day) {
162
- return null;
163
- }
164
- return { year, month: month + 1, day };
165
- }
166
- /**
167
- * Parses the value from a DateField change event (YYYY-MM-DD string) to a Date at local midnight.
168
- * Use this when you need a Date from event.target.value to avoid timezone shifts that occur
169
- * with `new Date(value)` (which interprets the string as UTC).
170
- *
171
- * @param value - The string from event.target.value (e.g. "2025-03-07" or "")
172
- * @returns {Date | null} - Date at local midnight for the given day, or null if value is empty or invalid
173
- */
174
- function parseDateFieldValue(value) {
175
- const parsed = parseAndValidateYYYYMMDD(value);
176
- if (!parsed)
177
- return null;
178
- return new Date(parsed.year, parsed.month - 1, parsed.day);
179
- }
180
- /**
181
- * Converts a YYYY-MM-DD string (e.g. from DateField) to an ISO string at UTC midnight for that calendar day.
182
- * Use this when storing or sending date-only values so that the calendar day is preserved regardless of
183
- * user timezone (unlike calling .toISOString() on a local-midnight Date, which shifts the day for UTC+).
184
- *
185
- * @param value - The string from event.target.value (e.g. "2025-03-07" or "")
186
- * @returns {string | null} - "YYYY-MM-DDT00:00:00.000Z" or null if value is empty or invalid
187
- */
188
- function toISODateStringUTC(value) {
189
- const parsed = parseAndValidateYYYYMMDD(value);
190
- if (!parsed)
191
- return null;
192
- const { year, month, day } = parsed;
193
- const mm = String(month).padStart(2, "0");
194
- const dd = String(day).padStart(2, "0");
195
- return `${year}-${mm}-${dd}T00:00:00.000Z`;
196
- }
197
- /**
198
- * Converts a Date (e.g. at local midnight) to an ISO string at UTC midnight for the same calendar day.
199
- * Use when sending a date-only value to an API that expects UTC midnight (e.g. booking date).
200
- *
201
- * @param date - A Date instance (typically local midnight from a date picker)
202
- * @returns {string} "YYYY-MM-DDT00:00:00.000Z" for that calendar day
203
- */
204
- function dateToISODateUTC(date) {
205
- const year = date.getFullYear();
206
- const month = date.getMonth() + 1;
207
- const day = date.getDate();
208
- const mm = String(month).padStart(2, "0");
209
- const dd = String(day).padStart(2, "0");
210
- return `${year}-${mm}-${dd}T00:00:00.000Z`;
211
- }
212
-
213
143
  const cvaInputBase = cvaMerge([
214
144
  "component-baseInput-shadow",
215
145
  "component-baseInput-border",
@@ -595,6 +525,57 @@ const BaseInput = ({ className, isInvalid = false, "data-testid": dataTestId, pr
595
525
  };
596
526
  BaseInput.displayName = "BaseInput";
597
527
 
528
+ const normalizeDisplayCompare = (value) => value
529
+ .normalize("NFKC")
530
+ .replace(/[\u202f\u00a0]/g, " ")
531
+ .replace(/\s+/g, " ")
532
+ .trim();
533
+ /**
534
+ * Parses date-field keyboard input to a local-midnight {@link Date}.
535
+ *
536
+ * Strict `YYYY-MM-DD` is accepted first; otherwise the string must round-trip with
537
+ * {@link formatShortDateUtil} for the given `locale` (i.e. equal the canonical localized
538
+ * display the field would render). Arbitrary `new Date(string)`-parseable inputs that don't
539
+ * match the locale's format return `null` to avoid accepting confusable formats like `03/07/2025`.
540
+ *
541
+ * @param value - Raw text from the input.
542
+ * @param locale - Locale to compare against when the input is not `YYYY-MM-DD`.
543
+ */
544
+ const parseDateFieldInputForChange = (value, locale) => {
545
+ const fromIso = parseYYYYMMDDUtil(value);
546
+ if (fromIso !== null)
547
+ return fromIso;
548
+ const trimmed = value.trim();
549
+ if (trimmed === "")
550
+ return null;
551
+ const parsed = parseValidDate(trimmed);
552
+ if (parsed === null)
553
+ return null;
554
+ const localMidnight = toDateUtil(startOfDayUtil(parsed));
555
+ const formatted = formatShortDateUtil(localMidnight, locale);
556
+ if (normalizeDisplayCompare(formatted) === normalizeDisplayCompare(trimmed)) {
557
+ return localMidnight;
558
+ }
559
+ return null;
560
+ };
561
+ /**
562
+ * Renders the stored date-field value for display:
563
+ * - empty string → empty
564
+ * - full canonical `YYYY-MM-DD` → locale-aware short date
565
+ * - partial input (e.g. `"2025-03"`) → returned as-is so the user can keep typing
566
+ *
567
+ * @param stored - The current canonical value (typically from `event.target.value`).
568
+ * @param locale - Locale for the display format.
569
+ */
570
+ const dateFieldStoredValueToDisplayString = (stored, locale) => {
571
+ if (stored === "")
572
+ return "";
573
+ const parsed = parseYYYYMMDDUtil(stored);
574
+ if (parsed !== null)
575
+ return formatShortDateUtil(parsed, locale);
576
+ return stored;
577
+ };
578
+
598
579
  function parseToDate(v) {
599
580
  if (v === undefined || v === "")
600
581
  return undefined;
@@ -605,23 +586,12 @@ function parseToDate(v) {
605
586
  return Number.isNaN(d.getTime()) ? undefined : d;
606
587
  }
607
588
  const str = String(v);
608
- const fromField = parseDateFieldValue(str);
589
+ const fromField = parseYYYYMMDDUtil(str);
609
590
  if (fromField !== null)
610
591
  return fromField;
611
592
  const fallback = new Date(str);
612
593
  return Number.isNaN(fallback.getTime()) ? undefined : fallback;
613
594
  }
614
- function formatToInputString(d) {
615
- if (!d)
616
- return "";
617
- return Temporal.PlainDateTime.from({
618
- year: d.getFullYear(),
619
- month: d.getMonth() + 1,
620
- day: d.getDate(),
621
- })
622
- .toPlainDate()
623
- .toString();
624
- }
625
595
  function startOfDayMs(d) {
626
596
  return toDateUtil(startOfDayUtil(d)).getTime();
627
597
  }
@@ -639,28 +609,27 @@ function clampToRange(date, minDate, maxDate) {
639
609
  /**
640
610
  * A wrapper around BaseInput with a pop-up day picker using the same calendar UI as DayPicker.
641
611
  *
642
- * The value is formatted to an ISO date string (YYYY-MM-DD)
612
+ * The input shows a locale-aware date (short month, numeric day and year, e.g. "Mar 23, 2026" in English).
613
+ * `onChange` still emits the canonical YYYY-MM-DD string for form parsing.
643
614
  *
644
615
  * NOTE: If shown with a label, please use the `DateField` component instead.
645
616
  */
646
617
  const DateBaseInput = ({ min, max, defaultValue, value, ref, onChange, suffix: suffixProp, "data-testid": dataTestId, ...rest }) => {
647
618
  const isControlled = value !== undefined;
648
- const [internalValue, setInternalValue] = useState(() => formatToInputString(parseToDate(defaultValue)));
619
+ const [internalValue, setInternalValue] = useState(() => dateToYYYYMMDDUtil(parseToDate(defaultValue)));
649
620
  const [pendingDate, setPendingDate] = useState(null);
621
+ const [t, i18n] = useTranslation();
622
+ const locale = i18n.resolvedLanguage ?? i18n.language;
650
623
  // For controlled string value, only normalize when it's strict YYYY-MM-DD so partial input (e.g. "2025-03") is preserved.
651
624
  const resolvedValue = isControlled
652
625
  ? typeof value === "string"
653
- ? (() => {
654
- const strict = parseDateFieldValue(value);
655
- return strict !== null ? formatToInputString(strict) : value;
656
- })()
657
- : formatToInputString(parseToDate(value))
658
- : internalValue;
626
+ ? dateFieldStoredValueToDisplayString(value, locale)
627
+ : dateFieldStoredValueToDisplayString(dateToYYYYMMDDUtil(parseToDate(value)), locale)
628
+ : dateFieldStoredValueToDisplayString(internalValue, locale);
659
629
  const selectedDate = isControlled && typeof value === "string"
660
- ? (parseDateFieldValue(value) ?? undefined)
630
+ ? (parseYYYYMMDDUtil(value) ?? undefined)
661
631
  : parseToDate(isControlled ? value : internalValue);
662
632
  const inputRef = useRef(null);
663
- const [t] = useTranslation();
664
633
  useImperativeHandle(ref, () => inputRef.current ?? document.createElement("input"), []);
665
634
  const syncPendingFromValue = useCallback(() => {
666
635
  setPendingDate(selectedDate ?? null);
@@ -694,7 +663,7 @@ const DateBaseInput = ({ min, max, defaultValue, value, ref, onChange, suffix: s
694
663
  const minDate = parseToDate(min);
695
664
  const maxDate = parseToDate(max);
696
665
  const clamped = clampToRange(pendingDate, minDate, maxDate);
697
- const str = clamped ? formatToInputString(clamped) : "";
666
+ const str = clamped ? dateToYYYYMMDDUtil(clamped) : "";
698
667
  if (!isControlled)
699
668
  setInternalValue(str);
700
669
  onChange?.(createInputChangeEvent(str, inputRef.current));
@@ -702,12 +671,12 @@ const DateBaseInput = ({ min, max, defaultValue, value, ref, onChange, suffix: s
702
671
  }, [isControlled, min, max, onChange, pendingDate]);
703
672
  const handleInputChange = useCallback((e) => {
704
673
  const raw = e.target.value;
705
- const parsed = parseDateFieldValue(raw);
674
+ const parsed = parseDateFieldInputForChange(raw, locale);
706
675
  const minDate = parseToDate(min);
707
676
  const maxDate = parseToDate(max);
708
677
  if (parsed !== null) {
709
678
  const clamped = clampToRange(parsed, minDate, maxDate);
710
- const str = clamped ? formatToInputString(clamped) : "";
679
+ const str = clamped ? dateToYYYYMMDDUtil(clamped) : "";
711
680
  if (!isControlled)
712
681
  setInternalValue(str);
713
682
  onChange?.(createInputChangeEvent(str, inputRef.current));
@@ -717,10 +686,10 @@ const DateBaseInput = ({ min, max, defaultValue, value, ref, onChange, suffix: s
717
686
  setInternalValue(raw);
718
687
  onChange?.(e);
719
688
  }
720
- }, [isControlled, min, max, onChange]);
689
+ }, [isControlled, locale, min, max, onChange]);
721
690
  return (jsxs(Popover, { onOpenStateChange: open => open && syncPendingFromValue(), placement: "bottom-start", children: [jsx(PopoverTrigger, { children: jsx("div", { className: twMerge("flex w-full min-w-0 cursor-pointer items-center", (Boolean(rest.disabled) || Boolean(rest.readOnly)) && "pointer-events-none"), children: jsx(BaseInput, { ...rest, "aria-readonly": true, className: twMerge("w-full min-w-0", rest.className), "data-testid": dataTestId ? `${dataTestId}-input` : undefined, onChange: handleInputChange, placeholder: rest.placeholder ?? t("dateField.placeholder"), ref: inputRef, suffix: suffixProp ?? (jsx(Icon, { "aria-label": undefined, className: Boolean(rest.disabled) || Boolean(rest.readOnly) ? "text-neutral-500" : undefined, "data-testid": dataTestId ? `${dataTestId}-calendar` : "calendar", name: "Calendar", size: "medium", type: "solid" })), type: "text", value: resolvedValue }) }) }), jsx(PopoverContent, { children: closePopover => {
722
691
  const displayDate = pendingDate ?? selectedDate ?? null;
723
- return (jsxs("div", { className: twMerge("flex w-min flex-col overflow-hidden rounded-md border border-neutral-300 bg-white p-0"), children: [jsx(ReactCalendar, { allowPartialRange: true, className: twMerge("custom-day-picker", "range-picker", "p-0"), defaultActiveStartDate: displayDate ?? undefined, defaultView: "month", onChange: val => {
692
+ return (jsxs("div", { className: twMerge("flex w-min flex-col overflow-hidden rounded-md border border-neutral-300 bg-white p-0"), children: [jsx(ReactCalendar, { allowPartialRange: true, className: twMerge("custom-day-picker", "range-picker", "p-0"), defaultActiveStartDate: displayDate ?? undefined, defaultView: "month", locale: locale, onChange: val => {
724
693
  const next = val instanceof Date ? val : Array.isArray(val) ? (val[0] instanceof Date ? val[0] : null) : null;
725
694
  handleCalendarChange(next);
726
695
  }, selectRange: false, tileDisabled: tileDisabled, value: displayDate }), jsx("hr", {}), jsxs("div", { className: "flex w-full justify-between gap-2 px-4 py-3", children: [jsx(Button, { className: "mr-auto", "data-testid": dataTestId ? `${dataTestId}-clear-button` : undefined, onClick: () => handleClear(closePopover), size: "small", variant: "secondary", children: t("dateField.actions.clear") }), jsxs("div", { className: "flex gap-2", children: [jsx(Button, { "data-testid": dataTestId ? `${dataTestId}-cancel-button` : undefined, onClick: () => handleCancel(closePopover), size: "small", variant: "ghost-neutral", children: t("dateField.actions.cancel") }), jsx(Button, { "data-testid": dataTestId ? `${dataTestId}-apply-button` : undefined, onClick: () => handleApply(closePopover), size: "small", children: t("dateField.actions.apply") })] })] })] }));
@@ -2503,6 +2472,10 @@ ColorField.displayName = "ColorField";
2503
2472
 
2504
2473
  /**
2505
2474
  * The date field component is used for entering date values with a calendar picker (same UI as DayPicker).
2475
+ * The input shows a localized short date; when the user commits a valid day, `onChange` provides `YYYY-MM-DD`
2476
+ * on `event.target.value` (use `parseYYYYMMDDUtil` from `@trackunit/date-and-time-utils` for a local-midnight
2477
+ * `Date`). Do not parse `event.target.value` from `onBlur` — the visible string may be localized while
2478
+ * `onChange` stays canonical.
2506
2479
  *
2507
2480
  * ### When to use
2508
2481
  * Use DateField for selecting calendar dates such as birthdates, deadlines, or scheduling.
@@ -2530,11 +2503,13 @@ ColorField.displayName = "ColorField";
2530
2503
  * @example Date field with constraints
2531
2504
  * ```tsx
2532
2505
  * import { DateField } from "@trackunit/react-form-components";
2506
+ * import { dateToYYYYMMDDUtil } from "@trackunit/date-and-time-utils";
2533
2507
  * import { useState } from "react";
2534
2508
  *
2535
2509
  * const BookingForm = () => {
2536
2510
  * const [checkIn, setCheckIn] = useState("");
2537
- * const today = new Date().toISOString().split("T")[0];
2511
+ * // Use the local calendar day, not UTC (avoids day-shift in non-UTC zones).
2512
+ * const today = dateToYYYYMMDDUtil(new Date());
2538
2513
  *
2539
2514
  * return (
2540
2515
  * <DateField
@@ -4930,4 +4905,4 @@ const useZodValidators = () => {
4930
4905
  */
4931
4906
  setupLibraryTranslations();
4932
4907
 
4933
- export { ActionButton, BaseInput, BaseSelect, Checkbox, CheckboxField, ColorField, CreatableSelect, CreatableSelectField, DEFAULT_TIME, DateBaseInput, DateField, DropZone, DropZoneDefaultLabel, EMAIL_REGEX, EmailField, FormFieldSelectAdapter, FormGroup, Label, MultiSelectField, NumberBaseInput, NumberField, OptionCard, PasswordBaseInput, PasswordField, PhoneBaseInput, PhoneField, PhoneFieldWithController, RadioGroup, RadioItem, Schedule, ScheduleVariant, Search, SelectField, TextAreaBaseInput, TextAreaField, TextBaseInput, TextField, TimeRange, TimeRangeField, ToggleSwitch, ToggleSwitchOption, UploadField, UploadInput, UrlField, checkIfPhoneNumberHasPlus, countryCodeToFlagEmoji, cvaAccessoriesContainer, cvaActionButton, cvaActionContainer, cvaInput$1 as cvaInput, cvaInputAddon, cvaInputBase, cvaInputBaseDisabled, cvaInputBaseInvalid, cvaInputBaseReadOnly, cvaInputBaseSize, cvaInputElement, cvaInputGroup, cvaInputItemPlacementManager, cvaInputPrefix, cvaInputSuffix, cvaLabel, cvaRadioItem, cvaSelectClearIndicator, cvaSelectContainer, cvaSelectControl, cvaSelectDropdownIconContainer, cvaSelectDropdownIndicator, cvaSelectIndicatorsContainer, cvaSelectLoadingMessage, cvaSelectMenu, cvaSelectMenuList, cvaSelectMultiValue, cvaSelectNoOptionsMessage, cvaSelectPlaceholder, cvaSelectPrefixSuffix, cvaSelectSingleValue, cvaSelectValueContainer, dateToISODateUTC, getCountryAbbreviation, getPhoneNumberWithPlus, isInvalidCountryCode, isInvalidPhoneNumber, isValidHEXColor, parseDateFieldValue, parseSchedule, phoneErrorMessage, serializeSchedule, toISODateStringUTC, useCreatableSelect, useCustomComponents, useGetPhoneValidationRules, usePhoneInput, useRadioItemChecked, useSelect, useZodValidators, validateEmailAddress, validatePhoneNumber, weekDay };
4908
+ export { ActionButton, BaseInput, BaseSelect, Checkbox, CheckboxField, ColorField, CreatableSelect, CreatableSelectField, DEFAULT_TIME, DateBaseInput, DateField, DropZone, DropZoneDefaultLabel, EMAIL_REGEX, EmailField, FormFieldSelectAdapter, FormGroup, Label, MultiSelectField, NumberBaseInput, NumberField, OptionCard, PasswordBaseInput, PasswordField, PhoneBaseInput, PhoneField, PhoneFieldWithController, RadioGroup, RadioItem, Schedule, ScheduleVariant, Search, SelectField, TextAreaBaseInput, TextAreaField, TextBaseInput, TextField, TimeRange, TimeRangeField, ToggleSwitch, ToggleSwitchOption, UploadField, UploadInput, UrlField, checkIfPhoneNumberHasPlus, countryCodeToFlagEmoji, cvaAccessoriesContainer, cvaActionButton, cvaActionContainer, cvaInput$1 as cvaInput, cvaInputAddon, cvaInputBase, cvaInputBaseDisabled, cvaInputBaseInvalid, cvaInputBaseReadOnly, cvaInputBaseSize, cvaInputElement, cvaInputGroup, cvaInputItemPlacementManager, cvaInputPrefix, cvaInputSuffix, cvaLabel, cvaRadioItem, cvaSelectClearIndicator, cvaSelectContainer, cvaSelectControl, cvaSelectDropdownIconContainer, cvaSelectDropdownIndicator, cvaSelectIndicatorsContainer, cvaSelectLoadingMessage, cvaSelectMenu, cvaSelectMenuList, cvaSelectMultiValue, cvaSelectNoOptionsMessage, cvaSelectPlaceholder, cvaSelectPrefixSuffix, cvaSelectSingleValue, cvaSelectValueContainer, getCountryAbbreviation, getPhoneNumberWithPlus, isInvalidCountryCode, isInvalidPhoneNumber, isValidHEXColor, parseSchedule, phoneErrorMessage, serializeSchedule, useCreatableSelect, useCustomComponents, useGetPhoneValidationRules, usePhoneInput, useRadioItemChecked, useSelect, useZodValidators, validateEmailAddress, validatePhoneNumber, weekDay };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trackunit/react-form-components",
3
- "version": "1.22.2",
3
+ "version": "1.22.4",
4
4
  "repository": "https://github.com/Trackunit/manager",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "engines": {
@@ -9,7 +9,7 @@
9
9
  "dependencies": {
10
10
  "react-calendar": "^6.0.0",
11
11
  "react-select": "^5.10.2",
12
- "@trackunit/date-and-time-utils": "1.11.101",
12
+ "@trackunit/date-and-time-utils": "1.11.102",
13
13
  "usehooks-ts": "^3.1.0",
14
14
  "libphonenumber-js": "^1.12.22",
15
15
  "zod": "^3.25.76",
@@ -22,7 +22,8 @@ export interface DateBaseInputProps extends BaseInputExposedProps {
22
22
  /**
23
23
  * A wrapper around BaseInput with a pop-up day picker using the same calendar UI as DayPicker.
24
24
  *
25
- * The value is formatted to an ISO date string (YYYY-MM-DD)
25
+ * The input shows a locale-aware date (short month, numeric day and year, e.g. "Mar 23, 2026" in English).
26
+ * `onChange` still emits the canonical YYYY-MM-DD string for form parsing.
26
27
  *
27
28
  * NOTE: If shown with a label, please use the `DateField` component instead.
28
29
  */
@@ -10,6 +10,10 @@ export interface DateFieldProps extends DateBaseInputProps, FormGroupExposedProp
10
10
  }
11
11
  /**
12
12
  * The date field component is used for entering date values with a calendar picker (same UI as DayPicker).
13
+ * The input shows a localized short date; when the user commits a valid day, `onChange` provides `YYYY-MM-DD`
14
+ * on `event.target.value` (use `parseYYYYMMDDUtil` from `@trackunit/date-and-time-utils` for a local-midnight
15
+ * `Date`). Do not parse `event.target.value` from `onBlur` — the visible string may be localized while
16
+ * `onChange` stays canonical.
13
17
  *
14
18
  * ### When to use
15
19
  * Use DateField for selecting calendar dates such as birthdates, deadlines, or scheduling.
@@ -37,11 +41,13 @@ export interface DateFieldProps extends DateBaseInputProps, FormGroupExposedProp
37
41
  * @example Date field with constraints
38
42
  * ```tsx
39
43
  * import { DateField } from "@trackunit/react-form-components";
44
+ * import { dateToYYYYMMDDUtil } from "@trackunit/date-and-time-utils";
40
45
  * import { useState } from "react";
41
46
  *
42
47
  * const BookingForm = () => {
43
48
  * const [checkIn, setCheckIn] = useState("");
44
- * const today = new Date().toISOString().split("T")[0];
49
+ * // Use the local calendar day, not UTC (avoids day-shift in non-UTC zones).
50
+ * const today = dateToYYYYMMDDUtil(new Date());
45
51
  *
46
52
  * return (
47
53
  * <DateField
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Parses date-field keyboard input to a local-midnight {@link Date}.
3
+ *
4
+ * Strict `YYYY-MM-DD` is accepted first; otherwise the string must round-trip with
5
+ * {@link formatShortDateUtil} for the given `locale` (i.e. equal the canonical localized
6
+ * display the field would render). Arbitrary `new Date(string)`-parseable inputs that don't
7
+ * match the locale's format return `null` to avoid accepting confusable formats like `03/07/2025`.
8
+ *
9
+ * @param value - Raw text from the input.
10
+ * @param locale - Locale to compare against when the input is not `YYYY-MM-DD`.
11
+ */
12
+ export declare const parseDateFieldInputForChange: (value: string, locale: string | undefined) => Date | null;
13
+ /**
14
+ * Renders the stored date-field value for display:
15
+ * - empty string → empty
16
+ * - full canonical `YYYY-MM-DD` → locale-aware short date
17
+ * - partial input (e.g. `"2025-03"`) → returned as-is so the user can keep typing
18
+ *
19
+ * @param stored - The current canonical value (typically from `event.target.value`).
20
+ * @param locale - Locale for the display format.
21
+ */
22
+ export declare const dateFieldStoredValueToDisplayString: (stored: string, locale: string | undefined) => string;
package/src/index.d.ts CHANGED
@@ -53,7 +53,6 @@ export * from "./components/UploadField/UploadField";
53
53
  export * from "./components/UploadInput/UploadInput";
54
54
  export * from "./components/UrlField/UrlField";
55
55
  export * from "./utilities/emailUtils";
56
- export * from "./utilities/parseDateFieldValue";
57
56
  export * from "./utilities/useGetPhoneValidationRules";
58
57
  export * from "./utilities/usePhoneInput";
59
58
  export * from "./utilities/useZodValidators";
@@ -9,7 +9,6 @@ var translation = {
9
9
  "dateField.actions.apply": "Anwenden",
10
10
  "dateField.actions.cancel": "Abbrechen",
11
11
  "dateField.actions.clear": "Löschen",
12
- "dateField.placeholder": "yyyy-mm-dd",
13
12
  "dropzone.input.title": "Dateien per Drag & Drop hinzufügen",
14
13
  "dropzone.label.default": "<clickable>Ordner durchsuchen</clickable> oder Dateien per Drag & Drop einfügen …",
15
14
  "emailField.error.INVALID_EMAIL": "Bitte geben Sie eine gültige E-Mail-Adresse ein",
@@ -9,7 +9,6 @@ var translation = {
9
9
  "dateField.actions.apply": "Zastosuj",
10
10
  "dateField.actions.cancel": "Anuluj",
11
11
  "dateField.actions.clear": "Wyczyść",
12
- "dateField.placeholder": "yyyy-mm-dd",
13
12
  "dropzone.input.title": "Przeciągnij i upuść dane pliku",
14
13
  "dropzone.label.default": "<clickable>Przeglądaj</clickable> lub przeciągnij pliki tutaj...",
15
14
  "emailField.error.INVALID_EMAIL": "Wprowadź poprawny adres e-mail",
@@ -9,7 +9,6 @@ var translation = {
9
9
  "dateField.actions.apply": "Aplicar",
10
10
  "dateField.actions.cancel": "Cancelar",
11
11
  "dateField.actions.clear": "Apagar",
12
- "dateField.placeholder": "yyyy-mm-dd",
13
12
  "dropzone.input.title": "Arrastar-e-soltar entrada de ficheiro",
14
13
  "dropzone.label.default": "<clickable>Procure</clickable> ou arraste os ficheiros aqui...",
15
14
  "emailField.error.INVALID_EMAIL": "Introduza um endereço de e-mail válido",
@@ -9,7 +9,6 @@ var translation = {
9
9
  "dateField.actions.apply": "Применить",
10
10
  "dateField.actions.cancel": "Отмена",
11
11
  "dateField.actions.clear": "Очистить",
12
- "dateField.placeholder": "yyyy-mm-dd",
13
12
  "dropzone.input.title": "Перетаскивание ввода файла",
14
13
  "dropzone.label.default": "<clickable>Просмотрите</clickable> или перетащите файлы сюда...",
15
14
  "emailField.error.INVALID_EMAIL": "Введите действительный адрес эл. почты",
@@ -9,7 +9,6 @@ var translation = {
9
9
  "dateField.actions.apply": "Aplicare",
10
10
  "dateField.actions.cancel": "Anulare",
11
11
  "dateField.actions.clear": "Ștergere",
12
- "dateField.placeholder": "yyyy-mm-dd",
13
12
  "dropzone.input.title": "Intrare fișier drag-and-drop",
14
13
  "dropzone.label.default": "<clickable>Răsfoiți</clickable> sau trageți fișiere aici...",
15
14
  "emailField.error.INVALID_EMAIL": "Vă rugăm să introduceți o adresă de e-mail valabilă",
@@ -9,7 +9,6 @@ var translation = {
9
9
  "dateField.actions.apply": "Aplicar",
10
10
  "dateField.actions.cancel": "Cancelar",
11
11
  "dateField.actions.clear": "Restablecer",
12
- "dateField.placeholder": "yyyy-mm-dd",
13
12
  "dropzone.input.title": "Entrada Arrastrar y soltar archivo",
14
13
  "dropzone.label.default": "<clickable>Explore</clickable> o arrastre archivos aquí...",
15
14
  "emailField.error.INVALID_EMAIL": "Introduzca una dirección de correo electrónico válida",
@@ -9,7 +9,6 @@ var translation = {
9
9
  "dateField.actions.apply": "Tillämpa",
10
10
  "dateField.actions.cancel": "Avbryt",
11
11
  "dateField.actions.clear": "Rensa",
12
- "dateField.placeholder": "yyyy-mm-dd",
13
12
  "dropzone.input.title": "Dra och släpp fil",
14
13
  "dropzone.label.default": "<clickable>Bläddra</clickable> bland filerna eller dra dem hit...",
15
14
  "emailField.error.INVALID_EMAIL": "Ange en giltig e-postadress",
@@ -9,7 +9,6 @@ var translation = {
9
9
  "dateField.actions.apply": "適用",
10
10
  "dateField.actions.cancel": "キャンセル",
11
11
  "dateField.actions.clear": "クリア",
12
- "dateField.placeholder": "yyyy-mm-dd",
13
12
  "dropzone.input.title": "ドラッグ&ドロップによるファイル入力",
14
13
  "dropzone.label.default": "<clickable>ファイルを選択</clickable>またはここにファイルをドラッグ",
15
14
  "emailField.error.INVALID_EMAIL": "有効なメールアドレスを入力してください",
@@ -9,7 +9,6 @@ var translation = {
9
9
  "dateField.actions.apply": "นำไปใช้",
10
10
  "dateField.actions.cancel": "ยกเลิก",
11
11
  "dateField.actions.clear": "ล้าง",
12
- "dateField.placeholder": "yyyy-mm-dd",
13
12
  "dropzone.input.title": "ลากและวางอินพุตไฟล์",
14
13
  "dropzone.label.default": "<clickable>เรียกดู</clickable> หรือลากไฟล์มาที่นี่...",
15
14
  "emailField.error.INVALID_EMAIL": "โปรดป้อนที่อยู่อีเมลที่ถูกต้อง",
@@ -9,7 +9,6 @@ var translation = {
9
9
  "dateField.actions.apply": "Anvend",
10
10
  "dateField.actions.cancel": "Annuller",
11
11
  "dateField.actions.clear": "Ryd",
12
- "dateField.placeholder": "yyyy-mm-dd",
13
12
  "dropzone.input.title": "Filinput via drag-and-drop",
14
13
  "dropzone.label.default": "<clickable>Gennemse</clickable> eller træk filer her...",
15
14
  "emailField.error.INVALID_EMAIL": "Please enter a valid email address",
@@ -9,7 +9,6 @@ var translation = {
9
9
  "dateField.actions.apply": "Použít",
10
10
  "dateField.actions.cancel": "Zrušit",
11
11
  "dateField.actions.clear": "Vymazat",
12
- "dateField.placeholder": "yyyy-mm-dd",
13
12
  "dropzone.input.title": "Přetažení vstupu do souboru",
14
13
  "dropzone.label.default": "<clickable>Procházejte</clickable> nebo přetáhněte soubory zde...",
15
14
  "emailField.error.INVALID_EMAIL": "Zadejte platnou e-mailovou adresu",
@@ -9,7 +9,6 @@ var translation = {
9
9
  "dateField.actions.apply": "Toepassen",
10
10
  "dateField.actions.cancel": "Annuleren",
11
11
  "dateField.actions.clear": "Wissen",
12
- "dateField.placeholder": "yyyy-mm-dd",
13
12
  "dropzone.input.title": "Drag-en-drop bestandsinvoer",
14
13
  "dropzone.label.default": "<clickable>Bladeren</clickable> of sleep bestanden hier naartoe...",
15
14
  "emailField.error.INVALID_EMAIL": "Please enter a valid email address",
@@ -9,7 +9,6 @@ var translation = {
9
9
  "dateField.actions.apply": "Appliquer",
10
10
  "dateField.actions.cancel": "Annuler",
11
11
  "dateField.actions.clear": "Effacer",
12
- "dateField.placeholder": "yyyy-mm-dd",
13
12
  "dropzone.input.title": "Ajout de fichiers par glisser-déposer",
14
13
  "dropzone.label.default": "<clickable>Parcourir</clickable> ou faire glisser les fichiers ici...",
15
14
  "emailField.error.INVALID_EMAIL": "Veuillez saisir une adresse e-mail valide",
@@ -9,7 +9,6 @@ var translation = {
9
9
  "dateField.actions.apply": "Käytä",
10
10
  "dateField.actions.cancel": "Peruuta",
11
11
  "dateField.actions.clear": "Tyhjennä",
12
- "dateField.placeholder": "yyyy-mm-dd",
13
12
  "dropzone.input.title": "Vedä ja pudota tiedostosyöte",
14
13
  "dropzone.label.default": "<clickable>Selaa</clickable> tai vedä tiedostoja täältä...",
15
14
  "emailField.error.INVALID_EMAIL": "Please enter a valid email address",
@@ -9,7 +9,6 @@ var translation = {
9
9
  "dateField.actions.apply": "Alkalmaz",
10
10
  "dateField.actions.cancel": "Mégse",
11
11
  "dateField.actions.clear": "Törlés",
12
- "dateField.placeholder": "yyyy-mm-dd",
13
12
  "dropzone.input.title": "Húzza ide a fájl(oka)t",
14
13
  "dropzone.label.default": "<clickable>Keresse meg</clickable> vagy húza ide a fájlokat…",
15
14
  "emailField.error.INVALID_EMAIL": "Please enter a valid email address",
@@ -9,7 +9,6 @@ var translation = {
9
9
  "dateField.actions.apply": "Applica",
10
10
  "dateField.actions.cancel": "Annulla",
11
11
  "dateField.actions.clear": "Cancella",
12
- "dateField.placeholder": "yyyy-mm-dd",
13
12
  "dropzone.input.title": "Inserimento di file tramite Trascina e rilascia",
14
13
  "dropzone.label.default": "<clickable>Sfoglia</clickable> o trascina i file qui...",
15
14
  "emailField.error.INVALID_EMAIL": "Inserisci un indirizzo email valido",
@@ -9,7 +9,6 @@ var translation = {
9
9
  "dateField.actions.apply": "Bruk",
10
10
  "dateField.actions.cancel": "Avbryt",
11
11
  "dateField.actions.clear": "Tøm",
12
- "dateField.placeholder": "yyyy-mm-dd",
13
12
  "dropzone.input.title": "Dra og slipp for å legge inn fil",
14
13
  "dropzone.label.default": "<clickable>Bla gjennom</clickable> eller dra filer hit...",
15
14
  "emailField.error.INVALID_EMAIL": "Please enter a valid email address",
@@ -7,7 +7,6 @@ var translation = {
7
7
  "dateField.actions.apply": "Anwenden",
8
8
  "dateField.actions.cancel": "Abbrechen",
9
9
  "dateField.actions.clear": "Löschen",
10
- "dateField.placeholder": "yyyy-mm-dd",
11
10
  "dropzone.input.title": "Dateien per Drag & Drop hinzufügen",
12
11
  "dropzone.label.default": "<clickable>Ordner durchsuchen</clickable> oder Dateien per Drag & Drop einfügen …",
13
12
  "emailField.error.INVALID_EMAIL": "Bitte geben Sie eine gültige E-Mail-Adresse ein",
@@ -7,7 +7,6 @@ var translation = {
7
7
  "dateField.actions.apply": "Zastosuj",
8
8
  "dateField.actions.cancel": "Anuluj",
9
9
  "dateField.actions.clear": "Wyczyść",
10
- "dateField.placeholder": "yyyy-mm-dd",
11
10
  "dropzone.input.title": "Przeciągnij i upuść dane pliku",
12
11
  "dropzone.label.default": "<clickable>Przeglądaj</clickable> lub przeciągnij pliki tutaj...",
13
12
  "emailField.error.INVALID_EMAIL": "Wprowadź poprawny adres e-mail",
@@ -7,7 +7,6 @@ var translation = {
7
7
  "dateField.actions.apply": "Aplicar",
8
8
  "dateField.actions.cancel": "Cancelar",
9
9
  "dateField.actions.clear": "Apagar",
10
- "dateField.placeholder": "yyyy-mm-dd",
11
10
  "dropzone.input.title": "Arrastar-e-soltar entrada de ficheiro",
12
11
  "dropzone.label.default": "<clickable>Procure</clickable> ou arraste os ficheiros aqui...",
13
12
  "emailField.error.INVALID_EMAIL": "Introduza um endereço de e-mail válido",
@@ -7,7 +7,6 @@ var translation = {
7
7
  "dateField.actions.apply": "Применить",
8
8
  "dateField.actions.cancel": "Отмена",
9
9
  "dateField.actions.clear": "Очистить",
10
- "dateField.placeholder": "yyyy-mm-dd",
11
10
  "dropzone.input.title": "Перетаскивание ввода файла",
12
11
  "dropzone.label.default": "<clickable>Просмотрите</clickable> или перетащите файлы сюда...",
13
12
  "emailField.error.INVALID_EMAIL": "Введите действительный адрес эл. почты",
@@ -7,7 +7,6 @@ var translation = {
7
7
  "dateField.actions.apply": "Aplicare",
8
8
  "dateField.actions.cancel": "Anulare",
9
9
  "dateField.actions.clear": "Ștergere",
10
- "dateField.placeholder": "yyyy-mm-dd",
11
10
  "dropzone.input.title": "Intrare fișier drag-and-drop",
12
11
  "dropzone.label.default": "<clickable>Răsfoiți</clickable> sau trageți fișiere aici...",
13
12
  "emailField.error.INVALID_EMAIL": "Vă rugăm să introduceți o adresă de e-mail valabilă",
@@ -7,7 +7,6 @@ var translation = {
7
7
  "dateField.actions.apply": "Aplicar",
8
8
  "dateField.actions.cancel": "Cancelar",
9
9
  "dateField.actions.clear": "Restablecer",
10
- "dateField.placeholder": "yyyy-mm-dd",
11
10
  "dropzone.input.title": "Entrada Arrastrar y soltar archivo",
12
11
  "dropzone.label.default": "<clickable>Explore</clickable> o arrastre archivos aquí...",
13
12
  "emailField.error.INVALID_EMAIL": "Introduzca una dirección de correo electrónico válida",
@@ -7,7 +7,6 @@ var translation = {
7
7
  "dateField.actions.apply": "Tillämpa",
8
8
  "dateField.actions.cancel": "Avbryt",
9
9
  "dateField.actions.clear": "Rensa",
10
- "dateField.placeholder": "yyyy-mm-dd",
11
10
  "dropzone.input.title": "Dra och släpp fil",
12
11
  "dropzone.label.default": "<clickable>Bläddra</clickable> bland filerna eller dra dem hit...",
13
12
  "emailField.error.INVALID_EMAIL": "Ange en giltig e-postadress",
@@ -7,7 +7,6 @@ var translation = {
7
7
  "dateField.actions.apply": "適用",
8
8
  "dateField.actions.cancel": "キャンセル",
9
9
  "dateField.actions.clear": "クリア",
10
- "dateField.placeholder": "yyyy-mm-dd",
11
10
  "dropzone.input.title": "ドラッグ&ドロップによるファイル入力",
12
11
  "dropzone.label.default": "<clickable>ファイルを選択</clickable>またはここにファイルをドラッグ",
13
12
  "emailField.error.INVALID_EMAIL": "有効なメールアドレスを入力してください",
@@ -7,7 +7,6 @@ var translation = {
7
7
  "dateField.actions.apply": "นำไปใช้",
8
8
  "dateField.actions.cancel": "ยกเลิก",
9
9
  "dateField.actions.clear": "ล้าง",
10
- "dateField.placeholder": "yyyy-mm-dd",
11
10
  "dropzone.input.title": "ลากและวางอินพุตไฟล์",
12
11
  "dropzone.label.default": "<clickable>เรียกดู</clickable> หรือลากไฟล์มาที่นี่...",
13
12
  "emailField.error.INVALID_EMAIL": "โปรดป้อนที่อยู่อีเมลที่ถูกต้อง",
@@ -7,7 +7,6 @@ var translation = {
7
7
  "dateField.actions.apply": "Anvend",
8
8
  "dateField.actions.cancel": "Annuller",
9
9
  "dateField.actions.clear": "Ryd",
10
- "dateField.placeholder": "yyyy-mm-dd",
11
10
  "dropzone.input.title": "Filinput via drag-and-drop",
12
11
  "dropzone.label.default": "<clickable>Gennemse</clickable> eller træk filer her...",
13
12
  "emailField.error.INVALID_EMAIL": "Please enter a valid email address",
@@ -7,7 +7,6 @@ var translation = {
7
7
  "dateField.actions.apply": "Použít",
8
8
  "dateField.actions.cancel": "Zrušit",
9
9
  "dateField.actions.clear": "Vymazat",
10
- "dateField.placeholder": "yyyy-mm-dd",
11
10
  "dropzone.input.title": "Přetažení vstupu do souboru",
12
11
  "dropzone.label.default": "<clickable>Procházejte</clickable> nebo přetáhněte soubory zde...",
13
12
  "emailField.error.INVALID_EMAIL": "Zadejte platnou e-mailovou adresu",
@@ -7,7 +7,6 @@ var translation = {
7
7
  "dateField.actions.apply": "Toepassen",
8
8
  "dateField.actions.cancel": "Annuleren",
9
9
  "dateField.actions.clear": "Wissen",
10
- "dateField.placeholder": "yyyy-mm-dd",
11
10
  "dropzone.input.title": "Drag-en-drop bestandsinvoer",
12
11
  "dropzone.label.default": "<clickable>Bladeren</clickable> of sleep bestanden hier naartoe...",
13
12
  "emailField.error.INVALID_EMAIL": "Please enter a valid email address",
@@ -7,7 +7,6 @@ var translation = {
7
7
  "dateField.actions.apply": "Appliquer",
8
8
  "dateField.actions.cancel": "Annuler",
9
9
  "dateField.actions.clear": "Effacer",
10
- "dateField.placeholder": "yyyy-mm-dd",
11
10
  "dropzone.input.title": "Ajout de fichiers par glisser-déposer",
12
11
  "dropzone.label.default": "<clickable>Parcourir</clickable> ou faire glisser les fichiers ici...",
13
12
  "emailField.error.INVALID_EMAIL": "Veuillez saisir une adresse e-mail valide",
@@ -7,7 +7,6 @@ var translation = {
7
7
  "dateField.actions.apply": "Käytä",
8
8
  "dateField.actions.cancel": "Peruuta",
9
9
  "dateField.actions.clear": "Tyhjennä",
10
- "dateField.placeholder": "yyyy-mm-dd",
11
10
  "dropzone.input.title": "Vedä ja pudota tiedostosyöte",
12
11
  "dropzone.label.default": "<clickable>Selaa</clickable> tai vedä tiedostoja täältä...",
13
12
  "emailField.error.INVALID_EMAIL": "Please enter a valid email address",
@@ -7,7 +7,6 @@ var translation = {
7
7
  "dateField.actions.apply": "Alkalmaz",
8
8
  "dateField.actions.cancel": "Mégse",
9
9
  "dateField.actions.clear": "Törlés",
10
- "dateField.placeholder": "yyyy-mm-dd",
11
10
  "dropzone.input.title": "Húzza ide a fájl(oka)t",
12
11
  "dropzone.label.default": "<clickable>Keresse meg</clickable> vagy húza ide a fájlokat…",
13
12
  "emailField.error.INVALID_EMAIL": "Please enter a valid email address",
@@ -7,7 +7,6 @@ var translation = {
7
7
  "dateField.actions.apply": "Applica",
8
8
  "dateField.actions.cancel": "Annulla",
9
9
  "dateField.actions.clear": "Cancella",
10
- "dateField.placeholder": "yyyy-mm-dd",
11
10
  "dropzone.input.title": "Inserimento di file tramite Trascina e rilascia",
12
11
  "dropzone.label.default": "<clickable>Sfoglia</clickable> o trascina i file qui...",
13
12
  "emailField.error.INVALID_EMAIL": "Inserisci un indirizzo email valido",
@@ -7,7 +7,6 @@ var translation = {
7
7
  "dateField.actions.apply": "Bruk",
8
8
  "dateField.actions.cancel": "Avbryt",
9
9
  "dateField.actions.clear": "Tøm",
10
- "dateField.placeholder": "yyyy-mm-dd",
11
10
  "dropzone.input.title": "Dra og slipp for å legge inn fil",
12
11
  "dropzone.label.default": "<clickable>Bla gjennom</clickable> eller dra filer hit...",
13
12
  "emailField.error.INVALID_EMAIL": "Please enter a valid email address",
@@ -1,26 +0,0 @@
1
- /**
2
- * Parses the value from a DateField change event (YYYY-MM-DD string) to a Date at local midnight.
3
- * Use this when you need a Date from event.target.value to avoid timezone shifts that occur
4
- * with `new Date(value)` (which interprets the string as UTC).
5
- *
6
- * @param value - The string from event.target.value (e.g. "2025-03-07" or "")
7
- * @returns {Date | null} - Date at local midnight for the given day, or null if value is empty or invalid
8
- */
9
- export declare function parseDateFieldValue(value: string): Date | null;
10
- /**
11
- * Converts a YYYY-MM-DD string (e.g. from DateField) to an ISO string at UTC midnight for that calendar day.
12
- * Use this when storing or sending date-only values so that the calendar day is preserved regardless of
13
- * user timezone (unlike calling .toISOString() on a local-midnight Date, which shifts the day for UTC+).
14
- *
15
- * @param value - The string from event.target.value (e.g. "2025-03-07" or "")
16
- * @returns {string | null} - "YYYY-MM-DDT00:00:00.000Z" or null if value is empty or invalid
17
- */
18
- export declare function toISODateStringUTC(value: string): string | null;
19
- /**
20
- * Converts a Date (e.g. at local midnight) to an ISO string at UTC midnight for the same calendar day.
21
- * Use when sending a date-only value to an API that expects UTC midnight (e.g. booking date).
22
- *
23
- * @param date - A Date instance (typically local midnight from a date picker)
24
- * @returns {string} "YYYY-MM-DDT00:00:00.000Z" for that calendar day
25
- */
26
- export declare function dateToISODateUTC(date: Date): string;