Skip to content

Commit 02e4714

Browse files
wolfibDevtools-frontend LUCI CQ
authored andcommitted
Add option for Tooltip to be triggered by both hover and click
This is used in the info-icon tooltip for Console Insights Teasers. Previously the info-icon button had no on-click behavior. Now a click opens/closes the connected tooltip. This behavior is also how it's done e.g. in Gemini for Google Docs. Bug: 443618746 Change-Id: I057f1e167adab421963754b92de046aafc0669c9 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/7086472 Reviewed-by: Benedikt Meurer <[email protected]> Commit-Queue: Wolfgang Beyer <[email protected]>
1 parent a53e59d commit 02e4714

File tree

4 files changed

+82
-28
lines changed

4 files changed

+82
-28
lines changed

front_end/panels/console/ConsoleInsightTeaser.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,13 @@ export const DEFAULT_VIEW = (input: ViewInput, _output: undefined, target: HTMLE
178178
aria-details=${'teaser-info-tooltip-' + input.uuid}
179179
.accessibleLabel=${lockedString(UIStringsNotTranslate.learnDataUsage)}
180180
></devtools-button>
181-
<devtools-tooltip id=${'teaser-info-tooltip-' + input.uuid} variant="rich" jslogContext="teaser-info-tooltip">
181+
<devtools-tooltip
182+
id=${'teaser-info-tooltip-' + input.uuid}
183+
variant="rich"
184+
jslogContext="teaser-info-tooltip"
185+
trigger="both"
186+
hover-delay=500
187+
>
182188
<div class="info-tooltip-text">${lockedString(UIStringsNotTranslate.infoTooltipText)}</div>
183189
<div class="learn-more">
184190
<x-link

front_end/ui/components/docs/tooltip/basic.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ Lit.render(
3131
>
3232
Non-button click trigger
3333
</span>
34-
<devtools-tooltip id="rich-tooltip" variant="rich" use-click>
34+
<devtools-tooltip id="rich-tooltip" variant="rich" trigger="click">
3535
<p>Rich tooltip</p>
3636
<button>Action</button>
3737
</devtools-tooltip>

front_end/ui/components/tooltips/Tooltip.test.ts

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const {html, nothing} = Lit;
1313
interface RenderProps {
1414
variant?: Tooltips.Tooltip.TooltipVariant;
1515
attribute?: 'aria-describedby'|'aria-details';
16-
useClick?: boolean;
16+
trigger?: Tooltips.Tooltip.TooltipTrigger;
1717
useHotkey?: boolean;
1818
jslogContext?: string;
1919
id?: string;
@@ -22,7 +22,7 @@ interface RenderProps {
2222
function renderTooltip({
2323
variant = 'simple',
2424
attribute = 'aria-describedby',
25-
useClick = false,
25+
trigger = 'hover',
2626
useHotkey = false,
2727
jslogContext = undefined,
2828
id = 'tooltip-id',
@@ -38,7 +38,7 @@ function renderTooltip({
3838
id=${id}
3939
variant=${variant}
4040
hover-delay=${0}
41-
?use-click=${useClick}
41+
trigger=${trigger}
4242
?use-hotkey=${useHotkey}
4343
jslogContext=${jslogContext??nothing}
4444
>
@@ -233,9 +233,9 @@ describe('Tooltip', () => {
233233
assert.isFalse(tooltip.open);
234234
});
235235

236-
it('should not open on hover if use-click is set', () => {
236+
it('should not open on hover if `trigger` is set to `click`', () => {
237237
const clock = sinon.useFakeTimers({toFake: ['setTimeout']});
238-
const container = renderTooltip({useClick: true});
238+
const container = renderTooltip({trigger: 'click'});
239239

240240
const button = container.querySelector('button');
241241
button?.dispatchEvent(new MouseEvent('mouseenter'));
@@ -245,9 +245,9 @@ describe('Tooltip', () => {
245245
clock.restore();
246246
});
247247

248-
it('should not open on focus if use-click is set', () => {
248+
it('should not open on focus if `trigger` is set to `click`', () => {
249249
const clock = sinon.useFakeTimers({toFake: ['setTimeout']});
250-
const container = renderTooltip({useClick: true});
250+
const container = renderTooltip({trigger: 'click'});
251251

252252
const button = container.querySelector('button');
253253
button?.dispatchEvent(new FocusEvent('focus'));
@@ -257,8 +257,41 @@ describe('Tooltip', () => {
257257
clock.restore();
258258
});
259259

260-
it('should open with click if use-click is set', () => {
261-
const container = renderTooltip({useClick: true});
260+
it('should open with click if `trigger` is set to `click`', () => {
261+
const container = renderTooltip({trigger: 'click'});
262+
263+
const button = container.querySelector('button');
264+
button?.click();
265+
266+
assert.isTrue(container.querySelector('devtools-tooltip')?.open);
267+
});
268+
269+
it('should open on hover if `trigger` is set to `both`', () => {
270+
const clock = sinon.useFakeTimers({toFake: ['setTimeout']});
271+
const container = renderTooltip({trigger: 'both'});
272+
273+
const button = container.querySelector('button');
274+
button?.dispatchEvent(new MouseEvent('mouseenter'));
275+
276+
clock.runAll();
277+
assert.isTrue(container.querySelector('devtools-tooltip')?.open);
278+
clock.restore();
279+
});
280+
281+
it('should open on focus if `trigger` is set to `both`', () => {
282+
const clock = sinon.useFakeTimers({toFake: ['setTimeout']});
283+
const container = renderTooltip({trigger: 'both'});
284+
285+
const button = container.querySelector('button');
286+
button?.dispatchEvent(new FocusEvent('focus'));
287+
288+
clock.runAll();
289+
assert.isTrue(container.querySelector('devtools-tooltip')?.open);
290+
clock.restore();
291+
});
292+
293+
it('should open with click if `trigger` is set to `both`', () => {
294+
const container = renderTooltip({trigger: 'both'});
262295

263296
const button = container.querySelector('button');
264297
button?.click();

front_end/ui/components/tooltips/Tooltip.ts

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -167,13 +167,15 @@ const proposedRectForSimpleTooltip =
167167

168168
export type TooltipVariant = 'simple'|'rich';
169169
export type PaddingMode = 'small'|'large';
170+
export type TooltipTrigger = 'hover'|'click'|'both';
170171

171172
export interface TooltipProperties {
172173
id: string;
173174
variant?: TooltipVariant;
174175
padding?: PaddingMode;
175176
anchor?: HTMLElement;
176177
jslogContext?: string;
178+
trigger?: TooltipTrigger;
177179
}
178180

179181
/**
@@ -182,25 +184,27 @@ export interface TooltipProperties {
182184
* @property hoverDelay - reflects the `"hover-delay"` attribute.
183185
* @property variant - reflects the `"variant"` attribute.
184186
* @property padding - reflects the `"padding"` attribute.
185-
* @property useClick - reflects the `"click"` attribute.
187+
* @property trigger - reflects the `"trigger"` attribute.
186188
* @property verticalDistanceIncrease - reflects the `"vertical-distance-increase"` attribute.
187189
* @property preferSpanLeft - reflects the `"prefer-span-left"` attribute.
188190
* @attribute id - Id of the tooltip. Used for searching an anchor element with aria-describedby.
189191
* @attribute hover-delay - Hover length in ms before the tooltip is shown and hidden.
190192
* @attribute variant - Variant of the tooltip, `"simple"` for strings only, inverted background,
191193
* `"rich"` for interactive content, background according to theme's surface.
192194
* @attribute padding - Which padding to use, defaults to `"small"`. Use `"large"` for richer content.
193-
* @attribute use-click - If present, the tooltip will be shown on click instead of on hover.
195+
* @attribute trigger - Specifies which action triggers the tooltip. `"hover"` is the default. `"click"` means the
196+
* tooltip will be shown on click instead of hover. `"both"` means both hover and click trigger the
197+
* tooltip.
194198
* @attribute vertical-distance-increase - The tooltip is moved vertically this many pixels further away from its anchor.
195199
* @attribute prefer-span-left - If present, the tooltip's preferred position is `"span-left"` (The right
196200
* side of the tooltip and its anchor are aligned. The tooltip expands to the left from
197201
* there.). Applies to rich tooltips only.
198202
* @attribute use-hotkey - If present, the tooltip will be shown on hover but not when receiving focus.
199-
* Requires a hotkey to open when fosed (Alt-down). When `"use-click"` is present
200-
* as well, use-click takes precedence.
203+
* Requires a hotkey to open when fosed (Alt-down). When `"trigger"` is present
204+
* as well, `"trigger"` takes precedence.
201205
*/
202206
export class Tooltip extends HTMLElement {
203-
static readonly observedAttributes = ['id', 'variant', 'jslogcontext'];
207+
static readonly observedAttributes = ['id', 'variant', 'jslogcontext', 'trigger'];
204208
static lastOpenedTooltipId: string|null = null;
205209

206210
readonly #shadow = this.attachShadow({mode: 'open'});
@@ -231,16 +235,20 @@ export class Tooltip extends HTMLElement {
231235
}
232236
}
233237

234-
get useClick(): boolean {
235-
return this.hasAttribute('use-click') ?? false;
236-
}
237-
set useClick(useClick: boolean) {
238-
if (useClick) {
239-
this.setAttribute('use-click', '');
240-
} else {
241-
this.removeAttribute('use-click');
238+
get trigger(): TooltipTrigger {
239+
switch (this.getAttribute('trigger')) {
240+
case 'click':
241+
return 'click';
242+
case 'both':
243+
return 'both';
244+
case 'hover':
245+
default:
246+
return 'hover';
242247
}
243248
}
249+
set trigger(trigger: TooltipTrigger) {
250+
this.setAttribute('trigger', trigger);
251+
}
244252

245253
get hoverDelay(): number {
246254
return this.hasAttribute('hover-delay') ? Number(this.getAttribute('hover-delay')) : 300;
@@ -297,7 +305,7 @@ export class Tooltip extends HTMLElement {
297305

298306
constructor(properties?: TooltipProperties) {
299307
super();
300-
const {id, variant, padding, jslogContext, anchor} = properties ?? {};
308+
const {id, variant, padding, jslogContext, anchor, trigger} = properties ?? {};
301309
if (id) {
302310
this.id = id;
303311
}
@@ -317,6 +325,9 @@ export class Tooltip extends HTMLElement {
317325
}
318326
this.#anchor = anchor;
319327
}
328+
if (trigger) {
329+
this.trigger = trigger;
330+
}
320331
}
321332

322333
attributeChangedCallback(name: string, oldValue: string, newValue: string): void {
@@ -463,7 +474,7 @@ export class Tooltip extends HTMLElement {
463474
if (!this.hasAttribute('role')) {
464475
this.setAttribute('role', 'tooltip');
465476
}
466-
this.setAttribute('popover', this.useClick ? 'auto' : 'manual');
477+
this.setAttribute('popover', this.trigger === 'hover' ? 'manual' : 'auto');
467478
this.#updateJslog();
468479
}
469480

@@ -474,6 +485,9 @@ export class Tooltip extends HTMLElement {
474485
#setClosing = (event: Event): void => {
475486
if ((event as ToggleEvent).newState === 'closed') {
476487
this.#closing = true;
488+
if (this.#timeout) {
489+
window.clearTimeout(this.#timeout);
490+
}
477491
}
478492
};
479493

@@ -521,9 +535,10 @@ export class Tooltip extends HTMLElement {
521535
// as we always want to support ESC to close.
522536
this.#anchor.addEventListener('keydown', this.#keyDown);
523537

524-
if (this.useClick) {
538+
if (this.trigger === 'click' || this.trigger === 'both') {
525539
this.#anchor.addEventListener('click', this.toggle);
526-
} else {
540+
}
541+
if (this.trigger === 'hover' || this.trigger === 'both') {
527542
this.#anchor.addEventListener('mouseenter', this.showTooltip);
528543
if (!this.useHotkey) {
529544
this.#anchor.addEventListener('focus', this.showTooltip);

0 commit comments

Comments
 (0)