@khanacademy/wonder-blocks-date-picker 0.0.0-PR2934-20260115001506 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @khanacademy/wonder-blocks-date-picker
2
2
 
3
- ## 0.0.0-PR2934-20260115001506
3
+ ## 0.1.0
4
4
 
5
5
  ### Minor Changes
6
6
 
@@ -8,12 +8,11 @@
8
8
 
9
9
  ### Patch Changes
10
10
 
11
- - 58ed6cc: Adjusts date picker overlay to fit at small viewport sizes
12
11
  - 07c38ec: Finalizes support for DatePicker Locales
13
12
  - Updated dependencies [d6ae5fb]
14
- - @khanacademy/wonder-blocks-core@0.0.0-PR2934-20260115001506
15
- - @khanacademy/wonder-blocks-form@0.0.0-PR2934-20260115001506
16
- - @khanacademy/wonder-blocks-icon@0.0.0-PR2934-20260115001506
17
- - @khanacademy/wonder-blocks-modal@0.0.0-PR2934-20260115001506
13
+ - @khanacademy/wonder-blocks-core@12.4.3
14
+ - @khanacademy/wonder-blocks-form@7.5.2
15
+ - @khanacademy/wonder-blocks-icon@5.3.6
16
+ - @khanacademy/wonder-blocks-modal@8.5.13
18
17
  - @khanacademy/wonder-blocks-styles@0.2.37
19
18
  - @khanacademy/wonder-blocks-tokens@14.1.3
package/dist/es/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
- import { StyleSheet, css } from 'aphrodite';
2
+ import { StyleSheet } from 'aphrodite';
3
3
  import { Temporal } from 'temporal-polyfill';
4
4
  import * as React from 'react';
5
5
  import { DayPicker } from 'react-day-picker';
6
6
  import { enUS } from 'react-day-picker/locale';
7
7
  import { useOnMountEffect, View, findFocusableNodes } from '@khanacademy/wonder-blocks-core';
8
- import { semanticColor, sizing, border, boxShadow, font, breakpoint } from '@khanacademy/wonder-blocks-tokens';
8
+ import { semanticColor, sizing, border, boxShadow, font } from '@khanacademy/wonder-blocks-tokens';
9
9
  import { TextField } from '@khanacademy/wonder-blocks-form';
10
10
  import { PhosphorIcon } from '@khanacademy/wonder-blocks-icon';
11
11
  import calendarIcon from '@phosphor-icons/core/bold/calendar-blank-bold.svg';
@@ -16,11 +16,11 @@ import 'react-day-picker/style.css';
16
16
 
