feat(overlays): abstract arrow logic from tooltip into ArrowMixin
This commit is contained in:
parent
de0e0d52b5
commit
a9d6971c67
13 changed files with 384 additions and 141 deletions
6
.changeset/nine-actors-occur.md
Normal file
6
.changeset/nine-actors-occur.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
'@lion/overlays': minor
|
||||
'@lion/tooltip': minor
|
||||
---
|
||||
|
||||
Abstracted the tooltip arrow related logic to a mixin, so it can be used in other overlays. Also created some demos to show this.
|
||||
|
|
@ -111,7 +111,6 @@ class MyOverlayComponent extends LitElement {
|
|||
render() {
|
||||
return html`
|
||||
<slot name="invoker"></slot>
|
||||
<slot name="_overlay-shadow-outlet"></slot>
|
||||
<div id="overlay-content-node-wrapper">
|
||||
<slot name="content"></slot>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@
|
|||
|
||||
```js script
|
||||
import { html } from 'lit-html';
|
||||
import { render, LitElement } from '@lion/core';
|
||||
import {
|
||||
ArrowMixin,
|
||||
OverlayMixin,
|
||||
withBottomSheetConfig,
|
||||
withDropdownConfig,
|
||||
|
|
@ -94,8 +96,8 @@ or in your Web Component with `OverlayMixin`, make sure you override these metho
|
|||
- Handle the tearing down of those event listeners
|
||||
- Define a template which includes:
|
||||
- invoker slot for your user to provide the invoker node (the element that invokes the overlay content)
|
||||
- content slot for your user to provide the content that shows when the overlay is opened
|
||||
- \_overlay-shadow-outlet, this slot is currently necessary under the hood for acting as a wrapper element for placement purposes, but is not something your end user should be concerned with, unless they are extending your component.
|
||||
- content slot for your user to provide the content that shows when the overlay is opened,
|
||||
make sure to put it inside a div with id `overlay-content-node-wrapper` which is necessary for positioning logic to work properly.
|
||||
|
||||
```js
|
||||
_defineOverlayConfig() {
|
||||
|
|
@ -126,7 +128,6 @@ _teardownOpenCloseListeners() {
|
|||
render() {
|
||||
return html`
|
||||
<slot name="invoker"></slot>
|
||||
<slot name="_overlay-shadow-outlet"></slot>
|
||||
<div id="overlay-content-node-wrapper">
|
||||
<slot name="content"></slot>
|
||||
</div>
|
||||
|
|
@ -356,7 +357,7 @@ Under the hood, the `OverlayMixin` will instantiate an OverlayController with th
|
|||
|
||||
By default, there are only a few `OverlayMixin` methods you need to override to create a working Web Component using an overlay:
|
||||
|
||||
- `render`, the template needs to include a `<slot name="content">`, `<slot name="invoker">` and `<slot name="_overlay-shadow-outlet">`.
|
||||
- `render`, the template needs to include a `<slot name="invoker">` and `<slot name="content">` inside a div with id `overlay-content-node-wrapper` (for positioning).
|
||||
- `_defineOverlayConfig`, in this protected method, return an object that contains the default configuration for your Web Component's overlay. See configuration section of OverlayController.
|
||||
- `_setupOpenCloseListeners`, use this lifecycle hook to setup the open and close event listeners on your `_overlayInvokerNode`.
|
||||
- `_teardownOpenCloseListeners`, use this lifecycle hook to ensure that the listeners are removed when the OverlayController is tearing down. For example when the Web Component is disconnected from the DOM.
|
||||
|
|
@ -397,7 +398,6 @@ class MyOverlayWC extends OverlayMixin(LitElement) {
|
|||
render() {
|
||||
return html`
|
||||
<slot name="invoker"></slot>
|
||||
<slot name="_overlay-shadow-outlet"></slot>
|
||||
<div id="overlay-content-node-wrapper">
|
||||
<slot name="content"></slot>
|
||||
</div>
|
||||
|
|
@ -568,3 +568,57 @@ export const nestedOverlays = () => {
|
|||
`;
|
||||
};
|
||||
```
|
||||
|
||||
## Local overlay with an arrow
|
||||
|
||||
To add an arrow to the localOverlay you can add `ArrowMixin` to your component.
|
||||
And add the `arrowPopperConfig` to the `_defineOverlayConfig`.
|
||||
|
||||
```js preview-story
|
||||
export const LocalWithArrow = () => {
|
||||
class ArrowExample extends ArrowMixin(OverlayMixin(LitElement)) {
|
||||
// Alternatively, set `this.config = { popperConfig: { placement: 'bottom' } }` on connectedCallback
|
||||
_defineOverlayConfig() {
|
||||
return {
|
||||
...super._defineOverlayConfig(),
|
||||
popperConfig: {
|
||||
...super._defineOverlayConfig().popperConfig,
|
||||
placement: 'bottom',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.__toggle = this.__toggle.bind(this);
|
||||
}
|
||||
|
||||
__toggle() {
|
||||
this.opened = !this.opened;
|
||||
}
|
||||
|
||||
_setupOpenCloseListeners() {
|
||||
super._setupOpenCloseListeners();
|
||||
if (this._overlayInvokerNode) {
|
||||
this._overlayInvokerNode.addEventListener('click', this.__toggle);
|
||||
}
|
||||
}
|
||||
|
||||
_teardownOpenCloseListeners() {
|
||||
super._teardownOpenCloseListeners();
|
||||
if (this._overlayInvokerNode) {
|
||||
this._overlayInvokerNode.removeEventListener('click', this.__toggle);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!customElements.get('arrow-example')) {
|
||||
customElements.define('arrow-example', ArrowExample);
|
||||
}
|
||||
return html`
|
||||
<arrow-example>
|
||||
<button slot="invoker">Click me to open the overlay!</button>
|
||||
<div slot="content">This is a tooltip with an arrow<div>
|
||||
</arrow-example>
|
||||
`;
|
||||
};
|
||||
```
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@ class DemoOverlaySystem extends OverlayMixin(LitElement) {
|
|||
render() {
|
||||
return html`
|
||||
<slot name="invoker"></slot>
|
||||
<slot name="_overlay-shadow-outlet"></slot>
|
||||
<div id="overlay-content-node-wrapper">
|
||||
<slot name="content"></slot>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ export { overlays, setOverlays } from './src/overlays.js';
|
|||
export { OverlaysManager } from './src/OverlaysManager.js';
|
||||
export { OverlayController } from './src/OverlayController.js';
|
||||
export { OverlayMixin } from './src/OverlayMixin.js';
|
||||
export { ArrowMixin } from './src/ArrowMixin.js';
|
||||
|
||||
export { withBottomSheetConfig } from './src/configurations/withBottomSheetConfig.js';
|
||||
export { withModalDialogConfig } from './src/configurations/withModalDialogConfig.js';
|
||||
|
|
|
|||
162
packages/overlays/src/ArrowMixin.js
Normal file
162
packages/overlays/src/ArrowMixin.js
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
import { css, html, dedupeMixin } from '@lion/core';
|
||||
|
||||
/**
|
||||
* @typedef {import('../types/OverlayConfig').OverlayConfig} OverlayConfig
|
||||
* @typedef {import('../types/ArrowMixinTypes').ArrowMixin} ArrowMixin
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @type {ArrowMixin}
|
||||
*/
|
||||
export const ArrowMixinImplementation = superclass =>
|
||||
class ArrowMixin extends superclass {
|
||||
static get properties() {
|
||||
return {
|
||||
hasArrow: {
|
||||
type: Boolean,
|
||||
reflect: true,
|
||||
attribute: 'has-arrow',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
:host {
|
||||
--tooltip-arrow-width: 12px;
|
||||
--tooltip-arrow-height: 8px;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
display: none;
|
||||
position: absolute;
|
||||
width: var(--tooltip-arrow-width);
|
||||
height: var(--tooltip-arrow-height);
|
||||
}
|
||||
|
||||
:host([has-arrow]) .arrow {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.arrow svg {
|
||||
display: block;
|
||||
}
|
||||
|
||||
[x-placement^='bottom'] .arrow {
|
||||
top: calc(-1 * var(--tooltip-arrow-height));
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
[x-placement^='left'] .arrow {
|
||||
right: calc(
|
||||
-1 * (var(--tooltip-arrow-height) +
|
||||
(var(--tooltip-arrow-width) - var(--tooltip-arrow-height)) / 2)
|
||||
);
|
||||
transform: rotate(270deg);
|
||||
}
|
||||
|
||||
[x-placement^='right'] .arrow {
|
||||
left: calc(
|
||||
-1 * (var(--tooltip-arrow-height) +
|
||||
(var(--tooltip-arrow-width) - var(--tooltip-arrow-height)) / 2)
|
||||
);
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.hasArrow = true;
|
||||
this.__setupRepositionCompletePromise();
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<slot name="invoker"></slot>
|
||||
<div id="overlay-content-node-wrapper">
|
||||
<slot name="content"></slot>
|
||||
<div class="arrow" x-arrow>${this._arrowTemplate()}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
_arrowTemplate() {
|
||||
return html`
|
||||
<svg viewBox="0 0 12 8">
|
||||
<path d="M 0,0 h 12 L 6,8 z"></path>
|
||||
</svg>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @overridable method `_defineOverlay`
|
||||
* @desc Overrides arrow and keepTogether modifier to be enabled,
|
||||
* and adds onCreate and onUpdate hooks to sync from popper state
|
||||
* @returns {OverlayConfig}
|
||||
*/
|
||||
// eslint-disable-next-line
|
||||
_defineOverlayConfig() {
|
||||
if (!this.hasArrow) {
|
||||
return super._defineOverlayConfig();
|
||||
}
|
||||
return {
|
||||
...super._defineOverlayConfig(),
|
||||
popperConfig: {
|
||||
...super._defineOverlayConfig()?.popperConfig,
|
||||
placement: 'top',
|
||||
|
||||
modifiers: {
|
||||
...super._defineOverlayConfig()?.popperConfig?.modifiers,
|
||||
keepTogether: {
|
||||
...super._defineOverlayConfig()?.popperConfig?.modifiers?.keepTogether,
|
||||
enabled: true,
|
||||
},
|
||||
arrow: {
|
||||
...super._defineOverlayConfig()?.popperConfig?.modifiers?.arrow,
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
|
||||
/** @param {import("popper.js").default.Data} data */
|
||||
onCreate: data => {
|
||||
this.__syncFromPopperState(data);
|
||||
},
|
||||
/** @param {import("popper.js").default.Data} data */
|
||||
onUpdate: data => {
|
||||
this.__syncFromPopperState(data);
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
__setupRepositionCompletePromise() {
|
||||
this.repositionComplete = new Promise(resolve => {
|
||||
this.__repositionCompleteResolver = resolve;
|
||||
});
|
||||
}
|
||||
|
||||
get _arrowNode() {
|
||||
return /** @type {ShadowRoot} */ (this.shadowRoot).querySelector('[x-arrow]');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("popper.js").default.Data} data
|
||||
*/
|
||||
__syncFromPopperState(data) {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
this._arrowNode &&
|
||||
data.placement !== /** @type {Element & {placement:string}} */ (this._arrowNode).placement
|
||||
) {
|
||||
/** @type {function} */ (this.__repositionCompleteResolver)(data.placement);
|
||||
this.__setupRepositionCompletePromise();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const ArrowMixin = dedupeMixin(ArrowMixinImplementation);
|
||||
|
|
@ -895,7 +895,6 @@ export class OverlayController extends EventTargetShim {
|
|||
/** @type {HTMLElement} */
|
||||
(this.backdropNode).classList.add('local-overlays__backdrop');
|
||||
}
|
||||
this.backdropNode.slot = '_overlay-shadow-outlet';
|
||||
/** @type {HTMLElement} */
|
||||
(this.contentNode.parentNode).insertBefore(this.backdropNode, this.contentNode);
|
||||
break;
|
||||
|
|
|
|||
106
packages/overlays/test/ArrowMixin.test.js
Normal file
106
packages/overlays/test/ArrowMixin.test.js
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
import { expect, fixture } from '@open-wc/testing';
|
||||
import { LitElement, html } from '@lion/core';
|
||||
import { ArrowMixin, OverlayMixin } from '../index.js';
|
||||
|
||||
describe('ArrowMixin', () => {
|
||||
class ArrowTest extends ArrowMixin(OverlayMixin(LitElement)) {
|
||||
/**
|
||||
* @overridable method `_defineOverlay`
|
||||
* @desc Overrides arrow and keepTogether modifier to be enabled,
|
||||
* and adds onCreate and onUpdate hooks to sync from popper state
|
||||
* @returns {import('../types/OverlayConfig').OverlayConfig}
|
||||
*/
|
||||
_defineOverlayConfig() {
|
||||
return {
|
||||
...super._defineOverlayConfig(),
|
||||
placementMode: 'local',
|
||||
popperConfig: {
|
||||
...super._defineOverlayConfig().popperConfig,
|
||||
placement: 'bottom',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.__toggle = this.__toggle.bind(this);
|
||||
}
|
||||
|
||||
__toggle() {
|
||||
this.opened = !this.opened;
|
||||
}
|
||||
|
||||
_setupOpenCloseListeners() {
|
||||
super._setupOpenCloseListeners();
|
||||
if (this._overlayInvokerNode) {
|
||||
this._overlayInvokerNode.addEventListener('click', this.__toggle);
|
||||
}
|
||||
}
|
||||
|
||||
_teardownOpenCloseListeners() {
|
||||
super._teardownOpenCloseListeners();
|
||||
if (this._overlayInvokerNode) {
|
||||
this._overlayInvokerNode.removeEventListener('click', this.__toggle);
|
||||
}
|
||||
}
|
||||
}
|
||||
before(() => {
|
||||
customElements.define('arrow-test', ArrowTest);
|
||||
});
|
||||
|
||||
it('shows by default', async () => {
|
||||
const el = /** @type {ArrowTest} */ (await fixture(html`
|
||||
<arrow-test>
|
||||
<div slot="content">This is a tooltip</div>
|
||||
<button slot="invoker">Tooltip button</button>
|
||||
</arrow-test>
|
||||
`));
|
||||
expect(el.hasAttribute('has-arrow')).to.be.true;
|
||||
|
||||
const arrowNode = /** @type {Element} */ (el._arrowNode);
|
||||
expect(window.getComputedStyle(arrowNode).getPropertyValue('display')).to.equal('block');
|
||||
});
|
||||
|
||||
it('hides the arrow when has-arrow is false', async () => {
|
||||
const el = /** @type {ArrowTest} */ (await fixture(html`
|
||||
<arrow-test>
|
||||
<div slot="content">This is a tooltip</div>
|
||||
<button slot="invoker">Tooltip button</button>
|
||||
</arrow-test>
|
||||
`));
|
||||
el.hasArrow = false;
|
||||
await el.updateComplete;
|
||||
expect(el.hasAttribute('has-arrow')).to.be.false;
|
||||
const arrowNode = /** @type {Element} */ (el._arrowNode);
|
||||
expect(window.getComputedStyle(arrowNode).getPropertyValue('display')).to.equal('none');
|
||||
});
|
||||
|
||||
it('makes sure positioning of the arrow is correct', async () => {
|
||||
const el = /** @type {ArrowTest} */ (await fixture(html`
|
||||
<arrow-test
|
||||
.config="${/** @type {import('../types/OverlayConfig').OverlayConfig} */ ({
|
||||
popperConfig: {
|
||||
placement: 'right',
|
||||
},
|
||||
})}"
|
||||
style="position: relative; top: 10px;"
|
||||
>
|
||||
<div slot="content" style="height: 30px; background-color: red;">Hey there</div>
|
||||
<button slot="invoker" style="height: 30px;">Tooltip button</button>
|
||||
</arrow-test>
|
||||
`));
|
||||
|
||||
el.opened = true;
|
||||
|
||||
await el.repositionComplete;
|
||||
expect(
|
||||
getComputedStyle(/** @type {HTMLElement} */ (el._arrowNode)).getPropertyValue('left'),
|
||||
).to.equal(
|
||||
'-10px',
|
||||
`
|
||||
arrow height is 8px so this offset should be taken into account to align the arrow properly,
|
||||
as well as half the difference between width and height ((12 - 8) / 2 = 2)
|
||||
`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -8,7 +8,6 @@ const tagString = defineCE(
|
|||
render() {
|
||||
return html`
|
||||
<button slot="invoker">invoker button</button>
|
||||
<slot name="_overlay-shadow-outlet"></slot>
|
||||
<div id="overlay-content-node-wrapper">
|
||||
<div slot="content">content of the overlay</div>
|
||||
</div>
|
||||
|
|
|
|||
32
packages/overlays/types/ArrowMixinTypes.d.ts
vendored
Normal file
32
packages/overlays/types/ArrowMixinTypes.d.ts
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { Constructor } from '@open-wc/dedupe-mixin';
|
||||
import { LitElement, TemplateResult } from '@lion/core';
|
||||
import { CSSResultArray } from 'lit-element';
|
||||
import Data from 'popper.js';
|
||||
import { OverlayConfig } from '../types/OverlayConfig';
|
||||
|
||||
export declare class ArrowHost {
|
||||
static get properties(): {
|
||||
hasArrow: {
|
||||
type: BooleanConstructor;
|
||||
reflect: boolean;
|
||||
attribute: string;
|
||||
};
|
||||
};
|
||||
hasArrow: boolean;
|
||||
repositionComplete: Promise<void>;
|
||||
|
||||
static styles: CSSResultArray;
|
||||
|
||||
render(): TemplateResult;
|
||||
_arrowTemplate(): TemplateResult;
|
||||
_defineOverlayConfig(): OverlayConfig;
|
||||
__setupRepositionCompletePromise(): void;
|
||||
get _arrowNode(): Element | null;
|
||||
__syncFromPopperState(data: Data): void;
|
||||
}
|
||||
|
||||
export declare function ArrowImplementation<T extends Constructor<LitElement>>(
|
||||
superclass: T,
|
||||
): T & Constructor<ArrowHost> & ArrowHost;
|
||||
|
||||
export type ArrowMixin = typeof ArrowImplementation;
|
||||
|
|
@ -180,7 +180,7 @@ Modifier explanations:
|
|||
|
||||
By default, the arrow is disabled for our tooltip. Via the `has-arrow` property it can be enabled.
|
||||
|
||||
> As a Subclasser, you can decide to turn the arrow on by default if this fits your Design System
|
||||
> As a Subclasser, you can decide to turn the arrow on by default if this fits your Design System, by setting `this.hasArrow = true;` in the constructor.
|
||||
|
||||
```js preview-story
|
||||
export const arrow = () => html`
|
||||
|
|
|
|||
|
|
@ -1,21 +1,18 @@
|
|||
import { css, html, LitElement } from '@lion/core';
|
||||
import { OverlayMixin } from '@lion/overlays';
|
||||
import { css, LitElement } from '@lion/core';
|
||||
import { ArrowMixin, OverlayMixin } from '@lion/overlays';
|
||||
|
||||
/**
|
||||
* @typedef {import('@lion/overlays/types/OverlayConfig').OverlayConfig} OverlayConfig
|
||||
* @typedef {import('@lion/core').CSSResult} CSSResult
|
||||
* @typedef {import('lit-element').CSSResultArray} CSSResultArray
|
||||
*/
|
||||
|
||||
/**
|
||||
* @customElement lion-tooltip
|
||||
*/
|
||||
export class LionTooltip extends OverlayMixin(LitElement) {
|
||||
export class LionTooltip extends ArrowMixin(OverlayMixin(LitElement)) {
|
||||
static get properties() {
|
||||
return {
|
||||
hasArrow: {
|
||||
type: Boolean,
|
||||
reflect: true,
|
||||
attribute: 'has-arrow',
|
||||
},
|
||||
invokerRelation: {
|
||||
type: String,
|
||||
attribute: 'invoker-relation',
|
||||
|
|
@ -24,56 +21,18 @@ export class LionTooltip extends OverlayMixin(LitElement) {
|
|||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
:host {
|
||||
--tooltip-arrow-width: 12px;
|
||||
--tooltip-arrow-height: 8px;
|
||||
display: inline-block;
|
||||
}
|
||||
return [
|
||||
/** @type {CSSResult | CSSStyleSheet | CSSResultArray} */ (super.styles),
|
||||
css`
|
||||
:host {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
:host([hidden]) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
position: absolute;
|
||||
width: var(--tooltip-arrow-width);
|
||||
height: var(--tooltip-arrow-height);
|
||||
}
|
||||
|
||||
.arrow svg {
|
||||
display: block;
|
||||
}
|
||||
|
||||
[x-placement^='bottom'] .arrow {
|
||||
top: calc(-1 * var(--tooltip-arrow-height));
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
[x-placement^='left'] .arrow {
|
||||
right: calc(
|
||||
-1 * (var(--tooltip-arrow-height) +
|
||||
(var(--tooltip-arrow-width) - var(--tooltip-arrow-height)) / 2)
|
||||
);
|
||||
transform: rotate(270deg);
|
||||
}
|
||||
|
||||
[x-placement^='right'] .arrow {
|
||||
left: calc(
|
||||
-1 * (var(--tooltip-arrow-height) +
|
||||
(var(--tooltip-arrow-width) - var(--tooltip-arrow-height)) / 2)
|
||||
);
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.arrow {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:host([has-arrow]) .arrow {
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
:host([hidden]) {
|
||||
display: none;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
|
|
@ -91,85 +50,22 @@ export class LionTooltip extends OverlayMixin(LitElement) {
|
|||
this.invokerRelation = 'description';
|
||||
this._mouseActive = false;
|
||||
this._keyActive = false;
|
||||
this.__setupRepositionCompletePromise();
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<slot name="invoker"></slot>
|
||||
<slot name="_overlay-shadow-outlet"></slot>
|
||||
<div id="overlay-content-node-wrapper">
|
||||
<slot name="content"></slot>
|
||||
<div class="arrow" x-arrow>${this._arrowTemplate()}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
_arrowTemplate() {
|
||||
return html`
|
||||
<svg viewBox="0 0 12 8">
|
||||
<path d="M 0,0 h 12 L 6,8 z"></path>
|
||||
</svg>
|
||||
`;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
_defineOverlayConfig() {
|
||||
return /** @type {OverlayConfig} */ ({
|
||||
...super._defineOverlayConfig(),
|
||||
placementMode: 'local',
|
||||
elementToFocusAfterHide: undefined,
|
||||
hidesOnEsc: true,
|
||||
hidesOnOutsideEsc: true,
|
||||
popperConfig: {
|
||||
placement: 'top', // default
|
||||
modifiers: {
|
||||
keepTogether: {
|
||||
enabled: true,
|
||||
},
|
||||
arrow: {
|
||||
enabled: this.hasArrow,
|
||||
},
|
||||
},
|
||||
onCreate: data => {
|
||||
this.__syncFromPopperState(data);
|
||||
},
|
||||
onUpdate: data => {
|
||||
this.__syncFromPopperState(data);
|
||||
},
|
||||
},
|
||||
handlesAccessibility: true,
|
||||
isTooltip: true,
|
||||
invokerRelation: this.invokerRelation,
|
||||
});
|
||||
}
|
||||
|
||||
__setupRepositionCompletePromise() {
|
||||
this.repositionComplete = new Promise(resolve => {
|
||||
this.__repositionCompleteResolver = resolve;
|
||||
});
|
||||
}
|
||||
|
||||
get _arrowNode() {
|
||||
return /** @type {ShadowRoot} */ (this.shadowRoot).querySelector('[x-arrow]');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("popper.js").default.Data} data
|
||||
*/
|
||||
__syncFromPopperState(data) {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
this._arrowNode &&
|
||||
data.placement !== /** @type {Element & {placement:string}} */ (this._arrowNode).placement
|
||||
) {
|
||||
/** @type {function} */ (this.__repositionCompleteResolver)(data.placement);
|
||||
this.__setupRepositionCompletePromise();
|
||||
}
|
||||
}
|
||||
|
||||
_setupOpenCloseListeners() {
|
||||
super._setupOpenCloseListeners();
|
||||
this.__resetActive = this.__resetActive.bind(this);
|
||||
|
|
|
|||
|
|
@ -185,11 +185,6 @@ describe('lion-tooltip', () => {
|
|||
const initialPopperModifiers = el._overlayCtrl.config.popperConfig.modifiers;
|
||||
// @ts-expect-error allow protected props in tests
|
||||
expect(el._overlayCtrl.config.popperConfig.placement).to.equal('top');
|
||||
// TODO: this fails in CI, we need to investigate why in CI
|
||||
// the value of the transform is: translate3d(16px, -26px, 0px)'
|
||||
// expect(el.querySelector('[slot=_overlay-shadow-outlet]').style.transform).to.equal(
|
||||
// 'translate3d(15px, -26px, 0px)',
|
||||
// );
|
||||
|
||||
el.config = {
|
||||
popperConfig: {
|
||||
|
|
@ -205,11 +200,6 @@ describe('lion-tooltip', () => {
|
|||
expect(updatedPopperModifiers).to.deep.equal(initialPopperModifiers);
|
||||
// @ts-expect-error allow protected props in tests
|
||||
expect(el._overlayCtrl.config.popperConfig.placement).to.equal('bottom');
|
||||
// TODO: this fails in CI, we need to investigate why in CI
|
||||
// the value of the transform is: translate3d(16px, 26px, 0px)'
|
||||
// expect(el.querySelector('[slot=_overlay-shadow-outlet]').style.transform).to.equal(
|
||||
// 'translate3d(15px, 26px, 0px)',
|
||||
// );
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue