Skip to content

Commit

Permalink
feat: Add optional delay to popover (#345)
Browse files Browse the repository at this point in the history
* feat: Add optional delay to popover

* feat: Add delay argument to tooltip

* refactor: Separate timeout task and give tooltip default delay

* refactor: Adjust tasks and default per PR comments

* chore: Alphabetize component class
  • Loading branch information
ChesneyJulian authored Jan 27, 2025
1 parent 25f0d6f commit a165766
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 56 deletions.
19 changes: 19 additions & 0 deletions apps/docs-app/app/components/f/components/popover.gts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ export default class extends Component {
@tracked
arrow: boolean = true;

@tracked
delay?: number;

@tracked
flip: boolean = false;

Expand All @@ -62,6 +65,7 @@ export default class extends Component {
<Popover
@alignment={{this.alignment}}
@arrow={{this.arrow}}
@delay={{this.delay}}
@flip={{this.flip}}
@offset={{this.offset}}
@side={{this.side}}
Expand Down Expand Up @@ -105,6 +109,13 @@ export default class extends Component {
@value={{this.arrow}}
@onInput={{fn this.update "arrow"}}
/>
<Args.Number
@name="delay"
@defaultValue="undefined"
@description="Amount of delay before showing the popover (in milliseconds)"
@value={{this.delay}}
@onInput={{fn this.update "delay"}}
/>
<Args.Bool
@name="flip"
@defaultValue={{false}}
Expand Down Expand Up @@ -147,6 +158,7 @@ export default class extends Component {
<Popover
@alignment={{this.alignment}}
@arrow={{this.arrow}}
@delay={{this.delay}}
@flip={{this.flip}}
@offset={{this.offset}}
@side={{this.side}}
Expand Down Expand Up @@ -186,6 +198,13 @@ export default class extends Component {
@value={{this.arrow}}
@onInput={{fn this.update "arrow"}}
/>
<Args.Number
@name="delay"
@defaultValue="undefined"
@description="Amount of delay before showing the popover (in milliseconds)"
@value={{this.delay}}
@onInput={{fn this.update "delay"}}
/>
<Args.Bool
@name="flip"
@defaultValue={{false}}
Expand Down
11 changes: 11 additions & 0 deletions apps/docs-app/app/components/f/components/tooltip.gts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ export default class extends Component {
@tracked
alignment?: Alignment;

@tracked
delay?: number;

@tracked
flip?: boolean;

Expand All @@ -40,6 +43,7 @@ export default class extends Component {
<div class="p-2">
<Tooltip
@alignment={{this.alignment}}
@delay={{this.delay}}
@flip={{this.flip}}
@offset={{this.offset}}
@side={{this.side}}
Expand Down Expand Up @@ -152,6 +156,13 @@ export default class extends Component {
@options={{array "" "start" "end"}}
@onInput={{fn this.update "alignment"}}
/>
<Args.Number
@name="delay"
@defaultValue={{300}}
@description="Amount of delay before showing the tooltip (in milliseconds)"
@value={{this.delay}}
@onInput={{fn this.update "delay"}}
/>
<Args.Bool
@name="flip"
@defaultValue={{false}}
Expand Down
119 changes: 66 additions & 53 deletions packages/ember-core/src/components/popover.gts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { concat, hash } from '@ember/helper';
import { arrow, computePosition, flip, offset, size } from '@floating-ui/dom';
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { restartableTask, timeout } from 'ember-concurrency';

import { classes } from '../helpers/classes.ts';
import onInsert from '../modifiers/on-insert.ts';
Expand Down Expand Up @@ -53,6 +54,7 @@ export interface PopoverSignature {
alignment?: Alignment;
arrow?: boolean;
controlElement?: HTMLElement;
delay?: number;
flip?: boolean;
fullWidth?: boolean;
offset?: string | number;
Expand Down Expand Up @@ -94,31 +96,18 @@ export default class Popover extends Component<PopoverSignature> {

id = `popover-${crypto.randomUUID()}`;

@tracked
isShown = false;

@tracked
adjustedSide: Direction = 'bottom';

get hasArrow() {
return this.args.arrow ?? true;
}

get offset() {
const defaultOffset = this.hasArrow ? getRemValue() / 2 : 0;
const { offset } = this.args;
const numOffset =
typeof offset === 'number' ? offset : parseFloat(offset ?? '0');
@tracked
isShown = false;

return numOffset + defaultOffset;
get control() {
return this.args.controlElement ?? this._control;
}

get placement() {
let placement: Placement | Side = SIDE_TRANSLATION[this.side];
if (this.args.alignment) {
placement += `-${this.args.alignment}`;
}
return placement as Placement;
get hasArrow() {
return this.args.arrow ?? true;
}

get middleware() {
Expand Down Expand Up @@ -147,41 +136,30 @@ export default class Popover extends Component<PopoverSignature> {
return middleware;
}

get side(): Direction {
return this.args.side ?? 'bottom';
}
get offset() {
const defaultOffset = this.hasArrow ? getRemValue() / 2 : 0;
const { offset } = this.args;
const numOffset =
typeof offset === 'number' ? offset : parseFloat(offset ?? '0');

get control() {
return this.args.controlElement ?? this._control;
return numOffset + defaultOffset;
}

setArrow = (popover: HTMLElement) => {
this.arrow = popover;
};

show = async (evtOrInput: Event | HTMLInputElement) => {
if (this.isShown) {
return;
get placement() {
let placement: Placement | Side = SIDE_TRANSLATION[this.side];
if (this.args.alignment) {
placement += `-${this.args.alignment}`;
}
return placement as Placement;
}

const { currentTarget } = evtOrInput as Event;
this.isShown = true;

await this.args.onShow?.();

if (evtOrInput instanceof HTMLInputElement) {
this._control = evtOrInput;
this.showPopover();
} else if (
evtOrInput instanceof Event &&
currentTarget instanceof HTMLElement
) {
this._control = currentTarget;
this.showPopover();
}
};
get side(): Direction {
return this.args.side ?? 'bottom';
}

hide = async () => {
this.triggerDisplay.cancelAll();

if (!this.isShown) {
return;
}
Expand All @@ -192,16 +170,22 @@ export default class Popover extends Component<PopoverSignature> {
this._control = null;
};

toggle = async (evt: Event) => {
const action = this.isShown ? this.hide : this.show;

await action(evt);
};

initPopover = (popover: HTMLElement) => {
this.popover = popover;
};

setArrow = (popover: HTMLElement) => {
this.arrow = popover;
};

show = async (evtOrInput: Event | HTMLInputElement) => {
if (this.isShown) {
return;
}

this.triggerDisplay.perform(evtOrInput);
}

showPopover = async () => {
if (!this.control || !this.popover) {
return;
Expand Down Expand Up @@ -246,6 +230,35 @@ export default class Popover extends Component<PopoverSignature> {
});
};

toggle = async (evt: Event) => {
const action = this.isShown ? this.hide : this.show;

await action(evt);
};

triggerDisplay = restartableTask(async (evtOrInput: Event | HTMLInputElement) => {
const { currentTarget } = evtOrInput as Event;

if (this.args.delay) {
await timeout(this.args.delay);
}

this.isShown = true;

await this.args.onShow?.();

if (evtOrInput instanceof HTMLInputElement) {
this._control = evtOrInput;
this.showPopover();
} else if (
evtOrInput instanceof Event &&
currentTarget instanceof HTMLElement
) {
this._control = currentTarget;
this.showPopover();
}
});

<template>
{{#let
(hash
Expand Down
13 changes: 10 additions & 3 deletions packages/ember-core/src/components/tooltip.gts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import Component from '@glimmer/component';
import Popover from './popover.gts';

import type { Direction } from './popover.gts';
import type { TOC } from '@ember/component/template-only';
import type { Alignment } from '@floating-ui/dom';
import type { ComponentLike } from '@glint/template';

Expand All @@ -13,6 +12,7 @@ export interface TooltipSignature {
Args: {
alignment?: Alignment;
controlElement?: HTMLElement;
delay?: number;
flip?: boolean;
offset?: string | number;
side?: Direction;
Expand Down Expand Up @@ -76,10 +76,16 @@ class TooltipTarget extends Component<TooltipTargetSignature> {
</template>
}

const Tooltip: TOC<TooltipSignature> = <template>
class Tooltip extends Component<TooltipSignature> {
get delay() {
return this.args.delay ?? 300;
}

<template>
<Popover
class="tooltip"
@alignment={{@alignment}}
@delay={{this.delay}}
@flip={{@flip}}
@offset={{@offset}}
@side={{@side}}
Expand All @@ -99,6 +105,7 @@ const Tooltip: TOC<TooltipSignature> = <template>
{{/if}}
</:content>
</Popover>
</template>;
</template>
}

export default Tooltip;

0 comments on commit a165766

Please sign in to comment.