From e01555a4444b66911519ad6403c060d42b359bd3 Mon Sep 17 00:00:00 2001 From: Hauschild Date: Tue, 11 Nov 2025 13:22:04 +0100 Subject: [PATCH 1/6] fix(date-picker): update buttonLabel when localization changes --- .../components/date-picker/date-picker.tsx | 146 ++++++++++++------ 1 file changed, 100 insertions(+), 46 deletions(-) diff --git a/packages/components/src/components/date-picker/date-picker.tsx b/packages/components/src/components/date-picker/date-picker.tsx index ce73c2046c..c56c935cf5 100644 --- a/packages/components/src/components/date-picker/date-picker.tsx +++ b/packages/components/src/components/date-picker/date-picker.tsx @@ -249,6 +249,18 @@ export class DatePicker { } } + /** + * 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 }; // neue Ref + } + this.updateLocalizedDomBits(); // new methode + } + componentWillLoad() { if (this.popupTitle !== 'Pick a date') { statusNote({ @@ -334,53 +346,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.updateLocalizedDomBits(); } componentDidRender() { @@ -508,4 +477,89 @@ export class DatePicker { ); } + /** + * (Re)applies our manual DOM changes that depend on localization. + */ + private updateLocalizedDomBits = () => { + // 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' + }`; + } + }; } From 700404686704908cb021ccb650a44a7d58dc90fb Mon Sep 17 00:00:00 2001 From: Ricardo Hauschild Date: Tue, 11 Nov 2025 14:17:56 +0100 Subject: [PATCH 2/6] chore(date-picker): small changes --- .../components/src/components/date-picker/date-picker.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/components/src/components/date-picker/date-picker.tsx b/packages/components/src/components/date-picker/date-picker.tsx index c56c935cf5..7fa6257380 100644 --- a/packages/components/src/components/date-picker/date-picker.tsx +++ b/packages/components/src/components/date-picker/date-picker.tsx @@ -256,9 +256,9 @@ export class DatePicker { @Watch('localization') onLocalizationChange() { if (this.duetInput && this.localization) { - (this.duetInput as any).localization = { ...this.localization }; // neue Ref + (this.duetInput as any).localization = { ...this.localization }; } - this.updateLocalizedDomBits(); // new methode + this.updateLocalizedDomBits(); } componentWillLoad() { @@ -477,9 +477,6 @@ export class DatePicker { ); } - /** - * (Re)applies our manual DOM changes that depend on localization. - */ private updateLocalizedDomBits = () => { // Remove Duet’s default

and ensure our custom heading exists/updates const dialog = this.hostElement.querySelector('.duet-date__dialog'); From ab30478e5f45581f1fe9b786583b837729506d6a Mon Sep 17 00:00:00 2001 From: Ricardo Hauschild Date: Fri, 28 Nov 2025 17:22:16 +0100 Subject: [PATCH 3/6] refactor: removed spread operator and renamed method --- .../src/components/date-picker/date-picker.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/components/src/components/date-picker/date-picker.tsx b/packages/components/src/components/date-picker/date-picker.tsx index 7fa6257380..fef022e288 100644 --- a/packages/components/src/components/date-picker/date-picker.tsx +++ b/packages/components/src/components/date-picker/date-picker.tsx @@ -248,7 +248,6 @@ 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.). @@ -256,9 +255,9 @@ export class DatePicker { @Watch('localization') onLocalizationChange() { if (this.duetInput && this.localization) { - (this.duetInput as any).localization = { ...this.localization }; + (this.duetInput as any).localization = this.localization ; } - this.updateLocalizedDomBits(); + this.updateDomOnLocalizationChange(); } componentWillLoad() { @@ -349,7 +348,7 @@ export class DatePicker { this.adjustButtonsLabelsForA11y(); // Initialize all localized bits - this.updateLocalizedDomBits(); + this.updateDomOnLocalizationChange(); } componentDidRender() { @@ -477,7 +476,7 @@ export class DatePicker { ); } - private updateLocalizedDomBits = () => { + 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( From fc9ef2a57ca1eaa1095a9a93a95c636974a1ccfd Mon Sep 17 00:00:00 2001 From: Ricardo Hauschild Date: Fri, 28 Nov 2025 17:53:28 +0100 Subject: [PATCH 4/6] refactor: removed spread operator and renamed method --- packages/components/src/components/date-picker/date-picker.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/src/components/date-picker/date-picker.tsx b/packages/components/src/components/date-picker/date-picker.tsx index fef022e288..3bc10f91db 100644 --- a/packages/components/src/components/date-picker/date-picker.tsx +++ b/packages/components/src/components/date-picker/date-picker.tsx @@ -255,7 +255,7 @@ export class DatePicker { @Watch('localization') onLocalizationChange() { if (this.duetInput && this.localization) { - (this.duetInput as any).localization = this.localization ; + (this.duetInput as any).localization = this.localization; } this.updateDomOnLocalizationChange(); } From 12277bccbdd9f5f45f16b7a310cc18954650f105 Mon Sep 17 00:00:00 2001 From: Ricardo Hauschild Date: Sat, 29 Nov 2025 17:58:30 +0100 Subject: [PATCH 5/6] fix(button): ensure disabled attribute is removed when false to improve screen reader accessibility --- .../src/components/button/button.tsx | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/packages/components/src/components/button/button.tsx b/packages/components/src/components/button/button.tsx index dce8c505d1..99d7f16b28 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,19 @@ export class Button { private focusableElement: HTMLElement; private fallbackSubmitInputElement: HTMLInputElement; + private syncDisabledAttr() { + if (this.disabled) { + this.hostElement.setAttribute('disabled', ''); + } else { + this.hostElement.removeAttribute('disabled'); + } + } + + @Watch('disabled') + onDisabledChange() { + this.syncDisabledAttr(); + } + /** * Prevent clicks from being emitted from the host * when the component is `disabled`. @@ -119,6 +133,7 @@ export class Button { connectedCallback() { this.setIconPositionProp(); this.appendEnterKeySubmitFallback(); + this.syncDisabledAttr(); } disconnectedCallback() { @@ -226,7 +241,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} From 10f60513e788fdf1eb741112e3a977c813f3814e Mon Sep 17 00:00:00 2001 From: Ricardo Hauschild Date: Sun, 30 Nov 2025 13:19:24 +0100 Subject: [PATCH 6/6] fix(button): ensure disabled attribute is removed when false to improve screen reader accessibility --- .../components/src/components/button/button.tsx | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/components/src/components/button/button.tsx b/packages/components/src/components/button/button.tsx index 99d7f16b28..da08b11158 100644 --- a/packages/components/src/components/button/button.tsx +++ b/packages/components/src/components/button/button.tsx @@ -68,14 +68,6 @@ export class Button { private focusableElement: HTMLElement; private fallbackSubmitInputElement: HTMLInputElement; - private syncDisabledAttr() { - if (this.disabled) { - this.hostElement.setAttribute('disabled', ''); - } else { - this.hostElement.removeAttribute('disabled'); - } - } - @Watch('disabled') onDisabledChange() { this.syncDisabledAttr(); @@ -270,4 +262,11 @@ export class Button { this.disabled && `button--disabled` ); } + private syncDisabledAttr() { + if (this.disabled) { + this.hostElement.setAttribute('disabled', ''); + } else { + this.hostElement.removeAttribute('disabled'); + } + } }