Skip to content

Commit 98247ed

Browse files
authored
feat: add tooltip content-changed event and use it in controller (#10045) (#10046)
1 parent f36d10c commit 98247ed

File tree

7 files changed

+140
-9
lines changed

7 files changed

+140
-9
lines changed

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

Lines changed: 14 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,7 +51,8 @@ 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
/**
@@ -60,8 +62,10 @@ export class TooltipController extends SlotController {
6062
* @protected
6163
* @override
6264
*/
63-
teardownNode() {
64-
this.__notifyChange();
65+
teardownNode(tooltipNode) {
66+
tooltipNode.removeEventListener('content-changed', this.__onContentChange);
67+
68+
this.__notifyChange(null);
6569
}
6670

6771
/**
@@ -160,7 +164,12 @@ export class TooltipController extends SlotController {
160164
}
161165

162166
/** @private */
163-
__notifyChange() {
164-
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 } }));
165174
}
166175
}

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 { definePolymer, fixtureSync, nextFrame } from '@vaadin/testing-helpers';
2+
import { definePolymer, fire, fixtureSync, nextFrame } from '@vaadin/testing-helpers';
33
import sinon from 'sinon';
44
import { ControllerMixin } from '../src/controller-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: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,8 @@ export const TooltipMixin = (superClass) =>
700700

701701
// Update the sr-only label text content
702702
this._overlayContent = root.textContent;
703+
704+
this.dispatchEvent(new CustomEvent('content-changed', { detail: { content: root.textContent } }));
703705
}
704706

705707
/** @private */
@@ -742,4 +744,10 @@ export const TooltipMixin = (superClass) =>
742744
srLabel.textContent = textContent;
743745
}
744746
}
747+
748+
/**
749+
* Fired when the tooltip text content is changed.
750+
*
751+
* @event content-changed
752+
*/
745753
};

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

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

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

13+
/**
14+
* Fired when the tooltip text content is changed.
15+
*/
16+
export type TooltipContentChangedEvent = CustomEvent<{ content: string }>;
17+
18+
export interface TooltipCustomEventMap {
19+
'content-changed': TooltipContentChangedEvent;
20+
}
21+
22+
export interface TooltipEventMap extends HTMLElementEventMap, TooltipCustomEventMap {}
23+
1324
/**
1425
* `<vaadin-tooltip>` is a Web Component for creating tooltips.
1526
*
@@ -47,6 +58,8 @@ export { TooltipPosition } from './vaadin-tooltip-mixin.js';
4758
* `--vaadin-tooltip-offset-end` | Used as an offset when the tooltip is aligned horizontally before the target
4859
*
4960
* See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.
61+
*
62+
* @fires {CustomEvent} content-changed - Fired when the tooltip text content is changed.
5063
*/
5164
declare class Tooltip extends TooltipMixin(ThemePropertyMixin(ControllerMixin(ElementMixin(HTMLElement)))) {
5265
/**
@@ -66,6 +79,18 @@ declare class Tooltip extends TooltipMixin(ThemePropertyMixin(ControllerMixin(El
6679
* except for those that have hover delay configured using property.
6780
*/
6881
static setDefaultHoverDelay(hoverDelay: number): void;
82+
83+
addEventListener<K extends keyof TooltipEventMap>(
84+
type: K,
85+
listener: (this: Tooltip, ev: TooltipEventMap[K]) => void,
86+
options?: AddEventListenerOptions | boolean,
87+
): void;
88+
89+
removeEventListener<K extends keyof TooltipEventMap>(
90+
type: K,
91+
listener: (this: Tooltip, ev: TooltipEventMap[K]) => void,
92+
options?: EventListenerOptions | boolean,
93+
): void;
6994
}
7095

7196
declare global {

packages/tooltip/src/vaadin-tooltip.js

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

packages/tooltip/test/tooltip.test.js

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

105122
describe('generator', () => {
@@ -150,6 +167,43 @@ describe('vaadin-tooltip', () => {
150167
await nextUpdate(tooltip);
151168
expect(overlay.hasAttribute('hidden')).to.be.true;
152169
});
170+
171+
it('should fire content-changed event when generator changes', async () => {
172+
const spy = sinon.spy();
173+
tooltip.addEventListener('content-changed', spy);
174+
175+
tooltip.generator = () => 'Foo';
176+
await nextUpdate(tooltip);
177+
expect(spy.calledOnce).to.be.true;
178+
expect(spy.firstCall.args[0].detail).to.deep.equal({ content: 'Foo' });
179+
180+
spy.resetHistory();
181+
182+
tooltip.generator = () => '';
183+
await nextUpdate(tooltip);
184+
expect(spy.calledOnce).to.be.true;
185+
expect(spy.firstCall.args[0].detail).to.deep.equal({ content: '' });
186+
});
187+
188+
it('should fire content-changed event when context changes', async () => {
189+
const spy = sinon.spy();
190+
tooltip.addEventListener('content-changed', spy);
191+
192+
tooltip.generator = (context) => context.text;
193+
spy.resetHistory();
194+
195+
tooltip.context = { text: 'Foo' };
196+
await nextUpdate(tooltip);
197+
expect(spy.calledOnce).to.be.true;
198+
expect(spy.firstCall.args[0].detail).to.deep.equal({ content: 'Foo' });
199+
200+
spy.resetHistory();
201+
202+
tooltip.context = { text: 'Bar' };
203+
await nextUpdate(tooltip);
204+
expect(spy.calledOnce).to.be.true;
205+
expect(spy.firstCall.args[0].detail).to.deep.equal({ content: 'Bar' });
206+
});
153207
});
154208

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

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

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

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

@@ -32,3 +32,9 @@ assertType<(target: HTMLElement, context?: Record<string, unknown>) => boolean>(
3232
assertType<(delay: number) => void>(Tooltip.setDefaultFocusDelay);
3333
assertType<(delay: number) => void>(Tooltip.setDefaultHideDelay);
3434
assertType<(delay: number) => void>(Tooltip.setDefaultHoverDelay);
35+
36+
// Events
37+
tooltip.addEventListener('content-changed', (event) => {
38+
assertType<TooltipContentChangedEvent>(event);
39+
assertType<string>(event.detail.content);
40+
});

0 commit comments

Comments
 (0)