diff --git a/packages/webawesome/docs/docs/resources/changelog.md b/packages/webawesome/docs/docs/resources/changelog.md index 102e640f9..444911683 100644 --- a/packages/webawesome/docs/docs/resources/changelog.md +++ b/packages/webawesome/docs/docs/resources/changelog.md @@ -36,6 +36,7 @@ Components with the Experimental badge sh - Fixed a bug in `` that prevented tooltips from showing when disconnecting and then reconnecting to the DOM [issue:1595] - Fixed a bug that caused the required `*` in form labels to have incorrect spacing in `` and `` [issue:1472] - Fixed a bug in `` and `` that caused the component to prematurely hide when certain child elements are used [pr:1636] +- Fixed a bug in `` and `` that prevented dots and other valid ID characters from being used [issue:1648] - Improved autofill styles in `` so they span the entire width of the visual input [issue:1439] - Modified `` to only show the tooltip on the handle being dragged when in range mode [issue:1320] - Improved [text utilities](/docs/utilities/text/) so that each size modifier always exactly matches the applied font size [pr:1602] diff --git a/packages/webawesome/src/components/popover/popover.ts b/packages/webawesome/src/components/popover/popover.ts index 376263663..63ed3a14a 100644 --- a/packages/webawesome/src/components/popover/popover.ts +++ b/packages/webawesome/src/components/popover/popover.ts @@ -230,7 +230,7 @@ export default class WaPopover extends WebAwesomeElement { return; } - const newAnchor = this.for ? rootNode.querySelector(`#${this.for}`) : null; + const newAnchor = this.for ? rootNode.getElementById(this.for) : null; const oldAnchor = this.anchor; if (newAnchor === oldAnchor) { diff --git a/packages/webawesome/src/components/tooltip/tooltip.ts b/packages/webawesome/src/components/tooltip/tooltip.ts index 9c16798c5..abbbcfd85 100644 --- a/packages/webawesome/src/components/tooltip/tooltip.ts +++ b/packages/webawesome/src/components/tooltip/tooltip.ts @@ -137,8 +137,7 @@ export default class WaTooltip extends WebAwesomeElement { this.eventController.abort(); if (this.anchor) { - const label = this.anchor.getAttribute('aria-labelledby') || ''; - this.anchor.setAttribute('aria-labelledby', label.replace(this.id, '')); + this.removeFromAriaLabelledBy(this.anchor, this.id); } } @@ -202,6 +201,34 @@ export default class WaTooltip extends WebAwesomeElement { return triggers.includes(triggerType); } + /** Adds the tooltip ID to the aria-labelledby attribute */ + private addToAriaLabelledBy(element: Element, id: string) { + const currentLabel = element.getAttribute('aria-labelledby') || ''; + const labels = currentLabel.split(/\s+/).filter(Boolean); + + // Only add if not already present + if (!labels.includes(id)) { + labels.push(id); + element.setAttribute('aria-labelledby', labels.join(' ')); + } + } + + /** Removes the tooltip ID from the aria-labelledby attribute */ + private removeFromAriaLabelledBy(element: Element, id: string) { + const currentLabel = element.getAttribute('aria-labelledby') || ''; + const labels = currentLabel.split(/\s+/).filter(Boolean); + + // Remove the ID + const filteredLabels = labels.filter(label => label !== id); + + if (filteredLabels.length > 0) { + element.setAttribute('aria-labelledby', filteredLabels.join(' ')); + } else { + // Remove the attribute if empty + element.removeAttribute('aria-labelledby'); + } + } + @watch('open', { waitUntilFirstUpdate: true }) async handleOpenChange() { if (this.open) { @@ -252,7 +279,7 @@ export default class WaTooltip extends WebAwesomeElement { return; } - const newAnchor = this.for ? rootNode.querySelector(`#${this.for}`) : null; + const newAnchor = this.for ? rootNode.getElementById(this.for) : null; const oldAnchor = this.anchor; if (newAnchor === oldAnchor) { @@ -261,9 +288,6 @@ export default class WaTooltip extends WebAwesomeElement { const { signal } = this.eventController; - // "\\b" is a space boundary, used for making sure we don't add the tooltip to aria-labelledby twice. - const labelRegex = new RegExp(`\\b${this.id}\\b`); - if (newAnchor) { /** * We use `aria-labelledby` because it seems to have the most consistent screen reader experience. @@ -272,10 +296,7 @@ export default class WaTooltip extends WebAwesomeElement { * whereas with `aria-labelledby` it'll still read on first focus. The APG does and WAI-ARIA does recommend aria-describedby * so perhaps once we have cross-root aria, we can revisit this decision. */ - const currentLabel = newAnchor.getAttribute('aria-labelledby') || ''; - if (!currentLabel.match(labelRegex)) { - newAnchor.setAttribute('aria-labelledby', currentLabel + ' ' + this.id); - } + this.addToAriaLabelledBy(newAnchor, this.id); newAnchor.addEventListener('blur', this.handleBlur, { capture: true, signal }); newAnchor.addEventListener('focus', this.handleFocus, { capture: true, signal }); @@ -285,8 +306,7 @@ export default class WaTooltip extends WebAwesomeElement { } if (oldAnchor) { - const label = oldAnchor.getAttribute('aria-labelledby') || ''; - oldAnchor.setAttribute('aria-labelledby', label.replace(labelRegex, '')); + this.removeFromAriaLabelledBy(oldAnchor, this.id); oldAnchor.removeEventListener('blur', this.handleBlur, { capture: true }); oldAnchor.removeEventListener('focus', this.handleFocus, { capture: true }); oldAnchor.removeEventListener('click', this.handleClick);