Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add ariaTarget property for setting aria-describedby #6320

Merged
merged 3 commits into from
Aug 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions packages/component-base/src/tooltip-controller.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ type TooltipPosition =
* A controller that manages the slotted tooltip element.
*/
export class TooltipController extends SlotController {
/**
* An HTML element for linking with the tooltip overlay
* via `aria-describedby` attribute used by screen readers.
* When not set, defaults to `target`.
*/
ariaTarget: HTMLElement;

/**
* Object with properties passed to `generator`
* function to be used for generating tooltip text.
Expand Down Expand Up @@ -51,6 +58,12 @@ export class TooltipController extends SlotController {
*/
target: HTMLElement;

/**
* Set an HTML element for linking with the tooltip overlay
* via `aria-describedby` attribute used by screen readers.
*/
setAriaTarget(ariaTarget: HTMLElement): void;

/**
* Set a context object to be used by generator.
*/
Expand Down
18 changes: 18 additions & 0 deletions packages/component-base/src/tooltip-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ export class TooltipController extends SlotController {
initCustomNode(tooltipNode) {
tooltipNode.target = this.target;

if (this.ariaTarget !== undefined) {
tooltipNode.ariaTarget = this.ariaTarget;
}

if (this.context !== undefined) {
tooltipNode.context = this.context;
}
Expand All @@ -47,6 +51,20 @@ export class TooltipController extends SlotController {
}
}

/**
* Set an HTML element for linking with the tooltip overlay
* via `aria-describedby` attribute used by screen readers.
* @param {HTMLElement} ariaTarget
*/
setAriaTarget(ariaTarget) {
this.ariaTarget = ariaTarget;

const tooltipNode = this.node;
if (tooltipNode) {
tooltipNode.ariaTarget = ariaTarget;
}
}

/**
* Set a context object to be used by generator.
* @param {object} context
Expand Down
7 changes: 7 additions & 0 deletions packages/component-base/test/tooltip-controller.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,13 @@ describe('TooltipController', () => {
controller.setPosition('top-start');
expect(tooltip.position).to.not.eql('top-start');
});

it('should update tooltip ariaTarget using controller setAriaTarget method', () => {
const input = document.createElement('input');
host.appendChild(input);
controller.setAriaTarget(input);
expect(tooltip.ariaTarget).to.equal(input);
});
});

describe('slotted tooltip', () => {
Expand Down
6 changes: 6 additions & 0 deletions packages/tooltip/src/vaadin-tooltip.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ declare class Tooltip extends OverlayClassMixin(ThemePropertyMixin(ElementMixin(
*/
static setDefaultHoverDelay(delay: number): void;

/**
* Element used to link with the `aria-describedby`
* attribute. When not set, defaults to `target`.
*/
ariaTarget: HTMLElement | undefined;

/**
* Object with properties passed to `generator` and
* `shouldShow` functions for generating tooltip text
Expand Down
39 changes: 35 additions & 4 deletions packages/tooltip/src/vaadin-tooltip.js
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,14 @@ class Tooltip extends OverlayClassMixin(ThemePropertyMixin(ElementMixin(PolymerE

static get properties() {
return {
/**
* Element used to link with the `aria-describedby`
* attribute. When not set, defaults to `target`.
*/
ariaTarget: {
type: Object,
},

/**
* Object with properties passed to `generator` and
* `shouldShow` functions for generating tooltip text
Expand Down Expand Up @@ -420,6 +428,17 @@ class Tooltip extends OverlayClassMixin(ThemePropertyMixin(ElementMixin(PolymerE
value: 'bottom',
},

/**
* Element used to link with the `aria-describedby`
* attribute. When not set, defaults to `target`.
* @protected
*/
_effectiveAriaTarget: {
type: Object,
computed: '__computeAriaTarget(ariaTarget, target)',
observer: '__effectiveAriaTargetChanged',
},

/** @private */
__effectivePosition: {
type: String,
Expand Down Expand Up @@ -518,6 +537,11 @@ class Tooltip extends OverlayClassMixin(ThemePropertyMixin(ElementMixin(PolymerE
document.body.removeEventListener('vaadin-overlay-open', this.__onOverlayOpen);
}

/** @private */
__computeAriaTarget(ariaTarget, target) {
return ariaTarget || target;
}

/** @private */
__computeHorizontalAlign(position) {
return ['top-end', 'bottom-end', 'start-top', 'start', 'start-bottom'].includes(position) ? 'end' : 'start';
Expand Down Expand Up @@ -553,6 +577,17 @@ class Tooltip extends OverlayClassMixin(ThemePropertyMixin(ElementMixin(PolymerE
root.textContent = typeof this.generator === 'function' ? this.generator(this.context) : this.text;
}

/** @private */
__effectiveAriaTargetChanged(ariaTarget, oldAriaTarget) {
if (oldAriaTarget) {
removeValueFromAttribute(oldAriaTarget, 'aria-describedby', this._uniqueId);
}

if (ariaTarget) {
addValueToAttribute(ariaTarget, 'aria-describedby', this._uniqueId);
}
}

/** @private */
__autoOpenedChanged(opened, oldOpened) {
if (opened) {
Expand Down Expand Up @@ -596,8 +631,6 @@ class Tooltip extends OverlayClassMixin(ThemePropertyMixin(ElementMixin(PolymerE
oldTarget.removeEventListener('mousedown', this.__onMouseDown);

this.__targetVisibilityObserver.unobserve(oldTarget);

removeValueFromAttribute(oldTarget, 'aria-describedby', this._uniqueId);
}

if (target) {
Expand All @@ -611,8 +644,6 @@ class Tooltip extends OverlayClassMixin(ThemePropertyMixin(ElementMixin(PolymerE
requestAnimationFrame(() => {
this.__targetVisibilityObserver.observe(target);
});

addValueToAttribute(target, 'aria-describedby', this._uniqueId);
}
}

Expand Down
34 changes: 34 additions & 0 deletions packages/tooltip/test/tooltip.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,40 @@ describe('vaadin-tooltip', () => {
});
});

describe('ariaTarget', () => {
let target, ariaTarget;

beforeEach(() => {
target = document.createElement('div');
target.textContent = 'Target';
document.body.appendChild(target);

ariaTarget = document.createElement('input');
target.appendChild(ariaTarget);
});

afterEach(() => {
document.body.removeChild(target);
});

it('should set aria-describedby on the ariaTarget element', () => {
tooltip.target = target;
tooltip.ariaTarget = ariaTarget;

expect(ariaTarget.getAttribute('aria-describedby')).to.equal(overlay.id);
});

it('should remove aria-describedby when the ariaTarget is cleared', () => {
tooltip.target = target;
tooltip.ariaTarget = ariaTarget;

tooltip.ariaTarget = null;

expect(ariaTarget.hasAttribute('aria-describedby')).to.be.false;
expect(target.getAttribute('aria-describedby')).to.equal(overlay.id);
});
});

describe('for', () => {
let target;

Expand Down
1 change: 1 addition & 0 deletions packages/tooltip/test/typings/tooltip.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ assertType<ThemePropertyMixinClass>(tooltip);

// Properties
assertType<string | undefined>(tooltip.for);
assertType<HTMLElement | undefined>(tooltip.ariaTarget);
assertType<HTMLElement | undefined>(tooltip.target);
assertType<string | null | undefined>(tooltip.text);
assertType<Record<string, unknown>>(tooltip.context);
Expand Down