Skip to content
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
20 changes: 15 additions & 5 deletions packages/component-base/src/tooltip-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export class TooltipController extends SlotController {
super(host, 'tooltip');

this.setTarget(host);
this.__onContentChange = this.__onContentChange.bind(this);
}

/**
Expand Down Expand Up @@ -50,17 +51,21 @@ export class TooltipController extends SlotController {
tooltipNode.shouldShow = this.shouldShow;
}

this.__notifyChange();
this.__notifyChange(tooltipNode);
tooltipNode.addEventListener('content-changed', this.__onContentChange);
}

/**
* Override to notify the host when the tooltip is removed.
*
* @param {Node} tooltipNode
* @protected
* @override
*/
teardownNode() {
this.__notifyChange();
teardownNode(tooltipNode) {
tooltipNode.removeEventListener('content-changed', this.__onContentChange);

this.__notifyChange(null);
}

/**
Expand Down Expand Up @@ -159,7 +164,12 @@ export class TooltipController extends SlotController {
}

/** @private */
__notifyChange() {
this.dispatchEvent(new CustomEvent('tooltip-changed', { detail: { node: this.node } }));
__onContentChange(event) {
this.__notifyChange(event.target);
}

/** @private */
__notifyChange(node) {
this.dispatchEvent(new CustomEvent('tooltip-changed', { detail: { node } }));
}
}
33 changes: 30 additions & 3 deletions packages/component-base/test/tooltip-controller.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { expect } from '@vaadin/chai-plugins';
import { defineLit, fixtureSync, nextFrame } from '@vaadin/testing-helpers';
import { defineLit, fire, fixtureSync, nextFrame } from '@vaadin/testing-helpers';
import sinon from 'sinon';
import { PolylitMixin } from '../src/polylit-mixin.js';
import { TooltipController } from '../src/tooltip-controller.js';
Expand Down Expand Up @@ -166,15 +166,15 @@ describe('TooltipController', () => {
expect(tooltip._position).to.eql('top-start');
});

it('should fire tooltip-changed event on the host when the tooltip is added', async () => {
it('should fire tooltip-changed event when the tooltip is added', async () => {
const spy = sinon.spy();
controller.addEventListener('tooltip-changed', spy);
host.appendChild(tooltip);
await nextFrame();
expect(spy).to.be.calledOnce;
});

it('should fire tooltip-changed event on the host when the tooltip is removed', async () => {
it('should fire tooltip-changed event when the tooltip is removed', async () => {
const spy = sinon.spy();
controller.addEventListener('tooltip-changed', spy);
host.appendChild(tooltip);
Expand All @@ -184,5 +184,32 @@ describe('TooltipController', () => {
await nextFrame();
expect(spy).to.be.calledTwice;
});

it('should fire tooltip-changed event on tooltip content-changed', async () => {
const spy = sinon.spy();
controller.addEventListener('tooltip-changed', spy);
host.appendChild(tooltip);
await nextFrame();

spy.resetHistory();
fire(tooltip, 'content-changed');
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests don't actually import vaadin-tooltip so we can't use its API and have to fire event manually.

await nextFrame();
expect(spy).to.be.calledOnce;
});

it('should not fire tooltip-changed event on tooltip content-changed after removing', async () => {
const spy = sinon.spy();
controller.addEventListener('tooltip-changed', spy);
host.appendChild(tooltip);
await nextFrame();

host.removeChild(tooltip);
await nextFrame();

spy.resetHistory();
fire(tooltip, 'content-changed');
await nextFrame();
expect(spy).to.be.not.called;
});
});
});
7 changes: 7 additions & 0 deletions packages/tooltip/src/vaadin-tooltip-mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,7 @@ export const TooltipMixin = (superClass) =>
/** @private */
__updateContent() {
this.__contentNode.textContent = typeof this.generator === 'function' ? this.generator(this.context) : this.text;
this.dispatchEvent(new CustomEvent('content-changed', { detail: { content: this.__contentNode.textContent } }));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checking textContent after setting it to always return a string if null or undefined was set.

}

/** @private */
Expand All @@ -692,4 +693,10 @@ export const TooltipMixin = (superClass) =>
});
}
}

/**
* Fired when the tooltip text content is changed.
*
* @event content-changed
*/
};
25 changes: 25 additions & 0 deletions packages/tooltip/src/vaadin-tooltip.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,17 @@ import { TooltipMixin } from './vaadin-tooltip-mixin.js';

export { TooltipPosition } from './vaadin-tooltip-mixin.js';

/**
* Fired when the tooltip text content is changed.
*/
export type TooltipContentChangedEvent = CustomEvent<{ content: string }>;

export interface TooltipCustomEventMap {
'content-changed': TooltipContentChangedEvent;
}

export interface TooltipEventMap extends HTMLElementEventMap, TooltipCustomEventMap {}

