Skip to content

Commit 09ae8b1

Browse files
committed
Improves popover dismissal
1 parent 11a927b commit 09ae8b1

File tree

1 file changed

+30
-20
lines changed
  • src/webviews/apps/shared/components/overlays

1 file changed

+30
-20
lines changed

src/webviews/apps/shared/components/overlays/popover.ts

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,8 @@ interface CloseWatcherOptions {
2323
}
2424

2525
type TriggerType = 'hover' | 'focus' | 'click' | 'manual';
26-
27-
type LastOf<T> = UnionToIntersection<T extends any ? () => T : never> extends () => infer R ? R : never;
28-
type Push<T extends any[], V> = [...T, V];
29-
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
30-
31-
type TriggerCombinationHelper<T extends TriggerType, U extends TriggerType[] = []> = {
32-
[K in T]: K extends LastOf<U>
33-
? TriggerCombinationHelper<Exclude<T, K>, Push<U, K>>
34-
: `${U extends [] ? '' : `${U[number]} `}${K}` | TriggerCombinationHelper<Exclude<T, K>, Push<U, K>>;
35-
}[T];
36-
37-
type Triggers = TriggerCombinationHelper<TriggerType>;
26+
type Combine<T extends string, U extends string = T> = T extends any ? T | `${T} ${Combine<Exclude<U, T>>}` : never;
27+
type Triggers = Combine<TriggerType>;
3828

3929
declare global {
4030
interface HTMLElementTagNameMap {
@@ -247,24 +237,23 @@ export class GlPopover extends GlElement {
247237

248238
private handleTriggerBlur = (e: FocusEvent) => {
249239
if (this.open && this.hasTrigger('focus')) {
250-
const composedPath = e.composedPath();
251-
if (composedPath.includes(this)) return;
240+
if (e.relatedTarget && this.contains(e.relatedTarget as Node)) return;
252241

253242
void this.hide();
254243
}
255244
};
256245

257246
private handleTriggerClick = () => {
258247
if (this.hasTrigger('click')) {
259-
if (this.open) {
248+
if (this.open && this._triggeredBy !== 'hover') {
260249
if (this._skipHideOnClick) {
261250
this._skipHideOnClick = false;
262251
return;
263252
}
264253

265254
void this.hide();
266255
} else {
267-
void this.show();
256+
void this.show('click');
268257
}
269258
}
270259
};
@@ -280,7 +269,11 @@ export class GlPopover extends GlElement {
280269

281270
private handleTriggerFocus = () => {
282271
if (this.hasTrigger('focus')) {
283-
void this.show();
272+
if (this.open && this._triggeredBy !== 'hover' && !this.hasPopupFocus()) {
273+
void this.hide();
274+
} else {
275+
void this.show('focus');
276+
}
284277
}
285278
};
286279

@@ -303,12 +296,19 @@ export class GlPopover extends GlElement {
303296
void this.hide();
304297
};
305298

299+
private handleWebviewMouseDown = (e: MouseEvent) => {
300+
const composedPath = e.composedPath();
301+
if (!composedPath.includes(this)) {
302+
void this.hide();
303+
}
304+
};
305+
306306
private handleMouseOver = () => {
307307
if (this.hasTrigger('hover')) {
308308
clearTimeout(this.hoverTimeout);
309309

310310
const delay = parseDuration(getComputedStyle(this).getPropertyValue('--show-delay'));
311-
this.hoverTimeout = setTimeout(() => this.show(), delay);
311+
this.hoverTimeout = setTimeout(() => this.show('hover'), delay);
312312
}
313313
};
314314

@@ -319,7 +319,7 @@ export class GlPopover extends GlElement {
319319
const composedPath = e.composedPath();
320320
if (composedPath[composedPath.length - 2] === this) return;
321321

322-
if (this.hasPopupFocus()) return;
322+
if (this.hasPopupFocus() || this._triggeredBy !== 'hover') return;
323323

324324
const delay = parseDuration(getComputedStyle(this).getPropertyValue('--hide-delay'));
325325
this.hoverTimeout = setTimeout(() => this.hide(), delay);
@@ -353,6 +353,10 @@ export class GlPopover extends GlElement {
353353
document.addEventListener('focusin', this.handlePopupBlur);
354354
window.addEventListener('webview-blur', this.handleWebviewBlur, false);
355355

356+
if (this.hasTrigger('click') || this.hasTrigger('focus')) {
357+
document.addEventListener('mousedown', this.handleWebviewMouseDown);
358+
}
359+
356360
this.body.hidden = false;
357361
this.popup.active = true;
358362
this.popup.reposition();
@@ -361,6 +365,7 @@ export class GlPopover extends GlElement {
361365
} else {
362366
document.removeEventListener('focusin', this.handlePopupBlur);
363367
window.removeEventListener('webview-blur', this.handleWebviewBlur, false);
368+
document.removeEventListener('mousedown', this.handleWebviewMouseDown);
364369

365370
// Hide
366371

@@ -390,8 +395,12 @@ export class GlPopover extends GlElement {
390395
}
391396
}
392397

398+
private _triggeredBy: TriggerType | undefined;
393399
/** Shows the popover. */
394-
async show() {
400+
async show(triggeredBy?: TriggerType) {
401+
if (this._triggeredBy == null || triggeredBy !== 'hover') {
402+
this._triggeredBy = triggeredBy;
403+
}
395404
if (this.open) return undefined;
396405

397406
this.open = true;
@@ -400,6 +409,7 @@ export class GlPopover extends GlElement {
400409

401410
/** Hides the popover */
402411
async hide() {
412+
this._triggeredBy = undefined;
403413
if (!this.open) return undefined;
404414

405415
this.open = false;

0 commit comments

Comments
 (0)