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

IgniteUI / igniteui-webcomponents / 14613048661

23 Apr 2025 08:03AM UTC coverage: 98.269% (-0.05%) from 98.318%
14613048661

Pull #1621

github

web-flow
Merge 854ceafc5 into c7921cce3
Pull Request #1621: Add Tooltip component

4528 of 4760 branches covered (95.13%)

Branch coverage included in aggregate %.

922 of 948 new or added lines in 9 files covered. (97.26%)

29085 of 29445 relevant lines covered (98.78%)

441.86 hits per line

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

98.9
/src/components/tooltip/controller.ts
1
import type { ReactiveController } from 'lit';
11✔
2
import {
11✔
3
  addWeakEventListener,
11✔
4
  getElementByIdFromRoot,
11✔
5
  isString,
11✔
6
} from '../common/util.js';
11✔
7
import service from './service.js';
11✔
8
import type IgcTooltipComponent from './tooltip.js';
11✔
9

11✔
10
class TooltipController implements ReactiveController {
11✔
11
  //#region Internal properties and state
11✔
12

11✔
13
  private static readonly _listeners = [
11✔
14
    'pointerenter',
11✔
15
    'pointerleave',
11✔
16
  ] as const;
11✔
17

11✔
18
  private readonly _host: IgcTooltipComponent;
11✔
19
  private readonly _options: TooltipCallbacks;
11✔
20

11✔
21
  private _hostAbortController: AbortController | null = null;
11✔
22
  private _anchorAbortController: AbortController | null = null;
11✔
23

11✔
24
  private _showTriggers = new Set(['pointerenter']);
11✔
25
  private _hideTriggers = new Set(['pointerleave', 'click']);
11✔
26

11✔
27
  private _anchor: WeakRef<Element> | null = null;
11✔
28
  private _initialAnchor: WeakRef<Element> | null = null;
11✔
29

11✔
30
  private _isTransient = false;
11✔
31
  private _open = false;
11✔
32

11✔
33
  //#endregion
11✔
34

11✔
35
  //#region Public properties
11✔
36

11✔
37
  /** Whether the tooltip is in shown state. */
11✔
38
  public get open(): boolean {
11✔
39
    return this._open;
653✔
40
  }
653✔
41

11✔
42
  /** Sets the shown state of the current tooltip. */
11✔
43
  public set open(value: boolean) {
11✔
44
    this._open = value;
85✔
45

85✔
46
    if (this._open) {
85✔
47
      this._addTooltipListeners();
64✔
48
      service.add(this._host, this._options.onEscape);
64✔
49
    } else {
85✔
50
      if (this._isTransient) {
21✔
51
        this._isTransient = false;
2✔
52
        this.setAnchor(this._initialAnchor?.deref());
2✔
53
      }
2✔
54

21✔
55
      this._removeTooltipListeners();
21✔
56
      service.remove(this._host);
21✔
57
    }
21✔
58
  }
85✔
59

11✔
60
  /**
11✔
61
   * Returns the current tooltip anchor target if any.
11✔
62
   */
11✔
63
  public get anchor(): TooltipAnchor {
11✔
64
    return this._isTransient
199✔
65
      ? this._anchor?.deref()
6✔
66
      : this._initialAnchor?.deref();
193✔
67
  }
199✔
68

11✔
69
  /**
11✔
70
   * Returns the current set of hide triggers as a comma-separated string.
11✔
71
   */
11✔
72
  public get hideTriggers(): string {
11✔
73
    return Array.from(this._hideTriggers).join();
49✔
74
  }
49✔
75

11✔
76
  /**
11✔
77
   * Sets a new set of hide triggers from a comma-separated string.
11✔
78
   *
11✔
79
   * @remarks
11✔
80
   * If the tooltip already has an `anchor` bound it will remove the old
11✔
81
   * set of triggers from it and rebind it with the new one.
11✔
82
   */
11✔
83
  public set hideTriggers(value: string) {
11✔
84
    this._hideTriggers = parseTriggers(value);
2✔
85
    this._removeAnchorListeners();
2✔
86
    this._addAnchorListeners();
2✔
87
  }
2✔
88

11✔
89
  /**
11✔
90
   * Returns the current set of show triggers as a comma-separated string.
11✔
91
   */
11✔
92
  public get showTriggers(): string {
11✔
93
    return Array.from(this._showTriggers).join();
49✔
94
  }
49✔
95

11✔
96
  /**
11✔
97
   * Sets a new set of show triggers from a comma-separated string.
11✔
98
   *
11✔
99
   * @remarks
11✔
100
   * If the tooltip already has an `anchor` bound it will remove the old
11✔
101
   * set of triggers from it and rebind it with the new one.
11✔
102
   */
11✔
103
  public set showTriggers(value: string) {
11✔
104
    this._showTriggers = parseTriggers(value);
2✔
105
    this._removeAnchorListeners();
2✔
106
    this._addAnchorListeners();
2✔
107
  }
2✔
108

11✔
109
  //#endregion
11✔
110

11✔
111
  constructor(tooltip: IgcTooltipComponent, options: TooltipCallbacks) {
11✔
112
    this._host = tooltip;
61✔
113
    this._options = options;
61✔
114
    this._host.addController(this);
61✔
115
  }
61✔
116

11✔
117
  //#region Internal event listeners state
11✔
118

11✔
119
  private _addAnchorListeners(): void {
11✔
120
    const anchor = this.anchor;
40✔
121

40✔
122
    if (!anchor) {
40✔
123
      return;
2✔
124
    }
2✔
125

38✔
126
    this._anchorAbortController = new AbortController();
38✔
127
    const signal = this._anchorAbortController.signal;
38✔
128

38✔
129
    for (const each of this._showTriggers) {
40✔
130
      addWeakEventListener(anchor, each, this, { passive: true, signal });
41✔
131
    }
41✔
132

38✔
133
    for (const each of this._hideTriggers) {
40✔
134
      addWeakEventListener(anchor, each, this, { passive: true, signal });
75✔
135
    }
75✔
136
  }
40✔
137

11✔
138
  private _removeAnchorListeners(): void {
11✔
139
    this._anchorAbortController?.abort();
80✔
140
    this._anchorAbortController = null;
80✔
141
  }
80✔
142

11✔
143
  private _addTooltipListeners(): void {
11✔
144
    this._hostAbortController = new AbortController();
64✔
145
    const signal = this._hostAbortController.signal;
64✔
146

64✔
147
    for (const event of TooltipController._listeners) {
64✔
148
      this._host.addEventListener(event, this, { passive: true, signal });
128✔
149
    }
128✔
150
  }
64✔
151

11✔
152
  private _removeTooltipListeners(): void {
11✔
153
    this._hostAbortController?.abort();
64✔
154
    this._hostAbortController = null;
64✔
155
  }
64✔
156

11✔
157
  //#endregion
11✔
158

11✔
159
  //#region Event handlers
11✔
160

11✔
161
  private async _handleTooltipEvent(event: Event): Promise<void> {
11✔
162
    switch (event.type) {
2✔
163
      case 'pointerenter':
2✔
164
        await this._options.onShow.call(this._host);
1✔
165
        break;
1✔
166
      case 'pointerleave':
2✔
167
        await this._options.onHide.call(this._host);
1✔
168
        break;
1✔
169
      default:
2!
NEW
170
        return;
×
171
    }
2✔
172
  }
2✔
173

11✔
174
  private async _handleAnchorEvent(event: Event): Promise<void> {
11✔
175
    if (!this._open && this._showTriggers.has(event.type)) {
28✔
176
      await this._options.onShow.call(this._host);
17✔
177
    }
17✔
178

28✔
179
    if (this._open && this._hideTriggers.has(event.type)) {
28✔
180
      await this._options.onHide.call(this._host);
11✔
181
    }
11✔
182
  }
28✔
183

11✔
184
  /** @internal */
11✔
185
  public handleEvent(event: Event): void {
11✔
186
    if (event.target === this._host) {
30✔
187
      this._handleTooltipEvent(event);
2✔
188
    } else if (event.target === this._anchor?.deref()) {
30✔
189
      this._handleAnchorEvent(event);
27✔
190
    } else if (event.target === this._initialAnchor?.deref()) {
28✔
191
      this.open = false;
1✔
192
      this._handleAnchorEvent(event);
1✔
193
    }
1✔
194
  }
30✔
195

11✔
196
  //#endregion
11✔
197

11✔
198
  private _dispose(): void {
11✔
199
    this._removeAnchorListeners();
43✔
200
    this._removeTooltipListeners();
43✔
201
    service.remove(this._host);
43✔
202
    this._anchor = null;
43✔
203
    this._initialAnchor = null;
43✔
204
  }
43✔
205

11✔
206
  //#region Public API
11✔
207

11✔
208
  /**
11✔
209
   * Removes all triggers from the previous `anchor` target and rebinds the current
11✔
210
   * sets back to the new value if it exists.
11✔
211
   */
11✔
212
  public setAnchor(value: TooltipAnchor | string, transient = false): void {
11✔
213
    const newAnchor = isString(value)
80✔
214
      ? getElementByIdFromRoot(this._host, value)
1✔
215
      : value;
79✔
216

80✔
217
    if (this._anchor?.deref() === newAnchor) {
80✔
218
      return;
44✔
219
    }
44✔
220

36✔
221
    // Tooltip `show()` method called with a target. Set to hidden state.
36✔
222
    if (transient && this._open) {
80✔
223
      this.open = false;
1✔
224
    }
1✔
225

36✔
226
    if (this._anchor?.deref() !== this._initialAnchor?.deref()) {
80✔
227
      this._removeAnchorListeners();
33✔
228
    }
33✔
229

36✔
230
    this._anchor = newAnchor ? new WeakRef(newAnchor) : null;
80!
231
    this._isTransient = transient;
80✔
232
    this._addAnchorListeners();
80✔
233
  }
80✔
234

11✔
235
  public resolveAnchor(value: TooltipAnchor | string): void {
11✔
236
    const resolvedElement = isString(value)
74✔
237
      ? getElementByIdFromRoot(this._host, value)
59✔
238
      : value;
15✔
239

74✔
240
    this._initialAnchor = resolvedElement ? new WeakRef(resolvedElement) : null;
74✔
241
    this.setAnchor(resolvedElement);
74✔
242
  }
74✔
243

11✔
244
  //#endregion
11✔
245

11✔
246
  //#region ReactiveController interface
11✔
247

11✔
248
  /** @internal */
11✔
249
  public hostConnected(): void {
11✔
250
    this.resolveAnchor(this._host.anchor);
43✔
251
  }
43✔
252

11✔
253
  /** @internal */
11✔
254
  public hostDisconnected(): void {
11✔
255
    this._dispose();
43✔
256
  }
43✔
257

11✔
258
  //#endregion
11✔
259
}
11✔
260

