Skip to content

Commit 47c3586

Browse files
authored
feat: add tooltip content-changed event and use it in controller (#10045)
1 parent 15c3332 commit 47c3586

File tree

7 files changed

+138
-9
lines changed

7 files changed

+138
-9
lines changed

packages/component-base/src/tooltip-controller.js

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export class TooltipController extends SlotController {
1414
super(host, 'tooltip');
1515

1616
this.setTarget(host);
17+
this.__onContentChange = this.__onContentChange.bind(this);
1718
}
1819

1920
/**
@@ -50,17 +51,21 @@ export class TooltipController extends SlotController {
5051
tooltipNode.shouldShow = this.shouldShow;
5152
}
5253

53-
this.__notifyChange();
54+
this.__notifyChange(tooltipNode);
55+
tooltipNode.addEventListener('content-changed', this.__onContentChange);
5456
}
5557

5658
/**
5759
* Override to notify the host when the tooltip is removed.
5860
*
61+
* @param {Node} tooltipNode
5962
* @protected
6063
* @override
6164
*/
62-
teardownNode() {
63-
this.__notifyChange();
65+
teardownNode(tooltipNode) {
66+
tooltipNode.removeEventListener('content-changed', this.__onContentChange);
67+
68+
this.__notifyChange(null);
6469
}
6570

6671
/**
@@ -159,7 +164,12 @@ export class TooltipController extends SlotController {
159164
}
160165

161166
/** @private */
162-
__notifyChange() {
163-
this.dispatchEvent(new CustomEvent('tooltip-changed', { detail: { node: this.node } }));
167+
__onContentChange(event) {
168+
this.__notifyChange(event.target);
169+
}
170+
171+
/** @private */
172+
__notifyChange(node) {
173+
this.dispatchEvent(new CustomEvent('tooltip-changed', { detail: { node } }));
164174
}
165175
}

packages/component-base/test/tooltip-controller.test.js

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { expect } from '@vaadin/chai-plugins';
2-
import { defineLit, fixtureSync, nextFrame } from '@vaadin/testing-helpers';
2+
import { defineLit, fire, fixtureSync, nextFrame } from '@vaadin/testing-helpers';
33
import sinon from 'sinon';
44
import { PolylitMixin } from '../src/polylit-mixin.js';
55
import { TooltipController } from '../src/tooltip-controller.js';
@@ -166,15 +166,15 @@ describe('TooltipController', () => {
166166
expect(tooltip._position).to.eql('top-start');
167167
});
168168

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

177-
it('should fire tooltip-changed event on the host when the tooltip is removed', async () => {
177+
it('should fire tooltip-changed event when the tooltip is removed', async () => {
178178
const spy = sinon.spy();
179179
controller.addEventListener('tooltip-changed', spy);
180180
host.appendChild(tooltip);
@@ -184,5 +184,32 @@ describe('TooltipController', () => {
184184
await nextFrame();
185185
expect(spy).to.be.calledTwice;
186186
});
187+
188+
it('should fire tooltip-changed event on tooltip content-changed', async () => {
189+
const spy = sinon.spy();
190+
controller.addEventListener('tooltip-changed', spy);
191+
host.appendChild(tooltip);
192+
await nextFrame();
193+
194+
spy.resetHistory();
195+
fire(tooltip, 'content-changed');
196+
await nextFrame();
197+
expect(spy).to.be.calledOnce;
198+
});
199+
200+
it('should not fire tooltip-changed event on tooltip content-changed after removing', async () => {
201+
const spy = sinon.spy();
202+
controller.addEventListener('tooltip-changed', spy);
203+
host.appendChild(tooltip);
204+
await nextFrame();
205+
206+
host.removeChild(tooltip);
207+
await nextFrame();
208+
209+
spy.resetHistory();
210+
fire(tooltip, 'content-changed');
211+
await nextFrame();
212+
expect(spy).to.be.not.called;
213+
});
187214
});
188215
});

packages/tooltip/src/vaadin-tooltip-mixin.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -669,6 +669,7 @@ export const TooltipMixin = (superClass) =>
669669
/** @private */
670670
__updateContent() {
671671
this.__contentNode.textContent = typeof this.generator === 'function' ? this.generator(this.context) : this.text;
672+
this.dispatchEvent(new CustomEvent('content-changed', { detail: { content: this.__contentNode.textContent } }));
672673
}
673674

674675
/** @private */
@@ -692,4 +693,10 @@ export const TooltipMixin = (superClass) =>
692693
});
693694
}
694695
}
696+
697+
/**
698+
* Fired when the tooltip text content is changed.
699+
*
700+
* @event content-changed
701+
*/
695702
};

packages/tooltip/src/vaadin-tooltip.d.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,17 @@ import { TooltipMixin } from './vaadin-tooltip-mixin.js';
99

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

