• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

IgniteUI / igniteui-webcomponents / 14510568203

17 Apr 2025 07:36AM UTC coverage: 98.265% (-0.05%) from 98.318%
14510568203

Pull #1621

github

web-flow
Merge f053392fd into d3cc33c87
Pull Request #1621: Add Tooltip component

4509 of 4740 branches covered (95.13%)

Branch coverage included in aggregate %.

856 of 883 new or added lines in 8 files covered. (96.94%)

2 existing lines in 1 file now uncovered.

29019 of 29380 relevant lines covered (98.77%)

442.5 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.34
/src/components/tooltip/tooltip-event-controller.ts
1
import type { ReactiveController } from 'lit';
11✔
2
import { getElementByIdFromRoot, isString } from '../common/util.js';
11✔
3
import service from './tooltip-service.js';
11✔
4
import type IgcTooltipComponent from './tooltip.js';
11✔
5

11✔
6
class TooltipController implements ReactiveController {
11✔
7
  //#region Internal properties and state
11✔
8

11✔
9
  private readonly _host: IgcTooltipComponent;
11✔
10
  private readonly _options: TooltipCallbacks;
11✔
11

11✔
12
  private _hostAbortController: AbortController | null = null;
11✔
13
  private _anchorAbortController: AbortController | null = null;
11✔
14

11✔
15
  private _showTriggers = new Set(['pointerenter']);
11✔
16
  private _hideTriggers = new Set(['pointerleave', 'click']);
11✔
17

11✔
18
  private _anchor: TooltipAnchor;
11✔
19
  private _defaultAnchor: TooltipAnchor;
11✔
20
  private _isTransientAnchor = false;
11✔
21
  private _open = false;
11✔
22

11✔
23
  //#endregion
11✔
24

11✔
25
  //#region Public properties
11✔
26

11✔
27
  /** Whether the tooltip is in shown state. */
11✔
28
  public get open(): boolean {
11✔
29
    return this._open;
11✔
30
  }
11✔
31

11✔
32
  /** Sets the shown state of the current tooltip. */
11✔
33
  public set open(value: boolean) {
11✔
34
    this._open = value;
76✔
35

76✔
36
    if (this._open) {
76✔
37
      this._addTooltipListeners();
56✔
38
      service.add(this._host, this._options.onEscape);
56✔
39
    } else {
76✔
40
      if (this._isTransientAnchor) {
20✔
41
        this._isTransientAnchor = false;
1✔
42
        this.setAnchor(this._defaultAnchor);
1✔
43
      }
1✔
44

20✔
45
      this._removeTooltipListeners();
20✔
46
      service.remove(this._host);
20✔
47
    }
20✔
48
  }
76✔
49

11✔
50
  /**
11✔
51
   * Returns the current tooltip anchor target if any.
11✔
52
   */
11✔
53
  public get anchor(): TooltipAnchor {
11✔
54
    return this._anchor;
153✔
55
  }
153✔
56

11✔
57
  /**
11✔
58
   * Returns the current set of hide triggers as a comma-separated string.
11✔
59
   */
11✔
60
  public get hideTriggers(): string {
11✔
61
    return Array.from(this._hideTriggers).join();
47✔
62
  }
47✔
63

11✔
64
  /**
11✔
65
   * Sets a new set of hide triggers from a comma-separated string.
11✔
66
   *
11✔
67
   * @remarks
11✔
68
   * If the tooltip already has an `anchor` bound it will remove the old
11✔
69
   * set of triggers from it and rebind it with the new one.
11✔
70
   */
11✔
71
  public set hideTriggers(value: string) {
11✔
72
    this._hideTriggers = parseTriggers(value);
2✔
73
    this._removeAnchorListeners();
2✔
74
    this._addAnchorListeners();
2✔
75
  }
2✔
76

11✔
77
  /**
11✔
78
   * Returns the current set of show triggers as a comma-separated string.
11✔
79
   */
11✔
80
  public get showTriggers(): string {
11✔
81
    return Array.from(this._showTriggers).join();
47✔
82
  }
47✔
83

11✔
84
  /**
11✔
85
   * Sets a new set of show triggers from a comma-separated string.
11✔
86
   *
11✔
87
   * @remarks
11✔
88
   * If the tooltip already has an `anchor` bound it will remove the old
11✔
89
   * set of triggers from it and rebind it with the new one.
11✔
90
   */
11✔
91
  public set showTriggers(value: string) {
11✔
92
    this._showTriggers = parseTriggers(value);
2✔
93
    this._removeAnchorListeners();
2✔
94
    this._addAnchorListeners();
2✔
95
  }
2✔
96

11✔
97
  //#endregion
11✔
98

11✔
99
  constructor(tooltip: IgcTooltipComponent, options: TooltipCallbacks) {
11✔
100
    this._host = tooltip;
59✔
101
    this._options = options;
59✔
102
    this._host.addController(this);
59✔
103
  }
59✔
104

11✔
105
  //#region Internal event listeners state
11✔
106

11✔
107
  private _addAnchorListeners(): void {
11✔
108
    if (!this._anchor) return;
35✔
109

33✔
110
    this._anchorAbortController = new AbortController();
33✔
111
    const signal = this._anchorAbortController.signal;
33✔
112

33✔
113
    for (const each of this._showTriggers) {
35✔
114
      this._anchor.addEventListener(each, this, { passive: true, signal });
36✔
115
    }
36✔
116

33✔
117
    for (const each of this._hideTriggers) {
35✔
118
      this._anchor.addEventListener(each, this, { passive: true, signal });
65✔
119
    }
65✔
120
  }
35✔
121

11✔
122
  private _removeAnchorListeners(): void {
11✔
123
    this._anchorAbortController?.abort();
75✔
124
    this._anchorAbortController = null;
75✔
125
  }
75✔
126

11✔
127
  private _addTooltipListeners(): void {
11✔
128
    this._hostAbortController = new AbortController();
56✔
129
    const signal = this._hostAbortController.signal;
56✔
130

56✔
131
    this._host.addEventListener('pointerenter', this, {
56✔
132
      passive: true,
56✔
133
      signal,
56✔
134
    });
56✔
135
    this._host.addEventListener('pointerleave', this, {
56✔
136
      passive: true,
56✔
137
      signal,
56✔
138
    });
56✔
139
  }
56✔
140

11✔
141
  private _removeTooltipListeners(): void {
11✔
142
    this._hostAbortController?.abort();
61✔
143
    this._hostAbortController = null;
61✔
144
  }
61✔
145

11✔
146
  //#endregion
11✔
147

11✔
148
  //#region Event handlers
11✔
149

11✔
150
  private _handleTooltipEvent(event: Event): void {
11✔
151
    switch (event.type) {
2✔
152
      case 'pointerenter':
2✔
153
        this._options.onShow.call(this._host);
1✔
154
        break;
1✔
155
      case 'pointerleave':
2✔
156
        this._options.onHide.call(this._host);
1✔
157
    }
2✔
158
  }
2✔
159

11✔
160
  private _handleAnchorEvent(event: Event): void {
11✔
161
    if (!this._open && this._showTriggers.has(event.type)) {
26✔
162
      this._options.onShow.call(this._host);
15✔
163
    }
15✔
164

26✔
165
    if (this._open && this._hideTriggers.has(event.type)) {
26✔
166
      this._options.onHide.call(this._host);
11✔
167
    }
11✔
168
  }
26✔
169

11✔
170
  /** @internal */
11✔
171
  public handleEvent(event: Event): void {
11✔
172
    if (event.target === this._host) {
28✔
173
      this._handleTooltipEvent(event);
2✔
174
    } else if (event.target === this._anchor) {
28✔
175
      this._handleAnchorEvent(event);
26✔
176
    } else if (event.target === this._defaultAnchor) {
26!
NEW
177
      this.open = false;
×
NEW
178
      this._handleAnchorEvent(event);
×
NEW
179
    }
×
180
  }
28✔
181

11✔
182
  //#endregion
11✔
183

11✔
184
  private _dispose(): void {
11✔
185
    this._removeAnchorListeners();
41✔
186
    this._anchor = null;
41✔
187
  }
41✔
188

11✔
189
  //#region Public API
11✔
190

11✔
191
  /**
11✔
192
   * Removes all triggers from the previous `anchor` target and rebinds the current
11✔
193
   * sets back to the new value if it exists.
11✔
194
   */
11✔
195
  public setAnchor(value: TooltipAnchor, transient = false): void {
11✔
196
    if (this._anchor === value) return;
73✔
197

31✔
198
    if (this._anchor !== this._defaultAnchor) {
73✔
199
      this._removeAnchorListeners();
30✔
200
    }
30✔
201

31✔
202
    this._anchor = value;
31✔
203
    this._isTransientAnchor = transient;
31✔
204
    this._addAnchorListeners();
31✔
205
  }
73✔
206

11✔
207
  public resolveAnchor(value: Element | string | undefined): void {
11✔
208
    const resolvedElement = isString(value)
70✔
209
      ? getElementByIdFromRoot(this._host, value)
55✔
210
      : value;
15✔
211

70✔
212
    this._defaultAnchor = resolvedElement;
70✔
213
    this.setAnchor(resolvedElement);
70✔
214
  }
70✔
215

11✔
216
  //#endregion
11✔
217

11✔
218
  //#region ReactiveController interface
11✔
219

11✔
220
  /** @internal */
11✔
221
  public hostConnected(): void {
11✔
222
    this.resolveAnchor(this._host.anchor);
41✔
223
  }
41✔
224

11✔
225
  /** @internal */
11✔
226
  public hostDisconnected(): void {
11✔
227
    this._dispose();
41✔
228
    this._removeTooltipListeners();
41✔
229
    service.remove(this._host);
41✔
230
  }
41✔
231

11✔
232
  //#endregion
11✔
233
}
11✔
234

11✔
235
function parseTriggers(string: string): Set<string> {
4✔
236
  return new Set((string ?? '').split(/[,\s]+/).filter((s) => s.trim()));
4!
237
}
4✔
238

11✔
239
export function addTooltipController(
11✔
240
  host: IgcTooltipComponent,
59✔
241
  options: TooltipCallbacks
59✔
242
): TooltipController {
59✔
243
  return new TooltipController(host, options);
59✔
244
}
59✔
245

11✔
246
type TooltipAnchor = Element | null | undefined;
11✔
247

11✔
248
type TooltipCallbacks = {
11✔
249
  onShow: (event?: Event) => unknown;
11✔
250
  onHide: (event?: Event) => unknown;
11✔
251
  onEscape: (event?: Event) => unknown;
11✔
252
};
11✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc