diff --git a/packages/components/src/components/button/button.tsx b/packages/components/src/components/button/button.tsx index dce8c505d1..da08b11158 100644 --- a/packages/components/src/components/button/button.tsx +++ b/packages/components/src/components/button/button.tsx @@ -17,6 +17,7 @@ import { Listen, Element, Method, + Watch, } from '@stencil/core'; import classNames from 'classnames'; import { hasShadowDom, ScaleIcon, isScaleIcon } from '../../utils/utils'; @@ -40,7 +41,7 @@ export class Button { /** (optional) Button variant */ @Prop() variant?: string = 'primary'; /** (optional) If `true`, the button is disabled */ - @Prop({ reflect: true }) disabled?: boolean = false; + @Prop() disabled?: boolean = false; /** (optional) Button type */ @Prop() type?: 'reset' | 'submit' | 'button'; /** (optional) The name of the button, submitted as a pair with the button's `value` as part of the form data */ @@ -67,6 +68,11 @@ export class Button { private focusableElement: HTMLElement; private fallbackSubmitInputElement: HTMLInputElement; + @Watch('disabled') + onDisabledChange() { + this.syncDisabledAttr(); + } + /** * Prevent clicks from being emitted from the host * when the component is `disabled`. @@ -119,6 +125,7 @@ export class Button { connectedCallback() { this.setIconPositionProp(); this.appendEnterKeySubmitFallback(); + this.syncDisabledAttr(); } disconnectedCallback() { @@ -226,7 +233,9 @@ export class Button { ref={(el) => (this.focusableElement = el)} class={this.getCssClassMap()} onClick={this.handleClick} - disabled={this.disabled} + disabled={ + this.disabled ? true : undefined + } /* Use undefined to properly remove the disabled attribute when false*/ type={this.type} part={basePart} tabIndex={this.innerTabindex} @@ -253,4 +262,11 @@ export class Button { this.disabled && `button--disabled` ); } + private syncDisabledAttr() { + if (this.disabled) { + this.hostElement.setAttribute('disabled', ''); + } else { + this.hostElement.removeAttribute('disabled'); + } + } } diff --git a/packages/components/src/components/date-picker/date-picker.tsx b/packages/components/src/components/date-picker/date-picker.tsx index ce73c2046c..3bc10f91db 100644 --- a/packages/components/src/components/date-picker/date-picker.tsx +++ b/packages/components/src/components/date-picker/date-picker.tsx @@ -248,6 +248,17 @@ export class DatePicker { input.setAttribute('placeholder', newValue); } } + /** + * Watch `localization` for changes and refresh all DOM bits we set manually + * (buttonLabel/title, custom heading, weekday abbreviations, "today" suffix, etc.). + */ + @Watch('localization') + onLocalizationChange() { + if (this.duetInput && this.localization) { + (this.duetInput as any).localization = this.localization; + } + this.updateDomOnLocalizationChange(); + } componentWillLoad() { if (this.popupTitle !== 'Pick a date') { @@ -334,53 +345,10 @@ export class DatePicker { input.setAttribute('aria-invalid', 'true'); } - // Remove existing

with `{Month} {Year}` text - const dialog = this.hostElement.querySelector('.duet-date__dialog'); - let duetHeadingId: string = ''; - if (dialog) { - duetHeadingId = dialog.getAttribute('aria-labelledby'); - if (duetHeadingId) { - const duetHeading = this.hostElement.querySelector(`#${duetHeadingId}`); - if (duetHeading) { - duetHeading.parentElement.removeChild(duetHeading); - } - } - } - - // Add custom

heading - const dialogContent = this.hostElement.querySelector( - '.duet-date__dialog-content' - ); - if (dialogContent) { - const calendarHeading = - this.localization?.calendarHeading || this.popupTitle || 'Pick a date'; - const heading = document.createElement('h2'); - heading.id = duetHeadingId; // link to .duet-date__dialog[aria-labelledby] - heading.className = 'scale-date-picker__popup-heading'; - heading.innerHTML = calendarHeading; - dialogContent.insertBefore(heading, dialogContent.firstChild); - } - - // truncate table headings to a single character - const tableHeadings = this.hostElement.querySelectorAll( - '.duet-date__table-header span[aria-hidden="true"]' - ); - if (tableHeadings) { - Array.from(tableHeadings).forEach( - (item) => (item.innerHTML = item.innerHTML[0]) - ); - } - - const today = this.hostElement.querySelector( - '.duet-date__day.is-today span.duet-date__vhidden' - ); - if (today) { - today.innerHTML = `${today.innerHTML}, ${ - this.localization?.today || 'today' - }`; - } - this.adjustButtonsLabelsForA11y(); + + // Initialize all localized bits + this.updateDomOnLocalizationChange(); } componentDidRender() { @@ -508,4 +476,86 @@ export class DatePicker { ); } + private updateDomOnLocalizationChange = () => { + // Remove Duet’s default

and ensure our custom heading exists/updates + const dialog = this.hostElement.querySelector('.duet-date__dialog'); + const dialogContent = this.hostElement.querySelector( + '.duet-date__dialog-content' + ); + if (dialog && dialogContent) { + const duetHeadingId = dialog.getAttribute('aria-labelledby') || ''; + if (duetHeadingId) { + const duetHeading = this.hostElement.querySelector( + '#' + duetHeadingId + ); + if (duetHeading && duetHeading.parentElement) { + duetHeading.parentElement.removeChild(duetHeading); + } + } + const calendarHeading = + this.localization?.calendarHeading || this.popupTitle || 'Pick a date'; + let heading = this.hostElement.querySelector( + '.scale-date-picker__popup-heading' + ); + if (!heading) { + heading = document.createElement('h2'); + if (duetHeadingId) { + heading.id = duetHeadingId; + heading.className = 'scale-date-picker__popup-heading'; + dialogContent.insertBefore(heading, dialogContent.firstChild); + } + } + heading.textContent = calendarHeading; + } + + // Toggle button (buttonLabel / title / aria-label) + const toggleBtn = + this.hostElement.querySelector('.duet-date__toggle'); + const btnLabel = (this.localization as any)?.buttonLabel; + if (toggleBtn && btnLabel) { + toggleBtn.setAttribute('title', btnLabel); + toggleBtn.setAttribute('aria-label', btnLabel); + } + + // Truncate weekday headings to one character + // Update weekday headings based on current localization + const short = + (this.localization as any)?.weekdays?.shorthand || // Duet format + (this.localization as any)?.dayNamesShort || // alternative + (this.localization as any)?.dayNames?.map((d: string) => d.slice(0, 1)) || + []; + + // Adjust for firstDayOfWeek (Duet default = Monday = 1) + const start = Number.isInteger(this.firstDayOfWeek) + ? Number(this.firstDayOfWeek) + : 1; + + const ordered = + short.length === 7 + ? [...short.slice(start), ...short.slice(0, start)] + : short; + + // Apply the correct short labels to the DOM + const tableHeadings = Array.from( + this.hostElement.querySelectorAll( + '.duet-date__table-header span[aria-hidden="true"]' + ) + ); + + tableHeadings.forEach((el, i) => { + const label = ordered[i]; + // Use the first character if full names are provided + el.textContent = label ? label[0] : (el.textContent || '').slice(0, 1); + }); + + // "today" visually hidden text + const today = this.hostElement.querySelector( + '.duet-date__day.is-today span.duet-date__vhidden' + ); + if (today) { + today.innerHTML = `${today.innerHTML}, ${ + this.localization?.today || 'today' + }`; + } + }; }