@@ -305,9 +305,19 @@ function extractSingleLineControlGeometry(
305305 const cs = getComputedStyle ( el ) ;
306306 const fontSize = getFontSize ( node . extractedStyle , cs ) ;
307307 const lineHeight = Math . min ( getLineHeight ( node . extractedStyle , cs , fontSize ) , localHeight ) ;
308+ const temporalIconKind = getTemporalInputIconKind ( el . type ) ;
309+ const iconSlotWidth = temporalIconKind ? clamp ( localHeight * 0.62 , 14 , 22 ) : 0 ;
310+ const iconGap = temporalIconKind ? clamp ( localHeight * 0.12 , 2 , 4 ) : 0 ;
308311 const paddingX = Math . max ( parsePx ( cs . paddingLeft ) , align === "center" ? 8 : 6 ) ;
309312 const paddingY = Math . max ( parsePx ( cs . paddingTop ) , 4 ) ;
310- const contentBox = insetRect ( localWidth , localHeight , paddingX , paddingY , Math . max ( parsePx ( cs . paddingRight ) , paddingX ) , Math . max ( parsePx ( cs . paddingBottom ) , paddingY ) ) ;
313+ const contentBox = insetRect (
314+ localWidth ,
315+ localHeight ,
316+ paddingX ,
317+ paddingY ,
318+ Math . max ( parsePx ( cs . paddingRight ) , paddingX ) + iconSlotWidth + iconGap ,
319+ Math . max ( parsePx ( cs . paddingBottom ) , paddingY )
320+ ) ;
311321
312322 const nodes : IRNode [ ] = [ ] ;
313323 if ( shouldRenderSyntheticControlBox ( node . extractedStyle , cs ) ) {
@@ -320,6 +330,22 @@ function extractSingleLineControlGeometry(
320330 } ) ;
321331 }
322332
333+ const iconStyleColor = isVisibleColor ( node . extractedStyle . color )
334+ ? ( node . extractedStyle . color as string )
335+ : DEFAULT_ICON_COLOR ;
336+ if ( temporalIconKind ) {
337+ const iconNodes = createTemporalInputIconNodes (
338+ quad ,
339+ localWidth ,
340+ localHeight ,
341+ temporalIconKind ,
342+ iconStyleColor ,
343+ node . extractedStyle . opacity ,
344+ globalIndex + 2
345+ ) ;
346+ nodes . push ( ...iconNodes ) ;
347+ }
348+
323349 const text = displayText . text . trim ( ) ;
324350 if ( ! text ) return nodes ;
325351
@@ -329,6 +355,149 @@ function extractSingleLineControlGeometry(
329355 return nodes ;
330356}
331357
358+ function getTemporalInputIconKind ( type : string ) : "calendar" | "clock" | null {
359+ const normalizedType = type . toLowerCase ( ) ;
360+ if ( normalizedType === "time" ) return "clock" ;
361+ if ( normalizedType === "date"
362+ || normalizedType === "datetime-local"
363+ || normalizedType === "month"
364+ || normalizedType === "week" ) {
365+ return "calendar" ;
366+ }
367+ return null ;
368+ }
369+
370+ function createTemporalInputIconNodes (
371+ quad : Quad ,
372+ localWidth : number ,
373+ localHeight : number ,
374+ kind : "calendar" | "clock" ,
375+ color : string ,
376+ opacity : number | undefined ,
377+ zIndex : number
378+ ) : IRNode [ ] {
379+ const size = clamp ( localHeight * 0.42 , 9 , 14 ) ;
380+ const rightInset = clamp ( localHeight * 0.32 , 10 , 14 ) ;
381+ const left = Math . max ( 0 , localWidth - size - rightInset ) ;
382+ const top = Math . max ( 0 , ( localHeight - size ) / 2 ) ;
383+ const right = left + size ;
384+ const bottom = top + size ;
385+ const strokeWidth = `${ clamp ( size * 0.08 , 1 , 1.4 ) } px` ;
386+
387+ if ( kind === "calendar" ) {
388+ const binderY = top + size * 0.3 ;
389+ const tabOffset = size * 0.22 ;
390+ const tabWidth = size * 0.14 ;
391+ return [
392+ {
393+ type : "polyline" ,
394+ points : [
395+ mapLocalPoint ( quad , left , top , localWidth , localHeight ) ,
396+ mapLocalPoint ( quad , right , top , localWidth , localHeight ) ,
397+ mapLocalPoint ( quad , right , bottom , localWidth , localHeight ) ,
398+ mapLocalPoint ( quad , left , bottom , localWidth , localHeight ) ,
399+ mapLocalPoint ( quad , left , top , localWidth , localHeight ) ,
400+ ] ,
401+ closed : false ,
402+ style : {
403+ stroke : color ,
404+ strokeWidth,
405+ opacity,
406+ } ,
407+ zIndex,
408+ } ,
409+ {
410+ type : "polyline" ,
411+ points : [
412+ mapLocalPoint ( quad , left , binderY , localWidth , localHeight ) ,
413+ mapLocalPoint ( quad , right , binderY , localWidth , localHeight ) ,
414+ ] ,
415+ closed : false ,
416+ style : {
417+ stroke : color ,
418+ strokeWidth,
419+ opacity,
420+ } ,
421+ zIndex,
422+ } ,
423+ {
424+ type : "polyline" ,
425+ points : [
426+ mapLocalPoint ( quad , left + tabOffset , top - size * 0.05 , localWidth , localHeight ) ,
427+ mapLocalPoint ( quad , left + tabOffset , top + tabWidth , localWidth , localHeight ) ,
428+ ] ,
429+ closed : false ,
430+ style : {
431+ stroke : color ,
432+ strokeWidth,
433+ opacity,
434+ } ,
435+ zIndex,
436+ } ,
437+ {
438+ type : "polyline" ,
439+ points : [
440+ mapLocalPoint ( quad , right - tabOffset , top - size * 0.05 , localWidth , localHeight ) ,
441+ mapLocalPoint ( quad , right - tabOffset , top + tabWidth , localWidth , localHeight ) ,
442+ ] ,
443+ closed : false ,
444+ style : {
445+ stroke : color ,
446+ strokeWidth,
447+ opacity,
448+ } ,
449+ zIndex,
450+ } ,
451+ ] ;
452+ }
453+
454+ const centerX = left + size / 2 ;
455+ const centerY = top + size / 2 ;
456+ const radius = size * 0.46 ;
457+ return [
458+ {
459+ type : "polyline" ,
460+ points : approximateEllipsePoints ( quad , centerX , centerY , radius , radius , localWidth , localHeight , 20 ) ,
461+ closed : true ,
462+ style : {
463+ stroke : color ,
464+ strokeWidth,
465+ fill : undefined ,
466+ opacity,
467+ } ,
468+ zIndex,
469+ } ,
470+ {
471+ type : "polyline" ,
472+ points : [
473+ mapLocalPoint ( quad , centerX , centerY , localWidth , localHeight ) ,
474+ mapLocalPoint ( quad , centerX , centerY - radius * 0.52 , localWidth , localHeight ) ,
475+ ] ,
476+ closed : false ,
477+ style : {
478+ stroke : color ,
479+ strokeWidth,
480+ opacity,
481+ } ,
482+ zIndex,
483+ } ,
484+ {
485+ type : "polyline" ,
486+ points : [
487+ mapLocalPoint ( quad , centerX , centerY , localWidth , localHeight ) ,
488+ mapLocalPoint ( quad , centerX + radius * 0.4 , centerY , localWidth , localHeight ) ,
489+ ] ,
490+ closed : false ,
491+ style : {
492+ stroke : color ,
493+ strokeWidth,
494+ opacity,
495+ } ,
496+ zIndex,
497+ } ,
498+ ] ;
499+ }
500+
332501function extractTextAreaGeometry (
333502 textarea : HTMLTextAreaElement ,
334503 node : StackingNode ,
@@ -1107,6 +1276,7 @@ function formatTemporalInputDisplayValue(
11071276 type : string ,
11081277 value : string
11091278) : string | null {
1279+ void input ;
11101280 try {
11111281 switch ( type ) {
11121282 case "date" : {
@@ -1147,6 +1317,9 @@ function formatTemporalInputDisplayValue(
11471317 case "week" : {
11481318 const parsed = parseWeekValue ( value ) ;
11491319 if ( ! parsed ) return null ;
1320+ if ( / ^ d e (?: - | $ ) / i. test ( parsed . locale ) ) {
1321+ return `Woche ${ parsed . isoWeek } , ${ parsed . isoYear } ` ;
1322+ }
11501323 const rangeFormatter = new Intl . DateTimeFormat ( undefined , {
11511324 month : "short" ,
11521325 day : "numeric" ,
@@ -1240,7 +1413,13 @@ function parseMonthValue(value: string): Date | null {
12401413 return new Date ( year , month - 1 , 1 , 12 , 0 , 0 , 0 ) ;
12411414}
12421415
1243- function parseWeekValue ( value : string ) : { start : Date ; end : Date } | null {
1416+ function parseWeekValue ( value : string ) : {
1417+ start : Date ;
1418+ end : Date ;
1419+ isoYear : number ;
1420+ isoWeek : number ;
1421+ locale : string ;
1422+ } | null {
12441423 const match = value . match ( / ^ ( \d { 4 } ) - W ( \d { 2 } ) $ / ) ;
12451424 if ( ! match ) return null ;
12461425
@@ -1257,8 +1436,9 @@ function parseWeekValue(value: string): { start: Date; end: Date } | null {
12571436 start . setDate ( firstWeekMonday . getDate ( ) + ( isoWeek - 1 ) * 7 ) ;
12581437 const end = new Date ( start ) ;
12591438 end . setDate ( start . getDate ( ) + 6 ) ;
1439+ const locale = new Intl . DateTimeFormat ( undefined ) . resolvedOptions ( ) . locale ;
12601440
1261- return { start, end } ;
1441+ return { start, end, isoYear , isoWeek , locale } ;
12621442}
12631443
12641444function defaultButtonLabel ( type : string ) : string {
0 commit comments