@@ -248,6 +248,17 @@ export class DatePicker {
248248 input . setAttribute ( 'placeholder' , newValue ) ;
249249 }
250250 }
251+ /**
252+ * Watch `localization` for changes and refresh all DOM bits we set manually
253+ * (buttonLabel/title, custom heading, weekday abbreviations, "today" suffix, etc.).
254+ */
255+ @Watch ( 'localization' )
256+ onLocalizationChange ( ) {
257+ if ( this . duetInput && this . localization ) {
258+ ( this . duetInput as any ) . localization = this . localization ;
259+ }
260+ this . updateDomOnLocalizationChange ( ) ;
261+ }
251262
252263 componentWillLoad ( ) {
253264 if ( this . popupTitle !== 'Pick a date' ) {
@@ -334,53 +345,10 @@ export class DatePicker {
334345 input . setAttribute ( 'aria-invalid' , 'true' ) ;
335346 }
336347
337- // Remove existing <h2> with `{Month} {Year}` text
338- const dialog = this . hostElement . querySelector ( '.duet-date__dialog' ) ;
339- let duetHeadingId : string = '' ;
340- if ( dialog ) {
341- duetHeadingId = dialog . getAttribute ( 'aria-labelledby' ) ;
342- if ( duetHeadingId ) {
343- const duetHeading = this . hostElement . querySelector ( `#${ duetHeadingId } ` ) ;
344- if ( duetHeading ) {
345- duetHeading . parentElement . removeChild ( duetHeading ) ;
346- }
347- }
348- }
349-
350- // Add custom <h2> heading
351- const dialogContent = this . hostElement . querySelector (
352- '.duet-date__dialog-content'
353- ) ;
354- if ( dialogContent ) {
355- const calendarHeading =
356- this . localization ?. calendarHeading || this . popupTitle || 'Pick a date' ;
357- const heading = document . createElement ( 'h2' ) ;
358- heading . id = duetHeadingId ; // link to .duet-date__dialog[aria-labelledby]
359- heading . className = 'scale-date-picker__popup-heading' ;
360- heading . innerHTML = calendarHeading ;
361- dialogContent . insertBefore ( heading , dialogContent . firstChild ) ;
362- }
363-
364- // truncate table headings to a single character
365- const tableHeadings = this . hostElement . querySelectorAll (
366- '.duet-date__table-header span[aria-hidden="true"]'
367- ) ;
368- if ( tableHeadings ) {
369- Array . from ( tableHeadings ) . forEach (
370- ( item ) => ( item . innerHTML = item . innerHTML [ 0 ] )
371- ) ;
372- }
373-
374- const today = this . hostElement . querySelector (
375- '.duet-date__day.is-today span.duet-date__vhidden'
376- ) ;
377- if ( today ) {
378- today . innerHTML = `${ today . innerHTML } , ${
379- this . localization ?. today || 'today'
380- } `;
381- }
382-
383348 this . adjustButtonsLabelsForA11y ( ) ;
349+
350+ // Initialize all localized bits
351+ this . updateDomOnLocalizationChange ( ) ;
384352 }
385353
386354 componentDidRender ( ) {
@@ -508,4 +476,86 @@ export class DatePicker {
508476 </ Host >
509477 ) ;
510478 }
479+ private updateDomOnLocalizationChange = ( ) => {
480+ // Remove Duet’s default <h2> and ensure our custom heading exists/updates
481+ const dialog = this . hostElement . querySelector ( '.duet-date__dialog' ) ;
482+ const dialogContent = this . hostElement . querySelector (
483+ '.duet-date__dialog-content'
484+ ) ;
485+ if ( dialog && dialogContent ) {
486+ const duetHeadingId = dialog . getAttribute ( 'aria-labelledby' ) || '' ;
487+ if ( duetHeadingId ) {
488+ const duetHeading = this . hostElement . querySelector < HTMLElement > (
489+ '#' + duetHeadingId
490+ ) ;
491+ if ( duetHeading && duetHeading . parentElement ) {
492+ duetHeading . parentElement . removeChild ( duetHeading ) ;
493+ }
494+ }
495+ const calendarHeading =
496+ this . localization ?. calendarHeading || this . popupTitle || 'Pick a date' ;
497+ let heading = this . hostElement . querySelector < HTMLElement > (
498+ '.scale-date-picker__popup-heading'
499+ ) ;
500+ if ( ! heading ) {
501+ heading = document . createElement ( 'h2' ) ;
502+ if ( duetHeadingId ) {
503+ heading . id = duetHeadingId ;
504+ heading . className = 'scale-date-picker__popup-heading' ;
505+ dialogContent . insertBefore ( heading , dialogContent . firstChild ) ;
506+ }
507+ }
508+ heading . textContent = calendarHeading ;
509+ }
510+
511+ // Toggle button (buttonLabel / title / aria-label)
512+ const toggleBtn =
513+ this . hostElement . querySelector < HTMLButtonElement > ( '.duet-date__toggle' ) ;
514+ const btnLabel = ( this . localization as any ) ?. buttonLabel ;
515+ if ( toggleBtn && btnLabel ) {
516+ toggleBtn . setAttribute ( 'title' , btnLabel ) ;
517+ toggleBtn . setAttribute ( 'aria-label' , btnLabel ) ;
518+ }
519+
520+ // Truncate weekday headings to one character
521+ // Update weekday headings based on current localization
522+ const short =
523+ ( this . localization as any ) ?. weekdays ?. shorthand || // Duet format
524+ ( this . localization as any ) ?. dayNamesShort || // alternative
525+ ( this . localization as any ) ?. dayNames ?. map ( ( d : string ) => d . slice ( 0 , 1 ) ) ||
526+ [ ] ;
527+
528+ // Adjust for firstDayOfWeek (Duet default = Monday = 1)
529+ const start = Number . isInteger ( this . firstDayOfWeek )
530+ ? Number ( this . firstDayOfWeek )
531+ : 1 ;
532+
533+ const ordered =
534+ short . length === 7
535+ ? [ ...short . slice ( start ) , ...short . slice ( 0 , start ) ]
536+ : short ;
537+
538+ // Apply the correct short labels to the DOM
539+ const tableHeadings = Array . from (
540+ this . hostElement . querySelectorAll < HTMLElement > (
541+ '.duet-date__table-header span[aria-hidden="true"]'
542+ )
543+ ) ;
544+
545+ tableHeadings . forEach ( ( el , i ) => {
546+ const label = ordered [ i ] ;
547+ // Use the first character if full names are provided
548+ el . textContent = label ? label [ 0 ] : ( el . textContent || '' ) . slice ( 0 , 1 ) ;
549+ } ) ;
550+
551+ // "today" visually hidden text
552+ const today = this . hostElement . querySelector (
553+ '.duet-date__day.is-today span.duet-date__vhidden'
554+ ) ;
555+ if ( today ) {
556+ today . innerHTML = `${ today . innerHTML } , ${
557+ this . localization ?. today || 'today'
558+ } `;
559+ }
560+ } ;
511561}
0 commit comments