Skip to content

Commit fe73eed

Browse files
authored
feat: implement basic popover logic, move mixins from tooltip (#7409)
1 parent d247e95 commit fe73eed

19 files changed

+920
-176
lines changed

dev/popover.html

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>Popover</title>
8+
<script type="module" src="./common.js"></script>
9+
10+
<script type="module">
11+
import '@vaadin/button';
12+
import '@vaadin/horizontal-layout';
13+
import '@vaadin/popover';
14+
import '@vaadin/text-field';
15+
</script>
16+
</head>
17+
18+
<body>
19+
<vaadin-button id="button">Discount</vaadin-button>
20+
21+
<vaadin-popover for="button" position="bottom-start"></vaadin-popover>
22+
23+
<script type="module">
24+
const popover = document.querySelector('vaadin-popover');
25+
26+
popover.renderer = (root) => {
27+
if (root.firstChild) {
28+
return;
29+
}
30+
31+
const layout = document.createElement('vaadin-horizontal-layout');
32+
layout.setAttribute('theme', 'spacing-s');
33+
layout.style.alignItems = 'baseline';
34+
35+
const field = document.createElement('vaadin-text-field');
36+
field.label = 'Discount code';
37+
38+
const button = document.createElement('vaadin-button');
39+
button.textContent = 'Apply';
40+
41+
layout.append(field, button);
42+
43+
root.append(layout);
44+
};
45+
</script>
46+
</body>
47+
</html>

packages/popover/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,16 @@
3636
"dependencies": {
3737
"@open-wc/dedupe-mixin": "^1.3.0",
3838
"@vaadin/component-base": "24.5.0-alpha0",
39+
"@vaadin/overlay": "24.5.0-alpha0",
3940
"@vaadin/vaadin-lumo-styles": "24.5.0-alpha0",
4041
"@vaadin/vaadin-material-styles": "24.5.0-alpha0",
4142
"@vaadin/vaadin-themable-mixin": "24.5.0-alpha0",
4243
"lit": "^3.0.0"
4344
},
4445
"devDependencies": {
4546
"@esm-bundle/chai": "^4.3.4",
46-
"@vaadin/testing-helpers": "^0.6.0"
47+
"@vaadin/testing-helpers": "^0.6.0",
48+
"sinon": "^13.0.2"
4749
},
4850
"web-types": [
4951
"web-types.json",

packages/tooltip/src/vaadin-tooltip-overlay-mixin.js renamed to packages/popover/src/vaadin-popover-overlay-mixin.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ import { OverlayMixin } from '@vaadin/overlay/src/vaadin-overlay-mixin.js';
77
import { PositionMixin } from '@vaadin/overlay/src/vaadin-overlay-position-mixin.js';
88

99
/**
10-
* A mixin providing common tooltip overlay functionality.
10+
* A mixin providing common popover overlay functionality.
1111
*
1212
* @polymerMixin
1313
* @mixes PositionMixin
1414
* @mixes OverlayMixin
1515
*/
16-
export const TooltipOverlayMixin = (superClass) =>
17-
class TooltipOverlayMixinClass extends PositionMixin(OverlayMixin(superClass)) {
16+
export const PopoverOverlayMixin = (superClass) =>
17+
class PopoverOverlayMixinClass extends PositionMixin(OverlayMixin(superClass)) {
1818
static get properties() {
1919
return {
2020
position: {
@@ -30,13 +30,13 @@ export const TooltipOverlayMixin = (superClass) =>
3030
* @return {string}
3131
*/
3232
get _tagNamePrefix() {
33-
return 'vaadin-tooltip';
33+
return 'vaadin-popover';
3434
}
3535

3636
requestContentUpdate() {
3737
super.requestContentUpdate();
3838

39-
// Copy custom properties from the tooltip
39+
// Copy custom properties from the owner
4040
if (this.positionTarget && this.owner) {
4141
const style = getComputedStyle(this.owner);
4242
['top', 'bottom', 'start', 'end'].forEach((prop) => {
@@ -59,7 +59,7 @@ export const TooltipOverlayMixin = (superClass) =>
5959
return;
6060
}
6161

62-
// Center the tooltip overlay horizontally
62+
// Center the overlay horizontally
6363
if (this.position === 'bottom' || this.position === 'top') {
6464
const targetRect = this.positionTarget.getBoundingClientRect();
6565
const overlayRect = this.$.overlay.getBoundingClientRect();
@@ -81,7 +81,7 @@ export const TooltipOverlayMixin = (superClass) =>
8181
}
8282
}
8383

84-
// Center the tooltip overlay vertically
84+
// Center the overlay vertically
8585
if (this.position === 'start' || this.position === 'end') {
8686
const targetRect = this.positionTarget.getBoundingClientRect();
8787
const overlayRect = this.$.overlay.getBoundingClientRect();
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/**
2+
* @license
3+
* Copyright (c) 2024 Vaadin Ltd.
4+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5+
*/
6+
import { css, html, LitElement } from 'lit';
7+
import { defineCustomElement } from '@vaadin/component-base/src/define.js';
8+
import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js';
9+
import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js';
10+
import { overlayStyles } from '@vaadin/overlay/src/vaadin-overlay-styles.js';
11+
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
12+
import { PopoverOverlayMixin } from './vaadin-popover-overlay-mixin.js';
13+
14+
/**
15+
* An element used internally by `<vaadin-popover>`. Not intended to be used separately.
16+
*
17+
* @customElement
18+
* @extends HTMLElement
19+
* @mixes DirMixin
20+
* @mixes PopoverOverlayMixin
21+
* @mixes ThemableMixin
22+
* @private
23+
*/
24+
class PopoverOverlay extends PopoverOverlayMixin(DirMixin(ThemableMixin(PolylitMixin(LitElement)))) {
25+
static get is() {
26+
return 'vaadin-popover-overlay';
27+
}
28+
29+
static get styles() {
30+
return [
31+
overlayStyles,
32+
css`
33+
:host([position^='top'][top-aligned]) [part='overlay'],
34+
:host([position^='bottom'][top-aligned]) [part='overlay'] {
35+
margin-top: var(--vaadin-popover-offset-top, 0);
36+
}
37+
38+
:host([position^='top'][bottom-aligned]) [part='overlay'],
39+
:host([position^='bottom'][bottom-aligned]) [part='overlay'] {
40+
margin-bottom: var(--vaadin-popover-offset-bottom, 0);
41+
}
42+
43+
:host([position^='start'][start-aligned]) [part='overlay'],
44+
:host([position^='end'][start-aligned]) [part='overlay'] {
45+
margin-inline-start: var(--vaadin-popover-offset-start, 0);
46+
}
47+
48+
:host([position^='start'][end-aligned]) [part='overlay'],
49+
:host([position^='end'][end-aligned]) [part='overlay'] {
50+
margin-inline-end: var(--vaadin-popover-offset-end, 0);
51+
}
52+
`,
53+
];
54+
}
55+
56+
/** @protected */
57+
render() {
58+
return html`
59+
<div id="backdrop" part="backdrop" hidden ?hidden="${!this.withBackdrop}"></div>
60+
<div part="overlay" id="overlay">
61+
<div part="content" id="content"><slot></slot></div>
62+
</div>
63+
`;
64+
}
65+
}
66+
67+
defineCustomElement(PopoverOverlay);
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/**
2+
* @license
3+
* Copyright (c) 2024 Vaadin Ltd.
4+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5+
*/
6+
import type { Constructor } from '@open-wc/dedupe-mixin';
7+
8+
export type PopoverPosition =
9+
| 'bottom-end'
10+
| 'bottom-start'
11+
| 'bottom'
12+
| 'end-bottom'
13+
| 'end-top'
14+
| 'end'
15+
| 'start-bottom'
16+
| 'start-top'
17+
| 'start'
18+
| 'top-end'
19+
| 'top-start'
20+
| 'top';
21+
22+
/**
23+
* A mixin providing popover position functionality.
24+
*/
25+
export declare function PopoverPositionMixin<T extends Constructor<HTMLElement>>(
26+
base: T,
27+
): Constructor<PopoverPositionMixinClass> & T;
28+
29+
export declare class PopoverPositionMixinClass {
30+
/**
31+
* Position of the overlay with respect to the target.
32+
* Supported values: `top-start`, `top`, `top-end`,
33+
* `bottom-start`, `bottom`, `bottom-end`, `start-top`,
34+
* `start`, `start-bottom`, `end-top`, `end`, `end-bottom`.
35+
*/
36+
position: PopoverPosition;
37+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/**
2+
* @license
3+
* Copyright (c) 2024 Vaadin Ltd.
4+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5+
*/
6+
7+
/**
8+
* A mixin providing popover position functionality.
9+
*
10+
* @polymerMixin
11+
*/
12+
export const PopoverPositionMixin = (superClass) =>
13+
class PopoverPositionMixinClass extends superClass {
14+
static get properties() {
15+
return {
16+
/**
17+
* Position of the overlay with respect to the target.
18+
* Supported values: `top-start`, `top`, `top-end`,
19+
* `bottom-start`, `bottom`, `bottom-end`, `start-top`,
20+
* `start`, `start-bottom`, `end-top`, `end`, `end-bottom`.
21+
*/
22+
position: {
23+
type: String,
24+
},
25+
26+
/**
27+
* Default value used when `position` property is not set.
28+
* @protected
29+
*/
30+
_position: {
31+
type: String,
32+
value: 'bottom',
33+
},
34+
35+
/** @private */
36+
__effectivePosition: {
37+
type: String,
38+
computed: '__computePosition(position, _position)',
39+
},
40+
};
41+
}
42+
43+
/** @protected */
44+
__computeHorizontalAlign(position) {
45+
return ['top-end', 'bottom-end', 'start-top', 'start', 'start-bottom'].includes(position) ? 'end' : 'start';
46+
}
47+
48+
/** @protected */
49+
__computeNoHorizontalOverlap(position) {
50+
return ['start-top', 'start', 'start-bottom', 'end-top', 'end', 'end-bottom'].includes(position);
51+
}
52+
53+
/** @protected */
54+
__computeNoVerticalOverlap(position) {
55+
return ['top-start', 'top-end', 'top', 'bottom-start', 'bottom', 'bottom-end'].includes(position);
56+
}
57+
58+
/** @protected */
59+
__computeVerticalAlign(position) {
60+
return ['top-start', 'top-end', 'top', 'start-bottom', 'end-bottom'].includes(position) ? 'bottom' : 'top';
61+
}
62+
63+
/** @private */
64+
__computePosition(position, defaultPosition) {
65+
return position || defaultPosition;
66+
}
67+
};
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* @license
3+
* Copyright (c) 2024 Vaadin Ltd.
4+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5+
*/
6+
import type { Constructor } from '@open-wc/dedupe-mixin';
7+
8+
/**
9+
* A mixin providing popover target functionality.
10+
*/
11+
export declare function PopoverTargetMixin<T extends Constructor<HTMLElement>>(
12+
base: T,
13+
): Constructor<PopoverTargetMixinClass> & T;
14+
15+
export declare class PopoverTargetMixinClass {
16+
/**
17+
* The id of the element to be used as `target` value.
18+
* The element should be in the DOM by the time when
19+
* the attribute is set, otherwise a warning is shown.
20+
*/
21+
for: string | undefined;
22+
23+
/**
24+
* Reference to the DOM element used both to trigger the overlay
25+
* by user interaction and to visually position it on the screen.
26+
*
27+
* Defaults to an element referenced with `for` attribute, in
28+
* which case it must be located in the same shadow scope.
29+
*/
30+
target: HTMLElement | undefined;
31+
32+
protected _addTargetListeners(target: HTMLElement): void;
33+
34+
protected _removeTargetListeners(target: HTMLElement): void;
35+
}

0 commit comments

Comments
 (0)