17
17
  const enUSLocaleCode="en-US";function formatDate(date,format,locale=enUSLocaleCode){const formatString=Array.isArray(format)?format[0]:format;if(!formatString){return date.toString()}if(formatString==="YYYY-MM-DD"){return date.toString()}try{const options=getOptionsForFormat(formatString);return date.toLocaleString(locale,options)}catch(error){console.warn(`Failed to format date with format "${formatString}" and locale "${locale}". 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||"YYYY-MM-DD"];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);return temporalDate?temporalDateToJsDate(temporalDate):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 parseWithFormat(str,format,locale){if(!format){return undefined}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){try{return Temporal.PlainDate.from({year:parseInt(parts[2],10),month:parseInt(parts[0],10),day:parseInt(parts[1],10)})}catch{return undefined}}}if(format==="MMMM D, YYYY"||format==="MMM D, YYYY"){try{const cleaned=str.trim();const localeStr=locale||enUSLocaleCode;const jsDate=new Date(cleaned);if(!isNaN(jsDate.getTime())){return jsDateToTemporalDate(jsDate)}const parts=cleaned.split(",");if(parts.length===2){const[monthDay,yearStr]=parts;const year=parseInt(yearStr.trim(),10);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 maybeUpdateDate=React.useCallback(inputValue=>{const date=processDate(inputValue);if(date){updateDate(date,inputValue);}else {updateDateAsInvalid();}},[processDate,updateDate,updateDateAsInvalid]);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(()=>{setValue(propValue);},[propValue]);useOnMountEffect(()=>{if(!isValid()){updateDateAsInvalid();}});const handleBlur=e=>{if(!isValid()){setValue(propValue);}if(onBlur){onBlur(e);}};const handleChange=newValue=>{maybeUpdateDate(newValue);setValue(newValue);};return jsxs(View,{style:styles$2.container,onClick:e=>{if(!restProps.disabled&&onClick){onClick(e);}},children:[jsx(TextField,{ref:ref,...restProps,onBlur:handleBlur,onFocus:onFocus,onKeyDown:onKeyDown,onChange:handleChange,disabled:restProps.disabled,placeholder:placeholder,value:value??"",testId:testId,"aria-label":ariaLabel,autoComplete:"off",type:"text",style:styles$2.textField}),jsx(PhosphorIcon,{icon:calendarIcon,color:restProps.disabled?semanticColor.core.foreground.disabled.default:semanticColor.core.foreground.instructive.default,size:"small",style:styles$2.icon})]})});const styles$2=StyleSheet.create({container:{alignItems:"center",flexDirection:"row",justifyContent:"stretch"},icon:{pointerEvents:"none",position:"absolute",insetInlineEnd:sizing.size_080},textField:{width:"100%"}});
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 maybeUpdateDate=React.useCallback(inputValue=>{const date=processDate(inputValue);if(date){updateDate(date,inputValue);}else {updateDateAsInvalid();}},[processDate,updateDate,updateDateAsInvalid]);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(()=>{setValue(propValue);},[propValue]);useOnMountEffect(()=>{if(!isValid()){updateDateAsInvalid();}});const handleBlur=e=>{if(!isValid()){setValue(propValue);}if(onBlur){onBlur(e);}};const handleChange=newValue=>{maybeUpdateDate(newValue);setValue(newValue);};return jsxs(View,{style:styles$1.container,onClick:e=>{if(!restProps.disabled&&onClick){onClick(e);}},children:[jsx(TextField,{ref:ref,...restProps,onBlur:handleBlur,onFocus:onFocus,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
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"}})]})}
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 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 inlineStyles={...popperStyle,...style,...outOfBoundaries&&{pointerEvents:"none",visibility:"hidden"}};return jsx("div",{ref:ref,className:css(styles$1.overlay),style:inlineStyles,"data-placement":placement,children:children})}})}),modalHost)};const styles$1=StyleSheet.create({overlay:{fontFamily:font.family.sans,padding:sizing.size_100,[breakpoint.mediaQuery.xsOrSmaller]:{insetInlineStart:"0 !important",insetInlineEnd:"0 !important",maxWidth:"100vw"}}});
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={pointerEvents:"none",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
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;if(showOverlay&&closeOnSelect&&thisElement&&!thisElement.contains(target)&&dayPickerCalendar&&!dayPickerCalendar.contains(target)){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||modifiers.disabled){return}const wrappedDate=TemporalLocaleUtils.jsDateToTemporalDate(selectedDate);setCurrentDate(wrappedDate);updateDate(wrappedDate);};const handleKeyDown=e=>{if(e.key==="Escape"){close();datePickerInputRef.current?.focus();}};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,selected})=>{if(disabled||!date){return}datePickerInputRef.current?.focus();const wrappedDate=TemporalLocaleUtils.jsDateToTemporalDate(date);setCurrentDate(selected?undefined:wrappedDate);setShowOverlay(!closeOnSelect);updateDate(wrappedDate);};const renderInput=modifiers=>{const selectedDateAsValue=currentDate?TemporalLocaleUtils.formatDate(currentDate,dateFormat,enUSLocaleCode):"";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,modifiers:modifiers,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,{defaultMonth:selectedDateValue??undefined,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
 
package/dist/index.js CHANGED
@@ -43,11 +43,11 @@ var calendarIcon__default = /*#__PURE__*/_interopDefaultLegacy(calendarIcon);
43
43
 
44
44
  const enUSLocaleCode="en-US";function formatDate(date,format,locale=enUSLocaleCode){const formatString=Array.isArray(format)?format[0]:format;if(!formatString){return date.toString()}if(formatString==="YYYY-MM-DD"){return date.toString()}try{const options=getOptionsForFormat(formatString);return date.toLocaleString(locale,options)}catch(error){console.warn(`Failed to format date with format "${formatString}" and locale "${locale}". 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||"YYYY-MM-DD"];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);return temporalDate?temporalDateToJsDate(temporalDate):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 parseWithFormat(str,format,locale){if(!format){return undefined}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){try{return temporalPolyfill.Temporal.PlainDate.from({year:parseInt(parts[2],10),month:parseInt(parts[0],10),day:parseInt(parts[1],10)})}catch{return undefined}}}if(format==="MMMM D, YYYY"||format==="MMM D, YYYY"){try{const cleaned=str.trim();const localeStr=locale||enUSLocaleCode;const jsDate=new Date(cleaned);if(!isNaN(jsDate.getTime())){return jsDateToTemporalDate(jsDate)}const parts=cleaned.split(",");if(parts.length===2){const[monthDay,yearStr]=parts;const year=parseInt(yearStr.trim(),10);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 maybeUpdateDate=React__namespace.useCallback(inputValue=>{const date=processDate(inputValue);if(date){updateDate(date,inputValue);}else {updateDateAsInvalid();}},[processDate,updateDate,updateDateAsInvalid]);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(()=>{setValue(propValue);},[propValue]);wonderBlocksCore.useOnMountEffect(()=>{if(!isValid()){updateDateAsInvalid();}});const handleBlur=e=>{if(!isValid()){setValue(propValue);}if(onBlur){onBlur(e);}};const handleChange=newValue=>{maybeUpdateDate(newValue);setValue(newValue);};return jsxRuntime.jsxs(wonderBlocksCore.View,{style:styles$2.container,onClick:e=>{if(!restProps.disabled&&onClick){onClick(e);}},children:[jsxRuntime.jsx(wonderBlocksForm.TextField,{ref:ref,...restProps,onBlur:handleBlur,onFocus:onFocus,onKeyDown:onKeyDown,onChange:handleChange,disabled:restProps.disabled,placeholder:placeholder,value:value??"",testId:testId,"aria-label":ariaLabel,autoComplete:"off",type:"text",style:styles$2.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$2.icon})]})});const styles$2=aphrodite.StyleSheet.create({container:{alignItems:"center",flexDirection:"row",justifyContent:"stretch"},icon:{pointerEvents:"none",position:"absolute",insetInlineEnd:wonderBlocksTokens.sizing.size_080},textField:{width:"100%"}});
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 maybeUpdateDate=React__namespace.useCallback(inputValue=>{const date=processDate(inputValue);if(date){updateDate(date,inputValue);}else {updateDateAsInvalid();}},[processDate,updateDate,updateDateAsInvalid]);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(()=>{setValue(propValue);},[propValue]);wonderBlocksCore.useOnMountEffect(()=>{if(!isValid()){updateDateAsInvalid();}});const handleBlur=e=>{if(!isValid()){setValue(propValue);}if(onBlur){onBlur(e);}};const handleChange=newValue=>{maybeUpdateDate(newValue);setValue(newValue);};return jsxRuntime.jsxs(wonderBlocksCore.View,{style:styles$1.container,onClick:e=>{if(!restProps.disabled&&onClick){onClick(e);}},children:[jsxRuntime.jsx(wonderBlocksForm.TextField,{ref:ref,...restProps,onBlur:handleBlur,onFocus:onFocus,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
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"}})]})}
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 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 inlineStyles={...popperStyle,...style,...outOfBoundaries&&{pointerEvents:"none",visibility:"hidden"}};return jsxRuntime.jsx("div",{ref:ref,className:aphrodite.css(styles$1.overlay),style:inlineStyles,"data-placement":placement,children:children})}})}),modalHost)};const styles$1=aphrodite.StyleSheet.create({overlay:{fontFamily:wonderBlocksTokens.font.family.sans,padding:wonderBlocksTokens.sizing.size_100,[wonderBlocksTokens.breakpoint.mediaQuery.xsOrSmaller]:{insetInlineStart:"0 !important",insetInlineEnd:"0 !important",maxWidth:"100vw"}}});
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={pointerEvents:"none",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
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;if(showOverlay&&closeOnSelect&&thisElement&&!thisElement.contains(target)&&dayPickerCalendar&&!dayPickerCalendar.contains(target)){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||modifiers.disabled){return}const wrappedDate=TemporalLocaleUtils.jsDateToTemporalDate(selectedDate);setCurrentDate(wrappedDate);updateDate(wrappedDate);};const handleKeyDown=e=>{if(e.key==="Escape"){close();datePickerInputRef.current?.focus();}};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,selected})=>{if(disabled||!date){return}datePickerInputRef.current?.focus();const wrappedDate=TemporalLocaleUtils.jsDateToTemporalDate(date);setCurrentDate(selected?undefined:wrappedDate);setShowOverlay(!closeOnSelect);updateDate(wrappedDate);};const renderInput=modifiers=>{const selectedDateAsValue=currentDate?TemporalLocaleUtils.formatDate(currentDate,dateFormat,enUSLocaleCode):"";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,modifiers:modifiers,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,{defaultMonth:selectedDateValue??undefined,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
 
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.0.0-PR2934-20260115001506",
6
+ "version": "0.1.0",
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },
@@ -28,10 +28,10 @@
28
28
  },