/**
* `<vaadin-tooltip>` is a Web Component for creating tooltips.
*
Expand Down Expand Up @@ -44,6 +55,8 @@ export { TooltipPosition } from './vaadin-tooltip-mixin.js';
* `--vaadin-tooltip-offset-end` | Used as an offset when the tooltip is aligned horizontally before the target
*
* See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.
*
* @fires {CustomEvent} content-changed - Fired when the tooltip text content is changed.
*/
declare class Tooltip extends TooltipMixin(ThemePropertyMixin(ElementMixin(HTMLElement))) {
/**
Expand All @@ -63,6 +76,18 @@ declare class Tooltip extends TooltipMixin(ThemePropertyMixin(ElementMixin(HTMLE
* except for those that have hover delay configured using property.
*/
static setDefaultHoverDelay(hoverDelay: number): void;

addEventListener<K extends keyof TooltipEventMap>(
type: K,
listener: (this: Tooltip, ev: TooltipEventMap[K]) => void,
options?: AddEventListenerOptions | boolean,
): void;

removeEventListener<K extends keyof TooltipEventMap>(
type: K,
listener: (this: Tooltip, ev: TooltipEventMap[K]) => void,
options?: EventListenerOptions | boolean,
): void;
}

declare global {
Expand Down
2 changes: 2 additions & 0 deletions packages/tooltip/src/vaadin-tooltip.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ import { TooltipMixin } from './vaadin-tooltip-mixin.js';
*
* See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.
*
* @fires {CustomEvent} content-changed - Fired when the tooltip text content is changed.
*
* @customElement
* @extends HTMLElement
* @mixes ElementMixin
Expand Down
52 changes: 52 additions & 0 deletions packages/tooltip/test/tooltip.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,23 @@ describe('vaadin-tooltip', () => {
await nextUpdate(tooltip);
expect(overlay.hasAttribute('hidden')).to.be.true;
});

it('should fire content-changed event when text changes', async () => {
const spy = sinon.spy();
tooltip.addEventListener('content-changed', spy);

tooltip.text = 'Foo';
await nextUpdate(tooltip);
expect(spy.calledOnce).to.be.true;
expect(spy.firstCall.args[0].detail).to.deep.equal({ content: 'Foo' });

spy.resetHistory();

tooltip.text = null;
await nextUpdate(tooltip);
expect(spy.calledOnce).to.be.true;
expect(spy.firstCall.args[0].detail).to.deep.equal({ content: '' });
});
});

describe('generator', () => {
Expand Down Expand Up @@ -147,6 +164,41 @@ describe('vaadin-tooltip', () => {
await nextUpdate(tooltip);
expect(overlay.hasAttribute('hidden')).to.be.true;
});

it('should fire content-changed event when generator changes', async () => {
const spy = sinon.spy();
tooltip.addEventListener('content-changed', spy);

tooltip.generator = () => 'Foo';
await nextUpdate(tooltip);
expect(spy.calledOnce).to.be.true;
expect(spy.firstCall.args[0].detail).to.deep.equal({ content: 'Foo' });

spy.resetHistory();

tooltip.generator = () => '';
await nextUpdate(tooltip);
expect(spy.calledOnce).to.be.true;
expect(spy.firstCall.args[0].detail).to.deep.equal({ content: '' });
});

it('should fire content-changed event when context changes', async () => {
const spy = sinon.spy();
tooltip.addEventListener('content-changed', spy);

tooltip.context = { text: 'Foo' };
tooltip.generator = (context) => context.text;
await nextUpdate(tooltip);
expect(spy.calledOnce).to.be.true;
expect(spy.firstCall.args[0].detail).to.deep.equal({ content: 'Foo' });

spy.resetHistory();

tooltip.context = { text: 'Bar' };
await nextUpdate(tooltip);
expect(spy.calledOnce).to.be.true;
expect(spy.firstCall.args[0].detail).to.deep.equal({ content: 'Bar' });
});
});

describe('target', () => {
Expand Down
8 changes: 7 additions & 1 deletion packages/tooltip/test/typings/tooltip.types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import '../../vaadin-tooltip.js';
import type { ElementMixinClass } from '@vaadin/component-base/src/element-mixin.js';
import type { ThemePropertyMixinClass } from '@vaadin/vaadin-themable-mixin/vaadin-theme-property-mixin.js';
import { Tooltip, type TooltipPosition } from '../../vaadin-tooltip.js';
import { Tooltip, type TooltipContentChangedEvent, type TooltipPosition } from '../../vaadin-tooltip.js';

const assertType = <TExpected>(actual: TExpected) => actual;

Expand Down Expand Up @@ -29,3 +29,9 @@ assertType<(target: HTMLElement, context?: Record<string, unknown>) => boolean>(
assertType<(delay: number) => void>(Tooltip.setDefaultFocusDelay);
assertType<(delay: number) => void>(Tooltip.setDefaultHideDelay);
assertType<(delay: number) => void>(Tooltip.setDefaultHoverDelay);

// Events
tooltip.addEventListener('content-changed', (event) => {
assertType<TooltipContentChangedEvent>(event);
assertType<string>(event.detail.content);
});