12+
/**
13+
* Fired when the tooltip text content is changed.
14+
*/
15+
export type TooltipContentChangedEvent = CustomEvent<{ content: string }>;
16+
17+
export interface TooltipCustomEventMap {
18+
'content-changed': TooltipContentChangedEvent;
19+
}
20+
21+
export interface TooltipEventMap extends HTMLElementEventMap, TooltipCustomEventMap {}
22+
1223
/**
1324
* `<vaadin-tooltip>` is a Web Component for creating tooltips.
1425
*
@@ -44,6 +55,8 @@ export { TooltipPosition } from './vaadin-tooltip-mixin.js';
4455
* `--vaadin-tooltip-offset-end` | Used as an offset when the tooltip is aligned horizontally before the target
4556
*
4657
* See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.
58+
*
59+
* @fires {CustomEvent} content-changed - Fired when the tooltip text content is changed.
4760
*/
4861
declare class Tooltip extends TooltipMixin(ThemePropertyMixin(ElementMixin(HTMLElement))) {
4962
/**
@@ -63,6 +76,18 @@ declare class Tooltip extends TooltipMixin(ThemePropertyMixin(ElementMixin(HTMLE
6376
* except for those that have hover delay configured using property.
6477
*/
6578
static setDefaultHoverDelay(hoverDelay: number): void;
79+
80+
addEventListener<K extends keyof TooltipEventMap>(
81+
type: K,
82+
listener: (this: Tooltip, ev: TooltipEventMap[K]) => void,
83+
options?: AddEventListenerOptions | boolean,
84+
): void;
85+
86+
removeEventListener<K extends keyof TooltipEventMap>(
87+
type: K,
88+
listener: (this: Tooltip, ev: TooltipEventMap[K]) => void,
89+
options?: EventListenerOptions | boolean,
90+
): void;
6691
}
6792

6893
declare global {

packages/tooltip/src/vaadin-tooltip.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ import { TooltipMixin } from './vaadin-tooltip-mixin.js';
4848
*
4949
* See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.
5050
*
51+
* @fires {CustomEvent} content-changed - Fired when the tooltip text content is changed.
52+
*
5153
* @customElement
5254
* @extends HTMLElement
5355
* @mixes ElementMixin

packages/tooltip/test/tooltip.test.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,23 @@ describe('vaadin-tooltip', () => {
103103
await nextUpdate(tooltip);
104104
expect(overlay.hasAttribute('hidden')).to.be.true;
105105
});
106+
107+
it('should fire content-changed event when text changes', async () => {
108+
const spy = sinon.spy();
109+
tooltip.addEventListener('content-changed', spy);
110+
111+
tooltip.text = 'Foo';
112+
await nextUpdate(tooltip);
113+
expect(spy.calledOnce).to.be.true;
114+
expect(spy.firstCall.args[0].detail).to.deep.equal({ content: 'Foo' });
115+
116+
spy.resetHistory();
117+
118+
tooltip.text = null;
119+
await nextUpdate(tooltip);
120+
expect(spy.calledOnce).to.be.true;
121+
expect(spy.firstCall.args[0].detail).to.deep.equal({ content: '' });
122+
});
106123
});
107124

108125
describe('generator', () => {
@@ -147,6 +164,41 @@ describe('vaadin-tooltip', () => {
147164
await nextUpdate(tooltip);
148165
expect(overlay.hasAttribute('hidden')).to.be.true;
149166
});
167+
168+
it('should fire content-changed event when generator changes', async () => {
169+
const spy = sinon.spy();
170+
tooltip.addEventListener('content-changed', spy);
171+
172+
tooltip.generator = () => 'Foo';
173+
await nextUpdate(tooltip);
174+
expect(spy.calledOnce).to.be.true;
175+
expect(spy.firstCall.args[0].detail).to.deep.equal({ content: 'Foo' });
176+
177+
spy.resetHistory();
178+
179+
tooltip.generator = () => '';
180+
await nextUpdate(tooltip);
181+
expect(spy.calledOnce).to.be.true;
182+
expect(spy.firstCall.args[0].detail).to.deep.equal({ content: '' });
183+
});
184+
185+
it('should fire content-changed event when context changes', async () => {
186+
const spy = sinon.spy();
187+
tooltip.addEventListener('content-changed', spy);
188+
189+
tooltip.context = { text: 'Foo' };
190+
tooltip.generator = (context) => context.text;
191+
await nextUpdate(tooltip);
192+
expect(spy.calledOnce).to.be.true;
193+
expect(spy.firstCall.args[0].detail).to.deep.equal({ content: 'Foo' });
194+
195+
spy.resetHistory();
196+
197+
tooltip.context = { text: 'Bar' };
198+
await nextUpdate(tooltip);
199+
expect(spy.calledOnce).to.be.true;
200+
expect(spy.firstCall.args[0].detail).to.deep.equal({ content: 'Bar' });
201+
});
150202
});
151203

152204
describe('target', () => {

packages/tooltip/test/typings/tooltip.types.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import '../../vaadin-tooltip.js';
22
import type { ElementMixinClass } from '@vaadin/component-base/src/element-mixin.js';
33
import type { ThemePropertyMixinClass } from '@vaadin/vaadin-themable-mixin/vaadin-theme-property-mixin.js';
4-
import { Tooltip, type TooltipPosition } from '../../vaadin-tooltip.js';
4+
import { Tooltip, type TooltipContentChangedEvent, type TooltipPosition } from '../../vaadin-tooltip.js';
55

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

@@ -29,3 +29,9 @@ assertType<(target: HTMLElement, context?: Record<string, unknown>) => boolean>(
2929
assertType<(delay: number) => void>(Tooltip.setDefaultFocusDelay);
3030
assertType<(delay: number) => void>(Tooltip.setDefaultHideDelay);
3131
assertType<(delay: number) => void>(Tooltip.setDefaultHoverDelay);
32+
33+
// Events
34+
tooltip.addEventListener('content-changed', (event) => {
35+
assertType<TooltipContentChangedEvent>(event);
36+
assertType<string>(event.detail.content);
37+
});

0 commit comments

Comments
 (0)