11✔
261
function parseTriggers(string: string): Set<string> {
4✔
262
  return new Set(
4✔
263
    (string ?? '').split(TooltipRegexes.triggers).filter((s) => s.trim())
4!
264
  );
4✔
265
}
4✔
266

11✔
267
export const TooltipRegexes = Object.freeze({
11✔
268
  /** Used for parsing the strings passed in the tooltip `show/hide-trigger` properties. */
11✔
269
  triggers: /[,\s]+/,
11✔
270

11✔
271
  /** Matches horizontal `PopoverPlacement` start positions. */
11✔
272
  horizontalStart: /^(left|right)-start$/,
11✔
273

11✔
274
  /** Matches horizontal `PopoverPlacement` end positions. */
11✔
275
  horizontalEnd: /^(left|right)-end$/,
11✔
276

11✔
277
  /** Matches vertical `PopoverPlacement` start positions. */
11✔
278
  start: /start$/,
11✔
279

11✔
280
  /** Matches vertical `PopoverPlacement` end positions. */
11✔
281
  end: /end$/,
11✔
282
});
11✔
283

11✔
284
export function addTooltipController(
11✔
285
  host: IgcTooltipComponent,
61✔
286
  options: TooltipCallbacks
61✔
287
): TooltipController {
61✔
288
  return new TooltipController(host, options);
61✔
289
}
61✔
290

11✔
291
type TooltipAnchor = Element | null | undefined;
11✔
292

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

© 2026 Coveralls, Inc