29
29
  "dependencies": {
30
30
  "react-day-picker": "^9.11.1",
31
- "@khanacademy/wonder-blocks-core": "0.0.0-PR2934-20260115001506",
32
- "@khanacademy/wonder-blocks-form": "0.0.0-PR2934-20260115001506",
33
- "@khanacademy/wonder-blocks-icon": "0.0.0-PR2934-20260115001506",
34
- "@khanacademy/wonder-blocks-modal": "0.0.0-PR2934-20260115001506",
31
+ "@khanacademy/wonder-blocks-core": "12.4.3",
32
+ "@khanacademy/wonder-blocks-form": "7.5.2",
33
+ "@khanacademy/wonder-blocks-icon": "5.3.6",
34
+ "@khanacademy/wonder-blocks-modal": "8.5.13",
35
35
  "@khanacademy/wonder-blocks-styles": "0.2.37",
36
36
  "@khanacademy/wonder-blocks-tokens": "14.1.3"
37
37
  },
@@ -45,7 +45,7 @@
45
45
  "temporal-polyfill": "^0.3.0"
46
46
  },
47
47
  "devDependencies": {
48
- "@khanacademy/wb-dev-build-settings": "0.0.0-PR2934-20260115001506"
48
+ "@khanacademy/wb-dev-build-settings": "3.3.0"
49
49
  },
50
50
  "scripts": {
51
51
  "test": "echo \"Error: no test specified\" && exit 1"