@khanacademy/wonder-blocks-date-picker 0.1.0 → 0.1.2
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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @khanacademy/wonder-blocks-date-picker
|
|
2
2
|
|
|
3
|
+
## 0.1.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 0cce8bf: Adds fixes for localized DatePicker inputs, which were hard-coded to English. Improves keyboard accessibility and validation for a variety of date formats.
|
|
8
|
+
|
|
9
|
+
## 0.1.1
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Updated dependencies [8bb7ada]
|
|
14
|
+
- @khanacademy/wonder-blocks-tokens@15.0.0
|
|
15
|
+
- @khanacademy/wonder-blocks-form@7.5.3
|
|
16
|
+
- @khanacademy/wonder-blocks-icon@5.3.7
|
|
17
|
+
- @khanacademy/wonder-blocks-modal@8.5.14
|
|
18
|
+
- @khanacademy/wonder-blocks-styles@0.2.38
|
|
19
|
+
|
|
3
20
|
## 0.1.0
|
|
4
21
|
|
|
5
22
|
### Minor Changes
|
|
@@ -82,6 +82,14 @@ interface Props {
|
|
|
82
82
|
* Test ID used for testing.
|
|
83
83
|
*/
|
|
84
84
|
testId?: string;
|
|
85
|
+
/**
|
|
86
|
+
* Whether to reset invalid text to the last valid value on blur.
|
|
87
|
+
*
|
|
88
|
+
* - If `true` (default): Invalid text and out-of-range dates reset to the last valid value
|
|
89
|
+
* - If `false`: Invalid/unparseable text and out-of-range dates stay in the field,
|
|
90
|
+
* and onChange is called to notify the parent
|
|
91
|
+
*/
|
|
92
|
+
resetInvalidValueOnBlur?: boolean;
|
|
85
93
|
/**
|
|
86
94
|
* Used to define a string that labels the current element.
|
|
87
95
|
*/
|
|
@@ -11,12 +11,36 @@ interface Props {
|
|
|
11
11
|
*/
|
|
12
12
|
locale?: Locale;
|
|
13
13
|
/**
|
|
14
|
-
* When the selected date changes, this callback is
|
|
14
|
+
* When the selected date changes, this callback is passed a Temporal object
|
|
15
15
|
* for midnight on the selected date, set to the user's local time zone.
|
|
16
|
+
*
|
|
17
|
+
* Note: This callback is called as the user types based on resetInvalidValueOnBlur:
|
|
18
|
+
*
|
|
19
|
+
* With resetInvalidValueOnBlur={false}:
|
|
20
|
+
* - Called immediately for all parsed dates (valid or out-of-range)
|
|
21
|
+
* - Called immediately with null for invalid/unparseable text
|
|
22
|
+
* - Enables real-time validation feedback
|
|
23
|
+
*
|
|
24
|
+
* With resetInvalidValueOnBlur={true} (default):
|
|
25
|
+
* - Called immediately only for valid in-range dates
|
|
26
|
+
* - Out-of-range dates and invalid text only notify on blur (will auto-reset)
|
|
27
|
+
*
|
|
28
|
+
* For validation feedback, use resetInvalidValueOnBlur={false} and always update selectedDate
|
|
29
|
+
* in your callback to display invalid values with error messages.
|
|
16
30
|
*/
|
|
17
31
|
updateDate: (arg1?: Temporal.PlainDate | null | undefined) => any;
|
|
18
32
|
/**
|
|
19
33
|
* Used to format the value as a valid Date.
|
|
34
|
+
* If not specified, defaults to locale-aware short date format.
|
|
35
|
+
*
|
|
36
|
+
* Supported formats:
|
|
37
|
+
* - **undefined**: Locale-aware short date (default - uses Intl.DateTimeFormat with full year)
|
|
38
|
+
* - **"L"**: Locale-aware short date (e.g., "1/20/2026" in en-US, "20.01.2026" in de-DE, "20/01/2026" in bg)
|
|
39
|
+
* - **"LL"**: Locale-aware long date (e.g., "January 20, 2026" in en-US, "20 de enero de 2026" in es)
|
|
40
|
+
* - Supports manual text editing using locale-specific month names
|
|
41
|
+
* - **"MM/DD/YYYY"**: Fixed US format (e.g., "01/20/2026") - always US order regardless of locale
|
|
42
|
+
* - **"MMMM D, YYYY"**: Text format (e.g., "January 20, 2026") - month name localized but US order
|
|
43
|
+
* - **"dateStyle:short|medium|long|full"**: Explicit Intl.DateTimeFormat dateStyle values
|
|
20
44
|
*/
|
|
21
45
|
dateFormat?: Array<string> | string;
|
|
22
46
|
/**
|
|
@@ -54,10 +78,25 @@ interface Props {
|
|
|
54
78
|
*/
|
|
55
79
|
style?: StyleType;
|
|
56
80
|
/**
|
|
57
|
-
* Whether the date picker should close when a date is selected
|
|
81
|
+
* Whether the date picker overlay should close when a date is selected
|
|
82
|
+
* or when Enter key is pressed in the input.
|
|
58
83
|
* Defaults to true.
|
|
59
84
|
*/
|
|
60
85
|
closeOnSelect?: boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Whether to reset invalid/unparseable text to the last valid value on blur.
|
|
88
|
+
*
|
|
89
|
+
* When true (default):
|
|
90
|
+
* - Invalid values (out-of-range dates and unparseable text) auto-reset to last valid value on blur
|
|
91
|
+
* - updateDate only called on blur for invalid values (not during typing)
|
|
92
|
+
* - Cleaner UX when not using external validation
|
|
93
|
+
*
|
|
94
|
+
* When false:
|
|
95
|
+
* - Invalid values stay in field and updateDate is called immediately as user types
|
|
96
|
+
* - Enables real-time validation feedback with LabeledField error messages
|
|
97
|
+
* - Parent should always update selectedDate to show errors
|
|
98
|
+
*/
|
|
99
|
+
resetInvalidValueOnBlur?: boolean;
|
|
61
100
|
/**
|
|
62
101
|
* Allows including elements below the date selection area that can close
|
|
63
102
|
* the date picker.
|
package/dist/es/index.js
CHANGED
|
@@ -14,14 +14,14 @@ import { Popper } from 'react-popper';
|
|
|
14
14
|
import { maybeGetPortalMountedModalHostElement } from '@khanacademy/wonder-blocks-modal';
|
|
15
15
|
import 'react-day-picker/style.css';
|
|
16
16
|
|
|
17
|
-
const enUSLocaleCode="en-US";function formatDate(date,format,locale
|
|
17
|
+
const enUSLocaleCode="en-US";function formatDate(date,format,locale){const localeCode=typeof locale==="string"?locale:locale?.code??enUSLocaleCode;const formatString=Array.isArray(format)?format[0]:format;if(!formatString){return date.toLocaleString(localeCode,{year:"numeric",month:"numeric",day:"numeric"})}if(formatString==="L"){return date.toLocaleString(localeCode,{year:"numeric",month:"numeric",day:"numeric"})}if(formatString==="LL"){return date.toLocaleString(localeCode,{dateStyle:"long"})}if(formatString==="dateStyle:short"||formatString==="dateStyle:medium"||formatString==="dateStyle:long"||formatString==="dateStyle:full"){const style=formatString.split(":")[1];return date.toLocaleString(localeCode,{dateStyle:style})}if(formatString==="YYYY-MM-DD"){return date.toString()}if(formatString==="MMMM D, YYYY"||formatString==="MMM D, YYYY"){try{const monthFormat=formatString==="MMMM D, YYYY"?"long":"short";const monthName=date.toLocaleString(localeCode,{month:monthFormat});return `${monthName} ${date.day}, ${date.year}`}catch(error){return date.toString()}}if(formatString==="MM/DD/YYYY"||formatString==="M/D/YYYY"||formatString==="DD/MM/YYYY"){const shouldPad=formatString.includes("MM")||formatString.includes("DD");const month=shouldPad?String(date.month).padStart(2,"0"):String(date.month);const day=shouldPad?String(date.day).padStart(2,"0"):String(date.day);return `${month}/${day}/${date.year}`}try{const options=getOptionsForFormat(formatString);return date.toLocaleString(localeCode,options)}catch(error){console.warn(`Failed to format date with format "${formatString}" and locale "${localeCode}". Falling back to ISO format.`,error);return date.toString()}}function parseDate(str,format,locale){if(!str||str.trim()===""){return undefined}const formats=Array.isArray(format)?format:[format||"L"];try{return Temporal.PlainDate.from(str)}catch{}for(const fmt of formats){try{const parsed=parseWithFormat(str,fmt,locale);if(parsed){return parsed}}catch{continue}}return undefined}const getModifiersForDay=(day,modifiers)=>{const matchedModifiers=[];for(const[modifierName,matcher]of Object.entries(modifiers)){if(!matcher){continue}if(typeof matcher==="function"){if(matcher(day)){matchedModifiers.push(modifierName);}}else if(matcher instanceof Date){if(day.getFullYear()===matcher.getFullYear()&&day.getMonth()===matcher.getMonth()&&day.getDate()===matcher.getDate()){matchedModifiers.push(modifierName);}}}return matchedModifiers};function temporalDateToJsDate(date){return new Date(date.year,date.month-1,date.day)}function jsDateToTemporalDate(date){return Temporal.PlainDate.from({year:date.getFullYear(),month:date.getMonth()+1,day:date.getDate()})}function parseDateToJsDate(value,format,locale){if(value instanceof Date){return value}const temporalDate=parseDate(value,format,locale||undefined);if(temporalDate){const formatted=formatDate(temporalDate,format,locale||undefined);if(formatted===value){return temporalDateToJsDate(temporalDate)}const normalizedFormatted=formatted.replace(/\b0(\d)\b/g,"$1");const normalizedValue=value.replace(/\b0(\d)\b/g,"$1");if(normalizedFormatted===normalizedValue){return temporalDateToJsDate(temporalDate)}if(value===temporalDate.toString()){return temporalDateToJsDate(temporalDate)}if(format==="LL"){const normalizedFormatted=formatted.replace(/\s+/g," ").trim().toLowerCase();const normalizedValue=value.replace(/\s+/g," ").trim().toLowerCase();if(normalizedFormatted===normalizedValue){return temporalDateToJsDate(temporalDate)}return temporalDateToJsDate(temporalDate)}return undefined}return undefined}function getMonths(locale){const format=new Intl.DateTimeFormat(locale||enUSLocaleCode,{month:"long"});const formatShort=new Intl.DateTimeFormat(locale||enUSLocaleCode,{month:"short"});const months=[];for(let i=0;i<12;i++){const date=new Date(2021,i,15);months.push([format.format(date),formatShort.format(date)]);}return months}function getOptionsForFormat(format){const options={};if(format.includes("YYYY")){options.year="numeric";}else if(format.includes("YY")){options.year="2-digit";}if(format.includes("MMMM")){options.month="long";}else if(format.includes("MMM")){options.month="short";}else if(format.includes("MM")){options.month="2-digit";}else if(format.includes("M")){options.month="numeric";}if(format.includes("DD")){options.day="2-digit";}else if(format.includes("D")){options.day="numeric";}if(format.includes("dddd")){options.weekday="long";}else if(format.includes("ddd")){options.weekday="short";}return options}function parseLocaleAwareDate(str,locale){const localeStr=locale||enUSLocaleCode;const cleaned=str.trim();if(!cleaned){return undefined}try{const formatter=new Intl.DateTimeFormat(localeStr,{dateStyle:"short"});const testDate=new Date(2020,0,15);const parts=formatter.formatToParts(testDate);const pattern=parts.map(p=>({type:p.type,value:p.value}));const separators=pattern.filter(p=>p.type==="literal").map(p=>p.value);const inputParts=cleaned.split(new RegExp(`[${separators.map(s=>`\\${s}`).join("")}]`));if(inputParts.length!==3){throw new Error("Not a numeric date format")}const dateComponents={};let partIndex=0;for(const patternPart of pattern){if(patternPart.type==="literal"){continue}const value=parseInt(inputParts[partIndex],10);if(isNaN(value)){throw new Error("Not a numeric date format")}dateComponents[patternPart.type]=value;partIndex++;}if(!dateComponents.year||!dateComponents.month||!dateComponents.day||dateComponents.month<1||dateComponents.month>12||dateComponents.day<1||dateComponents.day>31||dateComponents.year<1e3||dateComponents.year>9999){throw new Error("Invalid date range")}return Temporal.PlainDate.from({year:dateComponents.year,month:dateComponents.month,day:dateComponents.day})}catch{return parseTextDate(cleaned,localeStr)}}function parseTextDate(str,locale){try{const months=getMonths(locale);const numbers=str.match(/\d+/g);if(!numbers||numbers.length<2){return undefined}let monthIndex=-1;const lowerStr=str.toLowerCase();for(let i=0;i<months.length;i++){const[longName,shortName]=months[i];if(lowerStr.includes(longName.toLowerCase())||lowerStr.includes(shortName.toLowerCase())){monthIndex=i+1;break}}if(monthIndex===-1){return undefined}let day;let year;const num1=parseInt(numbers[0],10);const num2=parseInt(numbers[1],10);if(num1>31){year=num1;day=num2;}else if(num2>31||num2.toString().length===4){day=num1;year=num2;}else {day=num1;year=num2;}if(day<1||day>31||monthIndex<1||monthIndex>12||year<1e3||year>9999){return undefined}return Temporal.PlainDate.from({year,month:monthIndex,day})}catch{return undefined}}function parseWithFormat(str,format,locale){if(!format){return undefined}if(format==="L"||format==="LL"||format.startsWith("dateStyle:")){return parseLocaleAwareDate(str,locale)}if(format==="M/D/YYYY"||format==="M-D-YYYY"||format==="MM/DD/YYYY"||format==="MM-DD-YYYY"){const separator=format.includes("/")?"/":"-";const parts=str.split(separator);if(parts.length===3){const month=parseInt(parts[0],10);const day=parseInt(parts[1],10);const year=parseInt(parts[2],10);if(isNaN(month)||isNaN(day)||isNaN(year)||month<1||month>12||day<1||day>31||year<1e3||year>9999){return undefined}try{return Temporal.PlainDate.from({year,month,day})}catch{return undefined}}}if(format==="MMMM D, YYYY"||format==="MMM D, YYYY"){try{const cleaned=str.trim();const localeStr=locale||enUSLocaleCode;const parts=cleaned.split(",");if(parts.length===2){const[monthDay,yearStr]=parts;const year=parseInt(yearStr.trim(),10);if(year<1e3||year>9999){return undefined}const months=getMonths(localeStr).map(m=>m[0]);const monthDayParts=monthDay.trim().split(" ");if(monthDayParts.length===2){const monthName=monthDayParts[0];const day=parseInt(monthDayParts[1],10);const monthIndex=months.findIndex(m=>m.toLowerCase()===monthName.toLowerCase()||m.slice(0,3).toLowerCase()===monthName.toLowerCase());if(monthIndex>=0&&!isNaN(day)&&!isNaN(year)){return Temporal.PlainDate.from({year,month:monthIndex+1,day})}}}}catch{return undefined}}return undefined}const startOfIsoWeek=date=>{const dayOfWeek=date.dayOfWeek;return date.subtract({days:dayOfWeek-1})};const startOfDay=date=>{const result=new Date(date);result.setHours(0,0,0,0);return result};const endOfDay=date=>{const result=new Date(date);result.setHours(23,59,59,999);return result};const TemporalLocaleUtils={formatDate,parseDate,parseDateToJsDate,startOfIsoWeek,startOfDay,endOfDay,temporalDateToJsDate,jsDateToTemporalDate,getModifiersForDay};
|
|
18
18
|
|
|
19
|
-
const DatePickerInput=React.forwardRef((props,ref)=>{const{value:propValue,onBlur,onClick,onFocus,onKeyDown,onChange,dateFormat,locale=enUSLocaleCode,modifiers,getModifiersForDay,parseDate,placeholder,testId,["aria-label"]:ariaLabel,...restProps}=props;const[value,setValue]=React.useState(propValue);const processModifiers=React.useCallback((date,value)=>{if(!getModifiersForDay||!modifiers){return {}}return getModifiersForDay(date,modifiers).reduce((obj,modifier)=>({...obj,[modifier]:true}),{})},[getModifiersForDay,modifiers]);const updateDate=React.useCallback((date,value)=>{if(onChange){onChange(date,processModifiers(date,value));}},[onChange,processModifiers]);const updateDateAsInvalid=React.useCallback(()=>{if(onChange){onChange(null,{});}},[onChange]);const processDate=React.useCallback(inputValue=>{if(!inputValue||inputValue.trim()===""){return}if(!parseDate){return}const date=parseDate(inputValue,dateFormat,locale);if(!date){return}return date},[parseDate,dateFormat,locale]);const
|
|
19
|
+
const DatePickerInput=React.forwardRef((props,ref)=>{const{value:propValue,onBlur,onClick,onFocus,onKeyDown,onChange,dateFormat,locale=enUSLocaleCode,modifiers,getModifiersForDay,parseDate,placeholder,testId,resetInvalidValueOnBlur=true,["aria-label"]:ariaLabel,...restProps}=props;const[value,setValue]=React.useState(propValue);const lastPropValueRef=React.useRef(propValue);const keepInvalidTextRef=React.useRef(false);const processModifiers=React.useCallback((date,value)=>{if(!getModifiersForDay||!modifiers){return {}}return getModifiersForDay(date,modifiers).reduce((obj,modifier)=>({...obj,[modifier]:true}),{})},[getModifiersForDay,modifiers]);const updateDate=React.useCallback((date,value)=>{if(onChange){onChange(date,processModifiers(date,value));}},[onChange,processModifiers]);const updateDateAsInvalid=React.useCallback(()=>{if(onChange){onChange(null,{});}},[onChange]);const processDate=React.useCallback(inputValue=>{if(!inputValue||inputValue.trim()===""){return}if(!parseDate){return}const date=parseDate(inputValue,dateFormat,locale);if(!date){return}return date},[parseDate,dateFormat,locale]);const isValid=React.useCallback(()=>{const date=processDate(value);if(!date){return false}const modifiersResult=processModifiers(date,value);if(modifiersResult.disabled){return false}return true},[value,processDate,processModifiers]);React.useEffect(()=>{const propValueChanged=lastPropValueRef.current!==propValue;lastPropValueRef.current=propValue;if(propValueChanged){if(keepInvalidTextRef.current&&(!propValue||propValue.trim()==="")){keepInvalidTextRef.current=false;}else {setValue(propValue);keepInvalidTextRef.current=false;}}},[propValue]);useOnMountEffect(()=>{const skipValidation=dateFormat==="LL"&&propValue;if(!skipValidation&&!isValid()){updateDateAsInvalid();}});const handleFocus=e=>{if(onFocus){onFocus(e);}};const pendingValidationRef=React.useRef(false);const validateInput=React.useCallback(()=>{const date=processDate(value);if(date){const modifiersResult=processModifiers(date,value);if(!modifiersResult.disabled){updateDate(date,value);}else {if(!resetInvalidValueOnBlur){keepInvalidTextRef.current=true;updateDate(date,value);}else {setValue(propValue);}}}else if(value&&value.trim()!==""){if(!resetInvalidValueOnBlur){keepInvalidTextRef.current=true;updateDateAsInvalid();}else {setValue(propValue);}}else {setValue(propValue);}},[value,processDate,processModifiers,resetInvalidValueOnBlur,propValue,updateDate,updateDateAsInvalid]);const handleBlur=e=>{const movingToCalendar=e.relatedTarget instanceof HTMLElement&&e.relatedTarget.closest('[data-testid="date-picker-overlay"]')!==null;if(movingToCalendar){pendingValidationRef.current=true;if(onBlur){onBlur(e);}return}validateInput();if(onBlur){onBlur(e);}};const handleChange=newValue=>{setValue(newValue);const date=processDate(newValue);if(date){const modifiersResult=processModifiers(date,newValue);if(!modifiersResult.disabled){updateDate(date,newValue);}else if(!resetInvalidValueOnBlur){keepInvalidTextRef.current=true;updateDate(date,newValue);}}else if(!resetInvalidValueOnBlur&&newValue&&newValue.trim()!==""){keepInvalidTextRef.current=true;updateDateAsInvalid();}};const innerRef=React.useRef(null);React.useImperativeHandle(ref,()=>{const inputElement=innerRef.current;if(!inputElement){return null}inputElement.validateInput=()=>{if(pendingValidationRef.current){pendingValidationRef.current=false;validateInput();}};return inputElement});return jsxs(View,{style:styles$1.container,onClick:e=>{if(!restProps.disabled&&onClick){onClick(e);}},children:[jsx(TextField,{ref:innerRef,...restProps,onBlur:handleBlur,onFocus:handleFocus,onKeyDown:onKeyDown,onChange:handleChange,disabled:restProps.disabled,placeholder:placeholder,value:value??"",testId:testId,"aria-label":ariaLabel,autoComplete:"off",type:"text",style:styles$1.textField}),jsx(PhosphorIcon,{icon:calendarIcon,color:restProps.disabled?semanticColor.core.foreground.disabled.default:semanticColor.core.foreground.instructive.default,size:"small",style:styles$1.icon})]})});const styles$1=StyleSheet.create({container:{alignItems:"center",flexDirection:"row",justifyContent:"stretch"},icon:{pointerEvents:"none",position:"absolute",insetInlineEnd:sizing.size_080},textField:{width:"100%"}});
|
|
20
20
|
|
|
21
|
-
function FocusManager(props){const{children,referenceElement,onStartFocused,onEndFocused}=props;const rootNodeRef=React.useRef(null);const focusableElementsRef=React.useRef([]);const focusableElementsInsideRef=React.useRef([]);const nextFocusableElementRef=React.useRef(null);const getFocusableElements=React.useCallback(()=>{return findFocusableNodes(document)},[]);const getReferenceIndex=React.useCallback(()=>{if(!referenceElement){return -1}return focusableElementsRef.current.indexOf(referenceElement)},[referenceElement]);const getNextFocusableElement=React.useCallback(()=>{const referenceIndex=getReferenceIndex();if(referenceIndex>=0){const nextElementIndex=referenceIndex<focusableElementsRef.current.length-1?referenceIndex+1:0;return focusableElementsRef.current[nextElementIndex]}return undefined},[getReferenceIndex]);React.useEffect(()=>{focusableElementsRef.current=getFocusableElements();nextFocusableElementRef.current=getNextFocusableElement();const handleKeydownReferenceElement=e=>{if(e.key==="Tab"&&!e.shiftKey){e.preventDefault();focusableElementsInsideRef.current[0]?.focus();}};const handleKeydownNextFocusableElement=e=>{if(e.key==="Tab"&&e.shiftKey){e.preventDefault();const lastIndex=focusableElementsInsideRef.current.length-1;focusableElementsInsideRef.current[lastIndex]?.focus();}};if(referenceElement){referenceElement.addEventListener("keydown",handleKeydownReferenceElement,true);}if(nextFocusableElementRef.current){nextFocusableElementRef.current.addEventListener("keydown",handleKeydownNextFocusableElement,true);}return ()=>{if(referenceElement){referenceElement.removeEventListener("keydown",handleKeydownReferenceElement,true);}if(nextFocusableElementRef.current){nextFocusableElementRef.current.removeEventListener("keydown",handleKeydownNextFocusableElement,true);}}},[referenceElement,getNextFocusableElement,getFocusableElements]);const setComponentRootNode=React.useCallback(node=>{if(!node){return}rootNodeRef.current=node;focusableElementsInsideRef.current=findFocusableNodes(node);},[]);const handleFocusPreviousFocusableElement=React.useCallback(()=>{if(referenceElement){referenceElement.focus();}if(onStartFocused){onStartFocused();}},[referenceElement,onStartFocused]);const handleFocusNextFocusableElement=React.useCallback(()=>{if(nextFocusableElementRef.current){nextFocusableElementRef.current.focus();}if(onEndFocused){onEndFocused();}},[onEndFocused]);return jsxs(React.Fragment,{children:[jsx("div",{tabIndex:0,"data-testid":"focus-sentinel-prev",onFocus:handleFocusPreviousFocusableElement,style:{position:"fixed"}}),jsx("div",{"data-testid":"date-picker-overlay",ref:setComponentRootNode,children:children}),jsx("div",{tabIndex:0,"data-testid":"focus-sentinel-next",onFocus:handleFocusNextFocusableElement,style:{position:"fixed"}})]})}
|
|
21
|
+
function FocusManager(props){const{children,referenceElement,onStartFocused,onEndFocused}=props;const rootNodeRef=React.useRef(null);const focusableElementsRef=React.useRef([]);const focusableElementsInsideRef=React.useRef([]);const nextFocusableElementRef=React.useRef(null);const getFocusableElements=React.useCallback(()=>{return findFocusableNodes(document)},[]);const getReferenceIndex=React.useCallback(()=>{if(!referenceElement){return -1}return focusableElementsRef.current.indexOf(referenceElement)},[referenceElement]);const getNextFocusableElement=React.useCallback(()=>{const referenceIndex=getReferenceIndex();if(referenceIndex>=0){const nextElementIndex=referenceIndex<focusableElementsRef.current.length-1?referenceIndex+1:0;return focusableElementsRef.current[nextElementIndex]}return undefined},[getReferenceIndex]);React.useEffect(()=>{focusableElementsRef.current=getFocusableElements();nextFocusableElementRef.current=getNextFocusableElement();const handleKeydownReferenceElement=e=>{if(e.key==="Tab"&&!e.shiftKey){if(rootNodeRef.current){focusableElementsInsideRef.current=findFocusableNodes(rootNodeRef.current);}if(focusableElementsInsideRef.current.length>0){e.preventDefault();focusableElementsInsideRef.current[0]?.focus();}}};const handleKeydownNextFocusableElement=e=>{if(e.key==="Tab"&&e.shiftKey){if(rootNodeRef.current){focusableElementsInsideRef.current=findFocusableNodes(rootNodeRef.current);}if(focusableElementsInsideRef.current.length>0){e.preventDefault();const lastIndex=focusableElementsInsideRef.current.length-1;focusableElementsInsideRef.current[lastIndex]?.focus();}}};if(referenceElement){referenceElement.addEventListener("keydown",handleKeydownReferenceElement,true);}if(nextFocusableElementRef.current){nextFocusableElementRef.current.addEventListener("keydown",handleKeydownNextFocusableElement,true);}return ()=>{if(referenceElement){referenceElement.removeEventListener("keydown",handleKeydownReferenceElement,true);}if(nextFocusableElementRef.current){nextFocusableElementRef.current.removeEventListener("keydown",handleKeydownNextFocusableElement,true);}}},[referenceElement,getNextFocusableElement,getFocusableElements]);const setComponentRootNode=React.useCallback(node=>{if(!node){return}rootNodeRef.current=node;focusableElementsInsideRef.current=findFocusableNodes(node);},[]);const handleFocusPreviousFocusableElement=React.useCallback(()=>{if(referenceElement){referenceElement.focus();}if(onStartFocused){onStartFocused();}},[referenceElement,onStartFocused]);const handleFocusNextFocusableElement=React.useCallback(()=>{if(nextFocusableElementRef.current){nextFocusableElementRef.current.focus();}if(onEndFocused){onEndFocused();}},[onEndFocused]);return jsxs(React.Fragment,{children:[jsx("div",{tabIndex:0,"data-testid":"focus-sentinel-prev",onFocus:handleFocusPreviousFocusableElement,style:{position:"fixed"}}),jsx("div",{"data-testid":"date-picker-overlay",ref:setComponentRootNode,children:children}),jsx("div",{tabIndex:0,"data-testid":"focus-sentinel-next",onFocus:handleFocusNextFocusableElement,style:{position:"fixed"}})]})}
|
|
22
22
|
|
|
23
|
-
const DEFAULT_STYLE={background:semanticColor.core.background.base.default,borderRadius:border.radius.radius_040,border:`solid ${border.width.thin} ${semanticColor.core.border.neutral.subtle}`,boxShadow:boxShadow.mid};const BASE_CONTAINER_STYLES={fontFamily:font.family.sans,padding:sizing.size_100};const OUT_OF_BOUNDARIES_STYLES={
|
|
23
|
+
const DEFAULT_STYLE={background:semanticColor.core.background.base.default,borderRadius:border.radius.radius_040,border:`solid ${border.width.thin} ${semanticColor.core.border.neutral.subtle}`,boxShadow:boxShadow.mid};const BASE_CONTAINER_STYLES={fontFamily:font.family.sans,padding:sizing.size_100};const OUT_OF_BOUNDARIES_STYLES={visibility:"hidden"};const DatePickerOverlay=({children,referenceElement,onClose,style=DEFAULT_STYLE})=>{if(!referenceElement){return null}const modalHost=maybeGetPortalMountedModalHostElement(referenceElement)||document.querySelector("body");if(!modalHost){return null}return createPortal(jsx(FocusManager,{referenceElement:referenceElement,onEndFocused:onClose,children:jsx(Popper,{referenceElement:referenceElement,placement:"bottom-start",strategy:"fixed",modifiers:[{name:"preventOverflow",options:{rootBoundary:"viewport"}}],children:({placement,ref,style:popperStyle,isReferenceHidden,hasPopperEscaped})=>{const isTestEnvironment=typeof window!=="undefined"&&window.navigator.userAgent.includes("jsdom");const outOfBoundaries=!isTestEnvironment&&(isReferenceHidden||hasPopperEscaped);const combinedStyles={...BASE_CONTAINER_STYLES,...popperStyle,...style,...outOfBoundaries&&OUT_OF_BOUNDARIES_STYLES};return jsx("div",{ref:ref,style:combinedStyles,"data-placement":placement,children:children})}})}),modalHost)};
|
|
24
24
|
|
|
25
|
-
const customRootStyle={"--rdp-accent-color":semanticColor.core.border.instructive.default};const DatePicker=props=>{const{locale,updateDate,dateFormat,disabled,id,maxDate,minDate,inputAriaLabel,placeholder,selectedDate,style,closeOnSelect=true,footer}=props;const[showOverlay,setShowOverlay]=React.useState(false);const[currentDate,setCurrentDate]=React.useState(selectedDate);const datePickerInputRef=React.useRef(null);const datePickerRef=React.useRef(null);const refWrapper=React.useRef(null);const open=React.useCallback(()=>{if(!disabled){setShowOverlay(true);}},[disabled]);const close=React.useCallback(()=>setShowOverlay(false),[]);const computedLocale=locale??enUS;const dir=refWrapper.current?.closest("[dir]")?.getAttribute("dir")||"ltr";React.useEffect(()=>{setCurrentDate(selectedDate);},[selectedDate]);React.useEffect(()=>{const handleClick=e=>{const target=e.target;const thisElement=refWrapper.current;const dayPickerCalendar=datePickerRef.current;
|
|
25
|
+
const customRootStyle={"--rdp-accent-color":semanticColor.core.border.instructive.default};const DatePicker=props=>{const{locale,updateDate,dateFormat,disabled,id,maxDate,minDate,inputAriaLabel,placeholder,selectedDate,style,closeOnSelect=true,resetInvalidValueOnBlur=true,footer}=props;const[showOverlay,setShowOverlay]=React.useState(false);const[currentDate,setCurrentDate]=React.useState(selectedDate);const[displayMonth,setDisplayMonth]=React.useState(selectedDate?TemporalLocaleUtils.temporalDateToJsDate(selectedDate):undefined);const datePickerInputRef=React.useRef(null);const datePickerRef=React.useRef(null);const refWrapper=React.useRef(null);const open=React.useCallback(()=>{if(!disabled){setShowOverlay(true);}},[disabled]);const close=React.useCallback(()=>{setShowOverlay(false);datePickerInputRef.current?.validateInput?.();},[]);const computedLocale=locale??enUS;const dir=refWrapper.current?.closest("[dir]")?.getAttribute("dir")||"ltr";React.useEffect(()=>{setCurrentDate(selectedDate);if(selectedDate){setDisplayMonth(TemporalLocaleUtils.temporalDateToJsDate(selectedDate));}},[selectedDate]);React.useEffect(()=>{const handleClick=e=>{const target=e.target;const thisElement=refWrapper.current;const dayPickerCalendar=datePickerRef.current;const isElement=target instanceof Element;const inThisElement=isElement&&thisElement?.contains(target);const inCalendar=isElement&&dayPickerCalendar?.contains(target);const inPortal=isElement&&target.closest("[data-placement]")!==null;const shouldClose=showOverlay&&closeOnSelect&&thisElement&&!inThisElement&&!inCalendar&&!inPortal;if(shouldClose){setShowOverlay(false);}};document.addEventListener("mouseup",handleClick);return ()=>{document.removeEventListener("mouseup",handleClick);}},[showOverlay,closeOnSelect]);const isLeavingDropdown=e=>{const dayPickerCalendar=datePickerRef.current;if(!dayPickerCalendar){return true}if(e.relatedTarget instanceof Node){return !dayPickerCalendar.contains(e.relatedTarget)}return true};const handleInputBlur=e=>{if(isLeavingDropdown(e)){close();}};const handleInputChange=(selectedDate,modifiers)=>{if(!selectedDate){setCurrentDate(null);updateDate(null);return}const wrappedDate=TemporalLocaleUtils.jsDateToTemporalDate(selectedDate);const isDisabled=typeof modifiers.disabled==="function"?modifiers.disabled(selectedDate):modifiers.disabled;if(isDisabled){updateDate(wrappedDate);return}setCurrentDate(wrappedDate);setDisplayMonth(selectedDate);updateDate(wrappedDate);};const handleKeyDown=e=>{if(e.key==="Escape"){close();datePickerInputRef.current?.focus();}if(e.key==="ArrowDown"&&!showOverlay){e.preventDefault();open();}if(e.key==="Enter"){e.preventDefault();if(showOverlay){if(closeOnSelect){close();}}else {open();}}};const RootWithEsc=props=>{const{onKeyDown,rootRef:_,...rest}=props;return jsx("div",{...rest,tabIndex:-1,onKeyDown:e=>{onKeyDown?.(e);if(e.key==="Escape"){close();datePickerInputRef.current?.focus();}}})};const handleDayClick=(date,{disabled})=>{if(disabled||!date){return}datePickerInputRef.current?.focus();const wrappedDate=TemporalLocaleUtils.jsDateToTemporalDate(date);setCurrentDate(wrappedDate);setDisplayMonth(date);updateDate(wrappedDate);setShowOverlay(!closeOnSelect);};const renderInput=modifiers=>{const selectedDateAsValue=currentDate?TemporalLocaleUtils.formatDate(currentDate,dateFormat,computedLocale):"";return jsx(DatePickerInput,{onBlur:handleInputBlur,onFocus:open,onClick:open,onChange:handleInputChange,onKeyDown:handleKeyDown,"aria-label":inputAriaLabel,disabled:disabled,id:id,placeholder:placeholder,value:selectedDateAsValue,ref:datePickerInputRef,dateFormat:dateFormat,locale:computedLocale.code,parseDate:TemporalLocaleUtils.parseDateToJsDate,getModifiersForDay:TemporalLocaleUtils.getModifiersForDay,modifiers:modifiers,resetInvalidValueOnBlur:resetInvalidValueOnBlur,testId:id&&`${id}-input`})};const maybeRenderFooter=()=>{if(!footer){return null}return jsx(View,{testId:"date-picker-footer",style:styles.footer,children:footer({close})})};const selectedDateValue=currentDate?TemporalLocaleUtils.temporalDateToJsDate(currentDate):undefined;const minDateToShow=minDate&&selectedDateValue?Temporal.PlainDate.compare(minDate,currentDate)<0?TemporalLocaleUtils.temporalDateToJsDate(minDate):selectedDateValue:minDate?TemporalLocaleUtils.temporalDateToJsDate(minDate):undefined;const modifiers={selected:selectedDateValue,disabled:date=>{const temporalDate=TemporalLocaleUtils.jsDateToTemporalDate(date);return minDate&&Temporal.PlainDate.compare(temporalDate,minDate)<0||maxDate&&Temporal.PlainDate.compare(temporalDate,maxDate)>0||false}};return jsxs(View,{style:[styles.wrapper,style],ref:refWrapper,children:[renderInput(modifiers),showOverlay&&jsx(DatePickerOverlay,{referenceElement:datePickerInputRef.current,onClose:close,children:jsxs(View,{ref:datePickerRef,children:[jsx(DayPicker,{mode:"single",selected:selectedDateValue,month:displayMonth,onMonthChange:setDisplayMonth,startMonth:minDateToShow??undefined,endMonth:maxDate?TemporalLocaleUtils.temporalDateToJsDate(maxDate):undefined,modifiers:modifiers,onDayClick:handleDayClick,components:{Root:RootWithEsc},locale:computedLocale,dir:dir,styles:{root:{...customRootStyle},nav:{width:"auto"}}}),maybeRenderFooter()]})})]})};DatePicker.defaultProps={closeOnSelect:true};const styles=StyleSheet.create({wrapper:{width:225,height:40},footer:{margin:sizing.size_120,marginBlockStart:0}});
|
|
26
26
|
|
|
27
27
|
export { DatePicker, TemporalLocaleUtils };
|
package/dist/index.js
CHANGED
|
@@ -41,15 +41,15 @@ function _interopNamespace(e) {
|
|
|
41
41
|
var React__namespace = /*#__PURE__*/_interopNamespace(React);
|
|
42
42
|
var calendarIcon__default = /*#__PURE__*/_interopDefaultLegacy(calendarIcon);
|
|
43
43
|
|
|
44
|
-
const enUSLocaleCode="en-US";function formatDate(date,format,locale
|
|
44
|
+
const enUSLocaleCode="en-US";function formatDate(date,format,locale){const localeCode=typeof locale==="string"?locale:locale?.code??enUSLocaleCode;const formatString=Array.isArray(format)?format[0]:format;if(!formatString){return date.toLocaleString(localeCode,{year:"numeric",month:"numeric",day:"numeric"})}if(formatString==="L"){return date.toLocaleString(localeCode,{year:"numeric",month:"numeric",day:"numeric"})}if(formatString==="LL"){return date.toLocaleString(localeCode,{dateStyle:"long"})}if(formatString==="dateStyle:short"||formatString==="dateStyle:medium"||formatString==="dateStyle:long"||formatString==="dateStyle:full"){const style=formatString.split(":")[1];return date.toLocaleString(localeCode,{dateStyle:style})}if(formatString==="YYYY-MM-DD"){return date.toString()}if(formatString==="MMMM D, YYYY"||formatString==="MMM D, YYYY"){try{const monthFormat=formatString==="MMMM D, YYYY"?"long":"short";const monthName=date.toLocaleString(localeCode,{month:monthFormat});return `${monthName} ${date.day}, ${date.year}`}catch(error){return date.toString()}}if(formatString==="MM/DD/YYYY"||formatString==="M/D/YYYY"||formatString==="DD/MM/YYYY"){const shouldPad=formatString.includes("MM")||formatString.includes("DD");const month=shouldPad?String(date.month).padStart(2,"0"):String(date.month);const day=shouldPad?String(date.day).padStart(2,"0"):String(date.day);return `${month}/${day}/${date.year}`}try{const options=getOptionsForFormat(formatString);return date.toLocaleString(localeCode,options)}catch(error){console.warn(`Failed to format date with format "${formatString}" and locale "${localeCode}". Falling back to ISO format.`,error);return date.toString()}}function parseDate(str,format,locale){if(!str||str.trim()===""){return undefined}const formats=Array.isArray(format)?format:[format||"L"];try{return temporalPolyfill.Temporal.PlainDate.from(str)}catch{}for(const fmt of formats){try{const parsed=parseWithFormat(str,fmt,locale);if(parsed){return parsed}}catch{continue}}return undefined}const getModifiersForDay=(day,modifiers)=>{const matchedModifiers=[];for(const[modifierName,matcher]of Object.entries(modifiers)){if(!matcher){continue}if(typeof matcher==="function"){if(matcher(day)){matchedModifiers.push(modifierName);}}else if(matcher instanceof Date){if(day.getFullYear()===matcher.getFullYear()&&day.getMonth()===matcher.getMonth()&&day.getDate()===matcher.getDate()){matchedModifiers.push(modifierName);}}}return matchedModifiers};function temporalDateToJsDate(date){return new Date(date.year,date.month-1,date.day)}function jsDateToTemporalDate(date){return temporalPolyfill.Temporal.PlainDate.from({year:date.getFullYear(),month:date.getMonth()+1,day:date.getDate()})}function parseDateToJsDate(value,format,locale){if(value instanceof Date){return value}const temporalDate=parseDate(value,format,locale||undefined);if(temporalDate){const formatted=formatDate(temporalDate,format,locale||undefined);if(formatted===value){return temporalDateToJsDate(temporalDate)}const normalizedFormatted=formatted.replace(/\b0(\d)\b/g,"$1");const normalizedValue=value.replace(/\b0(\d)\b/g,"$1");if(normalizedFormatted===normalizedValue){return temporalDateToJsDate(temporalDate)}if(value===temporalDate.toString()){return temporalDateToJsDate(temporalDate)}if(format==="LL"){const normalizedFormatted=formatted.replace(/\s+/g," ").trim().toLowerCase();const normalizedValue=value.replace(/\s+/g," ").trim().toLowerCase();if(normalizedFormatted===normalizedValue){return temporalDateToJsDate(temporalDate)}return temporalDateToJsDate(temporalDate)}return undefined}return undefined}function getMonths(locale){const format=new Intl.DateTimeFormat(locale||enUSLocaleCode,{month:"long"});const formatShort=new Intl.DateTimeFormat(locale||enUSLocaleCode,{month:"short"});const months=[];for(let i=0;i<12;i++){const date=new Date(2021,i,15);months.push([format.format(date),formatShort.format(date)]);}return months}function getOptionsForFormat(format){const options={};if(format.includes("YYYY")){options.year="numeric";}else if(format.includes("YY")){options.year="2-digit";}if(format.includes("MMMM")){options.month="long";}else if(format.includes("MMM")){options.month="short";}else if(format.includes("MM")){options.month="2-digit";}else if(format.includes("M")){options.month="numeric";}if(format.includes("DD")){options.day="2-digit";}else if(format.includes("D")){options.day="numeric";}if(format.includes("dddd")){options.weekday="long";}else if(format.includes("ddd")){options.weekday="short";}return options}function parseLocaleAwareDate(str,locale){const localeStr=locale||enUSLocaleCode;const cleaned=str.trim();if(!cleaned){return undefined}try{const formatter=new Intl.DateTimeFormat(localeStr,{dateStyle:"short"});const testDate=new Date(2020,0,15);const parts=formatter.formatToParts(testDate);const pattern=parts.map(p=>({type:p.type,value:p.value}));const separators=pattern.filter(p=>p.type==="literal").map(p=>p.value);const inputParts=cleaned.split(new RegExp(`[${separators.map(s=>`\\${s}`).join("")}]`));if(inputParts.length!==3){throw new Error("Not a numeric date format")}const dateComponents={};let partIndex=0;for(const patternPart of pattern){if(patternPart.type==="literal"){continue}const value=parseInt(inputParts[partIndex],10);if(isNaN(value)){throw new Error("Not a numeric date format")}dateComponents[patternPart.type]=value;partIndex++;}if(!dateComponents.year||!dateComponents.month||!dateComponents.day||dateComponents.month<1||dateComponents.month>12||dateComponents.day<1||dateComponents.day>31||dateComponents.year<1e3||dateComponents.year>9999){throw new Error("Invalid date range")}return temporalPolyfill.Temporal.PlainDate.from({year:dateComponents.year,month:dateComponents.month,day:dateComponents.day})}catch{return parseTextDate(cleaned,localeStr)}}function parseTextDate(str,locale){try{const months=getMonths(locale);const numbers=str.match(/\d+/g);if(!numbers||numbers.length<2){return undefined}let monthIndex=-1;const lowerStr=str.toLowerCase();for(let i=0;i<months.length;i++){const[longName,shortName]=months[i];if(lowerStr.includes(longName.toLowerCase())||lowerStr.includes(shortName.toLowerCase())){monthIndex=i+1;break}}if(monthIndex===-1){return undefined}let day;let year;const num1=parseInt(numbers[0],10);const num2=parseInt(numbers[1],10);if(num1>31){year=num1;day=num2;}else if(num2>31||num2.toString().length===4){day=num1;year=num2;}else {day=num1;year=num2;}if(day<1||day>31||monthIndex<1||monthIndex>12||year<1e3||year>9999){return undefined}return temporalPolyfill.Temporal.PlainDate.from({year,month:monthIndex,day})}catch{return undefined}}function parseWithFormat(str,format,locale){if(!format){return undefined}if(format==="L"||format==="LL"||format.startsWith("dateStyle:")){return parseLocaleAwareDate(str,locale)}if(format==="M/D/YYYY"||format==="M-D-YYYY"||format==="MM/DD/YYYY"||format==="MM-DD-YYYY"){const separator=format.includes("/")?"/":"-";const parts=str.split(separator);if(parts.length===3){const month=parseInt(parts[0],10);const day=parseInt(parts[1],10);const year=parseInt(parts[2],10);if(isNaN(month)||isNaN(day)||isNaN(year)||month<1||month>12||day<1||day>31||year<1e3||year>9999){return undefined}try{return temporalPolyfill.Temporal.PlainDate.from({year,month,day})}catch{return undefined}}}if(format==="MMMM D, YYYY"||format==="MMM D, YYYY"){try{const cleaned=str.trim();const localeStr=locale||enUSLocaleCode;const parts=cleaned.split(",");if(parts.length===2){const[monthDay,yearStr]=parts;const year=parseInt(yearStr.trim(),10);if(year<1e3||year>9999){return undefined}const months=getMonths(localeStr).map(m=>m[0]);const monthDayParts=monthDay.trim().split(" ");if(monthDayParts.length===2){const monthName=monthDayParts[0];const day=parseInt(monthDayParts[1],10);const monthIndex=months.findIndex(m=>m.toLowerCase()===monthName.toLowerCase()||m.slice(0,3).toLowerCase()===monthName.toLowerCase());if(monthIndex>=0&&!isNaN(day)&&!isNaN(year)){return temporalPolyfill.Temporal.PlainDate.from({year,month:monthIndex+1,day})}}}}catch{return undefined}}return undefined}const startOfIsoWeek=date=>{const dayOfWeek=date.dayOfWeek;return date.subtract({days:dayOfWeek-1})};const startOfDay=date=>{const result=new Date(date);result.setHours(0,0,0,0);return result};const endOfDay=date=>{const result=new Date(date);result.setHours(23,59,59,999);return result};const TemporalLocaleUtils={formatDate,parseDate,parseDateToJsDate,startOfIsoWeek,startOfDay,endOfDay,temporalDateToJsDate,jsDateToTemporalDate,getModifiersForDay};
|
|
45
45
|
|
|
46
|
-
const DatePickerInput=React__namespace.forwardRef((props,ref)=>{const{value:propValue,onBlur,onClick,onFocus,onKeyDown,onChange,dateFormat,locale=enUSLocaleCode,modifiers,getModifiersForDay,parseDate,placeholder,testId,["aria-label"]:ariaLabel,...restProps}=props;const[value,setValue]=React__namespace.useState(propValue);const processModifiers=React__namespace.useCallback((date,value)=>{if(!getModifiersForDay||!modifiers){return {}}return getModifiersForDay(date,modifiers).reduce((obj,modifier)=>({...obj,[modifier]:true}),{})},[getModifiersForDay,modifiers]);const updateDate=React__namespace.useCallback((date,value)=>{if(onChange){onChange(date,processModifiers(date,value));}},[onChange,processModifiers]);const updateDateAsInvalid=React__namespace.useCallback(()=>{if(onChange){onChange(null,{});}},[onChange]);const processDate=React__namespace.useCallback(inputValue=>{if(!inputValue||inputValue.trim()===""){return}if(!parseDate){return}const date=parseDate(inputValue,dateFormat,locale);if(!date){return}return date},[parseDate,dateFormat,locale]);const
|
|
46
|
+
const DatePickerInput=React__namespace.forwardRef((props,ref)=>{const{value:propValue,onBlur,onClick,onFocus,onKeyDown,onChange,dateFormat,locale=enUSLocaleCode,modifiers,getModifiersForDay,parseDate,placeholder,testId,resetInvalidValueOnBlur=true,["aria-label"]:ariaLabel,...restProps}=props;const[value,setValue]=React__namespace.useState(propValue);const lastPropValueRef=React__namespace.useRef(propValue);const keepInvalidTextRef=React__namespace.useRef(false);const processModifiers=React__namespace.useCallback((date,value)=>{if(!getModifiersForDay||!modifiers){return {}}return getModifiersForDay(date,modifiers).reduce((obj,modifier)=>({...obj,[modifier]:true}),{})},[getModifiersForDay,modifiers]);const updateDate=React__namespace.useCallback((date,value)=>{if(onChange){onChange(date,processModifiers(date,value));}},[onChange,processModifiers]);const updateDateAsInvalid=React__namespace.useCallback(()=>{if(onChange){onChange(null,{});}},[onChange]);const processDate=React__namespace.useCallback(inputValue=>{if(!inputValue||inputValue.trim()===""){return}if(!parseDate){return}const date=parseDate(inputValue,dateFormat,locale);if(!date){return}return date},[parseDate,dateFormat,locale]);const isValid=React__namespace.useCallback(()=>{const date=processDate(value);if(!date){return false}const modifiersResult=processModifiers(date,value);if(modifiersResult.disabled){return false}return true},[value,processDate,processModifiers]);React__namespace.useEffect(()=>{const propValueChanged=lastPropValueRef.current!==propValue;lastPropValueRef.current=propValue;if(propValueChanged){if(keepInvalidTextRef.current&&(!propValue||propValue.trim()==="")){keepInvalidTextRef.current=false;}else {setValue(propValue);keepInvalidTextRef.current=false;}}},[propValue]);wonderBlocksCore.useOnMountEffect(()=>{const skipValidation=dateFormat==="LL"&&propValue;if(!skipValidation&&!isValid()){updateDateAsInvalid();}});const handleFocus=e=>{if(onFocus){onFocus(e);}};const pendingValidationRef=React__namespace.useRef(false);const validateInput=React__namespace.useCallback(()=>{const date=processDate(value);if(date){const modifiersResult=processModifiers(date,value);if(!modifiersResult.disabled){updateDate(date,value);}else {if(!resetInvalidValueOnBlur){keepInvalidTextRef.current=true;updateDate(date,value);}else {setValue(propValue);}}}else if(value&&value.trim()!==""){if(!resetInvalidValueOnBlur){keepInvalidTextRef.current=true;updateDateAsInvalid();}else {setValue(propValue);}}else {setValue(propValue);}},[value,processDate,processModifiers,resetInvalidValueOnBlur,propValue,updateDate,updateDateAsInvalid]);const handleBlur=e=>{const movingToCalendar=e.relatedTarget instanceof HTMLElement&&e.relatedTarget.closest('[data-testid="date-picker-overlay"]')!==null;if(movingToCalendar){pendingValidationRef.current=true;if(onBlur){onBlur(e);}return}validateInput();if(onBlur){onBlur(e);}};const handleChange=newValue=>{setValue(newValue);const date=processDate(newValue);if(date){const modifiersResult=processModifiers(date,newValue);if(!modifiersResult.disabled){updateDate(date,newValue);}else if(!resetInvalidValueOnBlur){keepInvalidTextRef.current=true;updateDate(date,newValue);}}else if(!resetInvalidValueOnBlur&&newValue&&newValue.trim()!==""){keepInvalidTextRef.current=true;updateDateAsInvalid();}};const innerRef=React__namespace.useRef(null);React__namespace.useImperativeHandle(ref,()=>{const inputElement=innerRef.current;if(!inputElement){return null}inputElement.validateInput=()=>{if(pendingValidationRef.current){pendingValidationRef.current=false;validateInput();}};return inputElement});return jsxRuntime.jsxs(wonderBlocksCore.View,{style:styles$1.container,onClick:e=>{if(!restProps.disabled&&onClick){onClick(e);}},children:[jsxRuntime.jsx(wonderBlocksForm.TextField,{ref:innerRef,...restProps,onBlur:handleBlur,onFocus:handleFocus,onKeyDown:onKeyDown,onChange:handleChange,disabled:restProps.disabled,placeholder:placeholder,value:value??"",testId:testId,"aria-label":ariaLabel,autoComplete:"off",type:"text",style:styles$1.textField}),jsxRuntime.jsx(wonderBlocksIcon.PhosphorIcon,{icon:calendarIcon__default["default"],color:restProps.disabled?wonderBlocksTokens.semanticColor.core.foreground.disabled.default:wonderBlocksTokens.semanticColor.core.foreground.instructive.default,size:"small",style:styles$1.icon})]})});const styles$1=aphrodite.StyleSheet.create({container:{alignItems:"center",flexDirection:"row",justifyContent:"stretch"},icon:{pointerEvents:"none",position:"absolute",insetInlineEnd:wonderBlocksTokens.sizing.size_080},textField:{width:"100%"}});
|
|
47
47
|
|
|
48
|
-
function FocusManager(props){const{children,referenceElement,onStartFocused,onEndFocused}=props;const rootNodeRef=React__namespace.useRef(null);const focusableElementsRef=React__namespace.useRef([]);const focusableElementsInsideRef=React__namespace.useRef([]);const nextFocusableElementRef=React__namespace.useRef(null);const getFocusableElements=React__namespace.useCallback(()=>{return wonderBlocksCore.findFocusableNodes(document)},[]);const getReferenceIndex=React__namespace.useCallback(()=>{if(!referenceElement){return -1}return focusableElementsRef.current.indexOf(referenceElement)},[referenceElement]);const getNextFocusableElement=React__namespace.useCallback(()=>{const referenceIndex=getReferenceIndex();if(referenceIndex>=0){const nextElementIndex=referenceIndex<focusableElementsRef.current.length-1?referenceIndex+1:0;return focusableElementsRef.current[nextElementIndex]}return undefined},[getReferenceIndex]);React__namespace.useEffect(()=>{focusableElementsRef.current=getFocusableElements();nextFocusableElementRef.current=getNextFocusableElement();const handleKeydownReferenceElement=e=>{if(e.key==="Tab"&&!e.shiftKey){e.preventDefault();focusableElementsInsideRef.current[0]?.focus();}};const handleKeydownNextFocusableElement=e=>{if(e.key==="Tab"&&e.shiftKey){e.preventDefault();const lastIndex=focusableElementsInsideRef.current.length-1;focusableElementsInsideRef.current[lastIndex]?.focus();}};if(referenceElement){referenceElement.addEventListener("keydown",handleKeydownReferenceElement,true);}if(nextFocusableElementRef.current){nextFocusableElementRef.current.addEventListener("keydown",handleKeydownNextFocusableElement,true);}return ()=>{if(referenceElement){referenceElement.removeEventListener("keydown",handleKeydownReferenceElement,true);}if(nextFocusableElementRef.current){nextFocusableElementRef.current.removeEventListener("keydown",handleKeydownNextFocusableElement,true);}}},[referenceElement,getNextFocusableElement,getFocusableElements]);const setComponentRootNode=React__namespace.useCallback(node=>{if(!node){return}rootNodeRef.current=node;focusableElementsInsideRef.current=wonderBlocksCore.findFocusableNodes(node);},[]);const handleFocusPreviousFocusableElement=React__namespace.useCallback(()=>{if(referenceElement){referenceElement.focus();}if(onStartFocused){onStartFocused();}},[referenceElement,onStartFocused]);const handleFocusNextFocusableElement=React__namespace.useCallback(()=>{if(nextFocusableElementRef.current){nextFocusableElementRef.current.focus();}if(onEndFocused){onEndFocused();}},[onEndFocused]);return jsxRuntime.jsxs(React__namespace.Fragment,{children:[jsxRuntime.jsx("div",{tabIndex:0,"data-testid":"focus-sentinel-prev",onFocus:handleFocusPreviousFocusableElement,style:{position:"fixed"}}),jsxRuntime.jsx("div",{"data-testid":"date-picker-overlay",ref:setComponentRootNode,children:children}),jsxRuntime.jsx("div",{tabIndex:0,"data-testid":"focus-sentinel-next",onFocus:handleFocusNextFocusableElement,style:{position:"fixed"}})]})}
|
|
48
|
+
function FocusManager(props){const{children,referenceElement,onStartFocused,onEndFocused}=props;const rootNodeRef=React__namespace.useRef(null);const focusableElementsRef=React__namespace.useRef([]);const focusableElementsInsideRef=React__namespace.useRef([]);const nextFocusableElementRef=React__namespace.useRef(null);const getFocusableElements=React__namespace.useCallback(()=>{return wonderBlocksCore.findFocusableNodes(document)},[]);const getReferenceIndex=React__namespace.useCallback(()=>{if(!referenceElement){return -1}return focusableElementsRef.current.indexOf(referenceElement)},[referenceElement]);const getNextFocusableElement=React__namespace.useCallback(()=>{const referenceIndex=getReferenceIndex();if(referenceIndex>=0){const nextElementIndex=referenceIndex<focusableElementsRef.current.length-1?referenceIndex+1:0;return focusableElementsRef.current[nextElementIndex]}return undefined},[getReferenceIndex]);React__namespace.useEffect(()=>{focusableElementsRef.current=getFocusableElements();nextFocusableElementRef.current=getNextFocusableElement();const handleKeydownReferenceElement=e=>{if(e.key==="Tab"&&!e.shiftKey){if(rootNodeRef.current){focusableElementsInsideRef.current=wonderBlocksCore.findFocusableNodes(rootNodeRef.current);}if(focusableElementsInsideRef.current.length>0){e.preventDefault();focusableElementsInsideRef.current[0]?.focus();}}};const handleKeydownNextFocusableElement=e=>{if(e.key==="Tab"&&e.shiftKey){if(rootNodeRef.current){focusableElementsInsideRef.current=wonderBlocksCore.findFocusableNodes(rootNodeRef.current);}if(focusableElementsInsideRef.current.length>0){e.preventDefault();const lastIndex=focusableElementsInsideRef.current.length-1;focusableElementsInsideRef.current[lastIndex]?.focus();}}};if(referenceElement){referenceElement.addEventListener("keydown",handleKeydownReferenceElement,true);}if(nextFocusableElementRef.current){nextFocusableElementRef.current.addEventListener("keydown",handleKeydownNextFocusableElement,true);}return ()=>{if(referenceElement){referenceElement.removeEventListener("keydown",handleKeydownReferenceElement,true);}if(nextFocusableElementRef.current){nextFocusableElementRef.current.removeEventListener("keydown",handleKeydownNextFocusableElement,true);}}},[referenceElement,getNextFocusableElement,getFocusableElements]);const setComponentRootNode=React__namespace.useCallback(node=>{if(!node){return}rootNodeRef.current=node;focusableElementsInsideRef.current=wonderBlocksCore.findFocusableNodes(node);},[]);const handleFocusPreviousFocusableElement=React__namespace.useCallback(()=>{if(referenceElement){referenceElement.focus();}if(onStartFocused){onStartFocused();}},[referenceElement,onStartFocused]);const handleFocusNextFocusableElement=React__namespace.useCallback(()=>{if(nextFocusableElementRef.current){nextFocusableElementRef.current.focus();}if(onEndFocused){onEndFocused();}},[onEndFocused]);return jsxRuntime.jsxs(React__namespace.Fragment,{children:[jsxRuntime.jsx("div",{tabIndex:0,"data-testid":"focus-sentinel-prev",onFocus:handleFocusPreviousFocusableElement,style:{position:"fixed"}}),jsxRuntime.jsx("div",{"data-testid":"date-picker-overlay",ref:setComponentRootNode,children:children}),jsxRuntime.jsx("div",{tabIndex:0,"data-testid":"focus-sentinel-next",onFocus:handleFocusNextFocusableElement,style:{position:"fixed"}})]})}
|
|
49
49
|
|
|
50
|
-
const DEFAULT_STYLE={background:wonderBlocksTokens.semanticColor.core.background.base.default,borderRadius:wonderBlocksTokens.border.radius.radius_040,border:`solid ${wonderBlocksTokens.border.width.thin} ${wonderBlocksTokens.semanticColor.core.border.neutral.subtle}`,boxShadow:wonderBlocksTokens.boxShadow.mid};const BASE_CONTAINER_STYLES={fontFamily:wonderBlocksTokens.font.family.sans,padding:wonderBlocksTokens.sizing.size_100};const OUT_OF_BOUNDARIES_STYLES={
|
|
50
|
+
const DEFAULT_STYLE={background:wonderBlocksTokens.semanticColor.core.background.base.default,borderRadius:wonderBlocksTokens.border.radius.radius_040,border:`solid ${wonderBlocksTokens.border.width.thin} ${wonderBlocksTokens.semanticColor.core.border.neutral.subtle}`,boxShadow:wonderBlocksTokens.boxShadow.mid};const BASE_CONTAINER_STYLES={fontFamily:wonderBlocksTokens.font.family.sans,padding:wonderBlocksTokens.sizing.size_100};const OUT_OF_BOUNDARIES_STYLES={visibility:"hidden"};const DatePickerOverlay=({children,referenceElement,onClose,style=DEFAULT_STYLE})=>{if(!referenceElement){return null}const modalHost=wonderBlocksModal.maybeGetPortalMountedModalHostElement(referenceElement)||document.querySelector("body");if(!modalHost){return null}return reactDom.createPortal(jsxRuntime.jsx(FocusManager,{referenceElement:referenceElement,onEndFocused:onClose,children:jsxRuntime.jsx(reactPopper.Popper,{referenceElement:referenceElement,placement:"bottom-start",strategy:"fixed",modifiers:[{name:"preventOverflow",options:{rootBoundary:"viewport"}}],children:({placement,ref,style:popperStyle,isReferenceHidden,hasPopperEscaped})=>{const isTestEnvironment=typeof window!=="undefined"&&window.navigator.userAgent.includes("jsdom");const outOfBoundaries=!isTestEnvironment&&(isReferenceHidden||hasPopperEscaped);const combinedStyles={...BASE_CONTAINER_STYLES,...popperStyle,...style,...outOfBoundaries&&OUT_OF_BOUNDARIES_STYLES};return jsxRuntime.jsx("div",{ref:ref,style:combinedStyles,"data-placement":placement,children:children})}})}),modalHost)};
|
|
51
51
|
|
|
52
|
-
const customRootStyle={"--rdp-accent-color":wonderBlocksTokens.semanticColor.core.border.instructive.default};const DatePicker=props=>{const{locale: locale$1,updateDate,dateFormat,disabled,id,maxDate,minDate,inputAriaLabel,placeholder,selectedDate,style,closeOnSelect=true,footer}=props;const[showOverlay,setShowOverlay]=React__namespace.useState(false);const[currentDate,setCurrentDate]=React__namespace.useState(selectedDate);const datePickerInputRef=React__namespace.useRef(null);const datePickerRef=React__namespace.useRef(null);const refWrapper=React__namespace.useRef(null);const open=React__namespace.useCallback(()=>{if(!disabled){setShowOverlay(true);}},[disabled]);const close=React__namespace.useCallback(()=>setShowOverlay(false),[]);const computedLocale=locale$1??locale.enUS;const dir=refWrapper.current?.closest("[dir]")?.getAttribute("dir")||"ltr";React__namespace.useEffect(()=>{setCurrentDate(selectedDate);},[selectedDate]);React__namespace.useEffect(()=>{const handleClick=e=>{const target=e.target;const thisElement=refWrapper.current;const dayPickerCalendar=datePickerRef.current;
|
|
52
|
+
const customRootStyle={"--rdp-accent-color":wonderBlocksTokens.semanticColor.core.border.instructive.default};const DatePicker=props=>{const{locale: locale$1,updateDate,dateFormat,disabled,id,maxDate,minDate,inputAriaLabel,placeholder,selectedDate,style,closeOnSelect=true,resetInvalidValueOnBlur=true,footer}=props;const[showOverlay,setShowOverlay]=React__namespace.useState(false);const[currentDate,setCurrentDate]=React__namespace.useState(selectedDate);const[displayMonth,setDisplayMonth]=React__namespace.useState(selectedDate?TemporalLocaleUtils.temporalDateToJsDate(selectedDate):undefined);const datePickerInputRef=React__namespace.useRef(null);const datePickerRef=React__namespace.useRef(null);const refWrapper=React__namespace.useRef(null);const open=React__namespace.useCallback(()=>{if(!disabled){setShowOverlay(true);}},[disabled]);const close=React__namespace.useCallback(()=>{setShowOverlay(false);datePickerInputRef.current?.validateInput?.();},[]);const computedLocale=locale$1??locale.enUS;const dir=refWrapper.current?.closest("[dir]")?.getAttribute("dir")||"ltr";React__namespace.useEffect(()=>{setCurrentDate(selectedDate);if(selectedDate){setDisplayMonth(TemporalLocaleUtils.temporalDateToJsDate(selectedDate));}},[selectedDate]);React__namespace.useEffect(()=>{const handleClick=e=>{const target=e.target;const thisElement=refWrapper.current;const dayPickerCalendar=datePickerRef.current;const isElement=target instanceof Element;const inThisElement=isElement&&thisElement?.contains(target);const inCalendar=isElement&&dayPickerCalendar?.contains(target);const inPortal=isElement&&target.closest("[data-placement]")!==null;const shouldClose=showOverlay&&closeOnSelect&&thisElement&&!inThisElement&&!inCalendar&&!inPortal;if(shouldClose){setShowOverlay(false);}};document.addEventListener("mouseup",handleClick);return ()=>{document.removeEventListener("mouseup",handleClick);}},[showOverlay,closeOnSelect]);const isLeavingDropdown=e=>{const dayPickerCalendar=datePickerRef.current;if(!dayPickerCalendar){return true}if(e.relatedTarget instanceof Node){return !dayPickerCalendar.contains(e.relatedTarget)}return true};const handleInputBlur=e=>{if(isLeavingDropdown(e)){close();}};const handleInputChange=(selectedDate,modifiers)=>{if(!selectedDate){setCurrentDate(null);updateDate(null);return}const wrappedDate=TemporalLocaleUtils.jsDateToTemporalDate(selectedDate);const isDisabled=typeof modifiers.disabled==="function"?modifiers.disabled(selectedDate):modifiers.disabled;if(isDisabled){updateDate(wrappedDate);return}setCurrentDate(wrappedDate);setDisplayMonth(selectedDate);updateDate(wrappedDate);};const handleKeyDown=e=>{if(e.key==="Escape"){close();datePickerInputRef.current?.focus();}if(e.key==="ArrowDown"&&!showOverlay){e.preventDefault();open();}if(e.key==="Enter"){e.preventDefault();if(showOverlay){if(closeOnSelect){close();}}else {open();}}};const RootWithEsc=props=>{const{onKeyDown,rootRef:_,...rest}=props;return jsxRuntime.jsx("div",{...rest,tabIndex:-1,onKeyDown:e=>{onKeyDown?.(e);if(e.key==="Escape"){close();datePickerInputRef.current?.focus();}}})};const handleDayClick=(date,{disabled})=>{if(disabled||!date){return}datePickerInputRef.current?.focus();const wrappedDate=TemporalLocaleUtils.jsDateToTemporalDate(date);setCurrentDate(wrappedDate);setDisplayMonth(date);updateDate(wrappedDate);setShowOverlay(!closeOnSelect);};const renderInput=modifiers=>{const selectedDateAsValue=currentDate?TemporalLocaleUtils.formatDate(currentDate,dateFormat,computedLocale):"";return jsxRuntime.jsx(DatePickerInput,{onBlur:handleInputBlur,onFocus:open,onClick:open,onChange:handleInputChange,onKeyDown:handleKeyDown,"aria-label":inputAriaLabel,disabled:disabled,id:id,placeholder:placeholder,value:selectedDateAsValue,ref:datePickerInputRef,dateFormat:dateFormat,locale:computedLocale.code,parseDate:TemporalLocaleUtils.parseDateToJsDate,getModifiersForDay:TemporalLocaleUtils.getModifiersForDay,modifiers:modifiers,resetInvalidValueOnBlur:resetInvalidValueOnBlur,testId:id&&`${id}-input`})};const maybeRenderFooter=()=>{if(!footer){return null}return jsxRuntime.jsx(wonderBlocksCore.View,{testId:"date-picker-footer",style:styles.footer,children:footer({close})})};const selectedDateValue=currentDate?TemporalLocaleUtils.temporalDateToJsDate(currentDate):undefined;const minDateToShow=minDate&&selectedDateValue?temporalPolyfill.Temporal.PlainDate.compare(minDate,currentDate)<0?TemporalLocaleUtils.temporalDateToJsDate(minDate):selectedDateValue:minDate?TemporalLocaleUtils.temporalDateToJsDate(minDate):undefined;const modifiers={selected:selectedDateValue,disabled:date=>{const temporalDate=TemporalLocaleUtils.jsDateToTemporalDate(date);return minDate&&temporalPolyfill.Temporal.PlainDate.compare(temporalDate,minDate)<0||maxDate&&temporalPolyfill.Temporal.PlainDate.compare(temporalDate,maxDate)>0||false}};return jsxRuntime.jsxs(wonderBlocksCore.View,{style:[styles.wrapper,style],ref:refWrapper,children:[renderInput(modifiers),showOverlay&&jsxRuntime.jsx(DatePickerOverlay,{referenceElement:datePickerInputRef.current,onClose:close,children:jsxRuntime.jsxs(wonderBlocksCore.View,{ref:datePickerRef,children:[jsxRuntime.jsx(reactDayPicker.DayPicker,{mode:"single",selected:selectedDateValue,month:displayMonth,onMonthChange:setDisplayMonth,startMonth:minDateToShow??undefined,endMonth:maxDate?TemporalLocaleUtils.temporalDateToJsDate(maxDate):undefined,modifiers:modifiers,onDayClick:handleDayClick,components:{Root:RootWithEsc},locale:computedLocale,dir:dir,styles:{root:{...customRootStyle},nav:{width:"auto"}}}),maybeRenderFooter()]})})]})};DatePicker.defaultProps={closeOnSelect:true};const styles=aphrodite.StyleSheet.create({wrapper:{width:225,height:40},footer:{margin:wonderBlocksTokens.sizing.size_120,marginBlockStart:0}});
|
|
53
53
|
|
|
54
54
|
exports.DatePicker = DatePicker;
|
|
55
55
|
exports.TemporalLocaleUtils = TemporalLocaleUtils;
|
|
@@ -1,38 +1,55 @@
|
|
|
1
1
|
import { Temporal } from "temporal-polyfill";
|
|
2
|
+
import type { Locale } from "react-day-picker/locale";
|
|
2
3
|
import { CustomModifiers } from "./types";
|
|
3
4
|
export declare const enUSLocaleCode = "en-US";
|
|
4
5
|
/**
|
|
5
6
|
* Utility functions for working with Temporal dates in react-day-picker.
|
|
6
|
-
*
|
|
7
|
+
* Uses Intl.DateTimeFormat for locale-aware date formatting and parsing.
|
|
7
8
|
*
|
|
8
9
|
* Question: Should we move this to a separate package?
|
|
9
10
|
*/
|
|
10
11
|
/**
|
|
11
12
|
* Format a Temporal.PlainDate using a format string.
|
|
12
|
-
* Supports
|
|
13
|
+
* Supports locale-aware formatting using Intl.DateTimeFormat and fixed format tokens.
|
|
13
14
|
*
|
|
14
15
|
* @param date - The Temporal.PlainDate to format
|
|
15
16
|
* @param format - The format string(s) to use for formatting:
|
|
16
|
-
* - **
|
|
17
|
+
* - **"L"**: Locale-aware short date with full year (e.g., "1/20/2026" in en-US, "20.01.2026" in de-DE, "20/01/2026" in bg)
|
|
18
|
+
* - **"LL"**: Locale-aware long date with full month name (e.g., "January 20, 2026" in en-US, "20 януари 2026 г." in bg)
|
|
19
|
+
* - **"dateStyle:short|medium|long|full"**: Explicit Intl.DateTimeFormat dateStyle values
|
|
20
|
+
* - **"YYYY-MM-DD"**: ISO 8601 format (e.g., "2024-01-15")
|
|
21
|
+
* - **"MM/DD/YYYY"**: Fixed US numeric format (always month/day/year regardless of locale)
|
|
22
|
+
* - **"MMMM D, YYYY"**: Text format with localized month name but US order
|
|
17
23
|
* - **Array<string>**: Uses the **first** format in the array (ignores the rest)
|
|
18
|
-
* - **null**:
|
|
19
|
-
*
|
|
20
|
-
*
|
|
24
|
+
* - **null/undefined**: Defaults to locale-aware short date (same as "L")
|
|
25
|
+
* @param locale - The locale to use for formatting. Accepts:
|
|
26
|
+
* - **Locale object** from react-day-picker (e.g., `es`, `fr`)
|
|
27
|
+
* - **string** locale code (e.g., "en-US", "de-DE", "bg")
|
|
28
|
+
* - **undefined**: defaults to "en-US"
|
|
21
29
|
* @returns The formatted date string
|
|
22
30
|
*
|
|
23
31
|
* @example
|
|
24
|
-
*
|
|
25
|
-
* formatDate(date,
|
|
26
|
-
* formatDate(date,
|
|
27
|
-
* formatDate(date,
|
|
28
|
-
* formatDate(date, "
|
|
32
|
+
* // Locale-aware formatting (recommended for international apps)
|
|
33
|
+
* formatDate(date, "L", "en-US") // => "1/15/2024"
|
|
34
|
+
* formatDate(date, "L", "de-DE") // => "15.01.2024"
|
|
35
|
+
* formatDate(date, "L", "bg") // => "15.01.2024"
|
|
36
|
+
* formatDate(date, "LL", "en-US") // => "January 15, 2024"
|
|
37
|
+
* formatDate(date, "LL", "bg") // => "15 януари 2024 г."
|
|
38
|
+
*
|
|
39
|
+
* // Fixed format (always same order regardless of locale)
|
|
40
|
+
* formatDate(date, "MM/DD/YYYY", "de-DE") // => "01/15/2024" (US order even in German locale)
|
|
41
|
+
* formatDate(date, "YYYY-MM-DD", "en-US") // => "2024-01-15" (ISO format)
|
|
42
|
+
*
|
|
43
|
+
* // Default behavior
|
|
44
|
+
* formatDate(date, undefined, "de-DE") // => "15.01.2024" (locale-aware short date)
|
|
45
|
+
* formatDate(date, null, "en-US") // => "1/15/2024" (locale-aware short date)
|
|
29
46
|
*
|
|
30
47
|
* @remarks
|
|
31
48
|
* If formatting fails (e.g., invalid locale, unsupported format), the function
|
|
32
49
|
* automatically falls back to ISO 8601 format and logs a warning to the console.
|
|
33
50
|
* This ensures the function never throws errors and always returns a valid date string.
|
|
34
51
|
*/
|
|
35
|
-
export declare function formatDate(date: Temporal.PlainDate, format: string | Array<string> | null | undefined, locale?: string): string;
|
|
52
|
+
export declare function formatDate(date: Temporal.PlainDate, format: string | Array<string> | null | undefined, locale?: Locale | string): string;
|
|
36
53
|
/**
|
|
37
54
|
* Parse a date string into a Temporal.PlainDate.
|
|
38
55
|
* Attempts multiple formats if an array is provided.
|
|
@@ -54,6 +71,13 @@ export declare function jsDateToTemporalDate(date: Date): Temporal.PlainDate;
|
|
|
54
71
|
* This is a convenience wrapper around parseDate that converts the result
|
|
55
72
|
* to a Date object for compatibility with react-day-picker.
|
|
56
73
|
* If a Date is passed in, it's returned as-is.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* // With format "MM/DD/YYYY":
|
|
77
|
+
* parseDateToJsDate("1/28/2026", "MM/DD/YYYY") // ✓ Returns Date (accepts unpadded)
|
|
78
|
+
* parseDateToJsDate("01/28/2026", "MM/DD/YYYY") // ✓ Returns Date (accepts padded)
|
|
79
|
+
* parseDateToJsDate("1/28", "MM/DD/YYYY") // ✗ Returns undefined (incomplete)
|
|
80
|
+
* parseDateToJsDate("2026-01-28", "MM/DD/YYYY") // ✗ Returns undefined (wrong format)
|
|
57
81
|
*/
|
|
58
82
|
export declare function parseDateToJsDate(value: string | Date, format: string | Array<string> | null | undefined, locale?: string | null | undefined): Date | null | undefined;
|
|
59
83
|
/**
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "Date picker component for Wonder Blocks.",
|
|
4
4
|
"author": "Khan Academy",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"version": "0.1.
|
|
6
|
+
"version": "0.1.2",
|
|
7
7
|
"publishConfig": {
|
|
8
8
|
"access": "public"
|
|
9
9
|
},
|
|
@@ -29,11 +29,11 @@
|
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"react-day-picker": "^9.11.1",
|
|
31
31
|
"@khanacademy/wonder-blocks-core": "12.4.3",
|
|
32
|
-
"@khanacademy/wonder-blocks-
|
|
33
|
-
"@khanacademy/wonder-blocks-
|
|
34
|
-
"@khanacademy/wonder-blocks-modal": "8.5.
|
|
35
|
-
"@khanacademy/wonder-blocks-
|
|
36
|
-
"@khanacademy/wonder-blocks-
|
|
32
|
+
"@khanacademy/wonder-blocks-icon": "5.3.7",
|
|
33
|
+
"@khanacademy/wonder-blocks-form": "7.5.3",
|
|
34
|
+
"@khanacademy/wonder-blocks-modal": "8.5.14",
|
|
35
|
+
"@khanacademy/wonder-blocks-tokens": "15.0.0",
|
|
36
|
+
"@khanacademy/wonder-blocks-styles": "0.2.38"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@phosphor-icons/core": "^2.0.2",
|