@@ -57,6 +57,104 @@ test.describe("Form control conversion", () => {
5757 } ;
5858 }
5959
60+ function parseDateValue ( value : string ) : Date | null {
61+ const match = value . match ( / ^ ( \d { 4 } ) - ( \d { 2 } ) - ( \d { 2 } ) $ / ) ;
62+ if ( ! match ) return null ;
63+ return new Date ( Number . parseInt ( match [ 1 ] , 10 ) , Number . parseInt ( match [ 2 ] , 10 ) - 1 , Number . parseInt ( match [ 3 ] , 10 ) , 12 , 0 , 0 , 0 ) ;
64+ }
65+
66+ function parseTimeValue ( value : string ) : { hour : number ; minute : number ; second : number ; hasSecond : boolean } | null {
67+ const match = value . match ( / ^ ( \d { 2 } ) : ( \d { 2 } ) (?: : ( \d { 2 } ) (?: \. ( \d { 1 , 3 } ) ) ? ) ? $ / ) ;
68+ if ( ! match ) return null ;
69+ return {
70+ hour : Number . parseInt ( match [ 1 ] , 10 ) ,
71+ minute : Number . parseInt ( match [ 2 ] , 10 ) ,
72+ second : match [ 3 ] ? Number . parseInt ( match [ 3 ] , 10 ) : 0 ,
73+ hasSecond : ! ! match [ 3 ] ,
74+ } ;
75+ }
76+
77+ function parseDateTimeLocalValue ( value : string ) : Date | null {
78+ const match = value . match ( / ^ ( \d { 4 } ) - ( \d { 2 } ) - ( \d { 2 } ) T ( \d { 2 } ) : ( \d { 2 } ) (?: : ( \d { 2 } ) (?: \. ( \d { 1 , 3 } ) ) ? ) ? $ / ) ;
79+ if ( ! match ) return null ;
80+ return new Date (
81+ Number . parseInt ( match [ 1 ] , 10 ) ,
82+ Number . parseInt ( match [ 2 ] , 10 ) - 1 ,
83+ Number . parseInt ( match [ 3 ] , 10 ) ,
84+ Number . parseInt ( match [ 4 ] , 10 ) ,
85+ Number . parseInt ( match [ 5 ] , 10 ) ,
86+ match [ 6 ] ? Number . parseInt ( match [ 6 ] , 10 ) : 0 ,
87+ match [ 7 ] ? Number . parseInt ( match [ 7 ] . padEnd ( 3 , "0" ) , 10 ) : 0
88+ ) ;
89+ }
90+
91+ function parseMonthValue ( value : string ) : Date | null {
92+ const match = value . match ( / ^ ( \d { 4 } ) - ( \d { 2 } ) $ / ) ;
93+ if ( ! match ) return null ;
94+ return new Date ( Number . parseInt ( match [ 1 ] , 10 ) , Number . parseInt ( match [ 2 ] , 10 ) - 1 , 1 , 12 , 0 , 0 , 0 ) ;
95+ }
96+
97+ function parseWeekValue ( value : string ) : { start : Date ; end : Date } | null {
98+ const match = value . match ( / ^ ( \d { 4 } ) - W ( \d { 2 } ) $ / ) ;
99+ if ( ! match ) return null ;
100+ const isoYear = Number . parseInt ( match [ 1 ] , 10 ) ;
101+ const isoWeek = Number . parseInt ( match [ 2 ] , 10 ) ;
102+ const jan4 = new Date ( isoYear , 0 , 4 , 12 , 0 , 0 , 0 ) ;
103+ const day = jan4 . getDay ( ) ;
104+ const mondayOffset = day === 0 ? - 6 : 1 - day ;
105+ const firstWeekMonday = new Date ( isoYear , 0 , 4 + mondayOffset , 12 , 0 , 0 , 0 ) ;
106+ const start = new Date ( firstWeekMonday ) ;
107+ start . setDate ( firstWeekMonday . getDate ( ) + ( isoWeek - 1 ) * 7 ) ;
108+ const end = new Date ( start ) ;
109+ end . setDate ( start . getDate ( ) + 6 ) ;
110+ return { start, end } ;
111+ }
112+
113+ function temporalDisplay ( type : string , value : string ) : string {
114+ if ( ! value ) return "" ;
115+ switch ( type ) {
116+ case "date" : {
117+ const date = parseDateValue ( value ) ;
118+ if ( ! date ) return value ;
119+ return new Intl . DateTimeFormat ( undefined , { year : "numeric" , month : "2-digit" , day : "2-digit" } ) . format ( date ) ;
120+ }
121+ case "time" : {
122+ const parts = parseTimeValue ( value ) ;
123+ if ( ! parts ) return value ;
124+ const date = new Date ( 2000 , 0 , 1 , parts . hour , parts . minute , parts . second , 0 ) ;
125+ return new Intl . DateTimeFormat ( undefined , {
126+ hour : "numeric" ,
127+ minute : "2-digit" ,
128+ second : parts . hasSecond ? "2-digit" : undefined ,
129+ } ) . format ( date ) ;
130+ }
131+ case "datetime-local" : {
132+ const date = parseDateTimeLocalValue ( value ) ;
133+ if ( ! date ) return value . replace ( "T" , " " ) ;
134+ return new Intl . DateTimeFormat ( undefined , { dateStyle : "short" , timeStyle : "short" } ) . format ( date ) ;
135+ }
136+ case "month" : {
137+ const date = parseMonthValue ( value ) ;
138+ if ( ! date ) return value ;
139+ return new Intl . DateTimeFormat ( undefined , { year : "numeric" , month : "long" } ) . format ( date ) ;
140+ }
141+ case "week" : {
142+ const parsed = parseWeekValue ( value ) ;
143+ if ( ! parsed ) return value ;
144+ const startLabel = new Intl . DateTimeFormat ( undefined , { month : "short" , day : "numeric" } ) . format ( parsed . start ) ;
145+ const endLabel = new Intl . DateTimeFormat ( undefined , { month : "short" , day : "numeric" } ) . format ( parsed . end ) ;
146+ const yearLabel = new Intl . DateTimeFormat ( undefined , { year : "numeric" } ) . format ( parsed . start ) ;
147+ return `${ startLabel } - ${ endLabel } , ${ yearLabel } ` ;
148+ }
149+ default :
150+ return value ;
151+ }
152+ }
153+
154+ function controlValue ( id : string ) : string {
155+ return ( document . getElementById ( id ) as HTMLInputElement ) . value ;
156+ }
157+
60158 return {
61159 gated : await summarize ( "text-input" , false ) ,
62160 textInput : await summarize ( "text-input" ) ,
@@ -78,6 +176,13 @@ test.describe("Form control conversion", () => {
78176 datetime : await summarize ( "datetime" ) ,
79177 month : await summarize ( "month" ) ,
80178 week : await summarize ( "week" ) ,
179+ expectedTemporal : {
180+ date : temporalDisplay ( "date" , controlValue ( "date" ) ) ,
181+ time : temporalDisplay ( "time" , controlValue ( "time" ) ) ,
182+ datetime : temporalDisplay ( "datetime-local" , controlValue ( "datetime" ) ) ,
183+ month : temporalDisplay ( "month" , controlValue ( "month" ) ) ,
184+ week : temporalDisplay ( "week" , controlValue ( "week" ) ) ,
185+ } ,
81186 } ;
82187 } ) ;
83188
@@ -129,11 +234,11 @@ test.describe("Form control conversion", () => {
129234 expect ( summary . file . texts ) . toContain ( "Choose File" ) ;
130235 expect ( summary . file . texts . join ( " " ) ) . toContain ( "notes.txt" ) ;
131236
132- expect ( summary . date . texts ) . toContain ( "2026-04-13" ) ;
133- expect ( summary . time . texts ) . toContain ( "14:35" ) ;
134- expect ( summary . datetime . texts ) . toContain ( "2026-04-13 14:35" ) ;
135- expect ( summary . month . texts ) . toContain ( "2026-04" ) ;
136- expect ( summary . week . texts ) . toContain ( "2026-W16" ) ;
237+ expect ( summary . date . texts ) . toContain ( summary . expectedTemporal . date ) ;
238+ expect ( summary . time . texts ) . toContain ( summary . expectedTemporal . time ) ;
239+ expect ( summary . datetime . texts ) . toContain ( summary . expectedTemporal . datetime ) ;
240+ expect ( summary . month . texts ) . toContain ( summary . expectedTemporal . month ) ;
241+ expect ( summary . week . texts ) . toContain ( summary . expectedTemporal . week ) ;
137242 } ) ;
138243
139244 test ( "uses placeholder pseudo styles and avoids inventing boxes for transparent proxy textareas" , async ( { page } ) => {
0 commit comments