UNPKG

@lion/ui

Version:

A package of extendable web components

219 lines (193 loc) 6.25 kB
import { css, html } from 'lit'; import { dedupeMixin } from '@open-wc/dedupe-mixin'; import { OverlayMixin } from './OverlayMixin.js'; /** * @typedef {import('../types/OverlayConfig.js').OverlayConfig} OverlayConfig * @typedef {import('../types/ArrowMixinTypes.js').ArrowMixin} ArrowMixin * @typedef {import('@popperjs/core/lib/popper.js').Options} PopperOptions * @typedef {import('@popperjs/core/lib/enums.js').Placement} Placement * @typedef {import('lit').CSSResultArray} CSSResultArray */ /** * @type {ArrowMixin} * @param {import('@open-wc/dedupe-mixin').Constructor<import('lit').LitElement>} superclass */ export const ArrowMixinImplementation = superclass => // @ts-ignore https://github.com/microsoft/TypeScript/issues/36821#issuecomment-588375051 class ArrowMixin extends OverlayMixin(superclass) { static get properties() { return { hasArrow: { type: Boolean, reflect: true, attribute: 'has-arrow', }, }; } static get styles() { return [ .../** @type {CSSResultArray} */ (super.styles || []), css` :host { --tooltip-arrow-width: 12px; --tooltip-arrow-height: 8px; } .arrow svg { display: block; } .arrow { position: absolute; width: var(--tooltip-arrow-width); height: var(--tooltip-arrow-height); } .arrow__graphic { display: block; } [data-popper-placement^='top'] .arrow { bottom: calc(-1 * var(--tooltip-arrow-height)); } [data-popper-placement^='bottom'] .arrow { top: calc(-1 * var(--tooltip-arrow-height)); } [data-popper-placement^='bottom'] .arrow__graphic { transform: rotate(180deg); } [data-popper-placement^='left'] .arrow { right: calc( -1 * (var(--tooltip-arrow-height) + (var(--tooltip-arrow-width) - var(--tooltip-arrow-height)) / 2) ); } [data-popper-placement^='left'] .arrow__graphic { transform: rotate(270deg); } [data-popper-placement^='right'] .arrow { left: calc( -1 * (var(--tooltip-arrow-height) + (var(--tooltip-arrow-width) - var(--tooltip-arrow-height)) / 2) ); } [data-popper-placement^='right'] .arrow__graphic { transform: rotate(90deg); } :host(:not([has-arrow])) .arrow { display: none; } `, ]; } constructor() { super(); this.hasArrow = true; /** @private */ this.__setupRepositionCompletePromise(); } render() { return html` <slot name="invoker"></slot> <div id="overlay-content-node-wrapper"> <slot name="content"></slot> ${this._arrowNodeTemplate()} </div> `; } /** @protected */ _arrowNodeTemplate() { return html` <div class="arrow" aria-hidden="true" data-popper-arrow>${this._arrowTemplate()}</div> `; } /** @protected */ // eslint-disable-next-line class-methods-use-this _arrowTemplate() { return html` <svg viewBox="0 0 12 8" class="arrow__graphic"> <path d="M 0,0 h 12 L 6,8 z"></path> </svg> `; } /** * Overrides arrow and keepTogether modifier to be enabled, * and adds onCreate and onUpdate hooks to sync from popper state * @configure OverlayMixin * @returns {OverlayConfig} * @protected */ // eslint-disable-next-line _defineOverlayConfig() { const superConfig = super._defineOverlayConfig() || {}; if (!this.hasArrow) { return superConfig; } return { ...superConfig, popperConfig: { ...this._getPopperArrowConfig( /** @type {Partial<PopperOptions>} */ (superConfig.popperConfig), ), }, }; } /** * @param {Partial<PopperOptions>} popperConfigToExtendFrom * @returns {Partial<PopperOptions>} * @protected */ _getPopperArrowConfig(popperConfigToExtendFrom) { /** @type {Partial<PopperOptions> & { afterWrite: (arg0: Partial<import('@popperjs/core/lib/popper.js').State>) => void }} */ const popperCfg = { ...(popperConfigToExtendFrom || {}), placement: /** @type {Placement} */ ('top'), modifiers: [ { name: 'arrow', enabled: true, options: { padding: 8, // 8px from the edges of the popper }, }, { name: 'offset', enabled: true, options: { offset: [0, 8] }, }, ...((popperConfigToExtendFrom && popperConfigToExtendFrom.modifiers) || []), ], /** @param {Partial<import('@popperjs/core/lib/popper.js').State>} data */ onFirstUpdate: data => { this.__syncFromPopperState(data); }, /** @param {Partial<import('@popperjs/core/lib/popper.js').State>} data */ afterWrite: data => { this.__syncFromPopperState(data); }, }; return popperCfg; } /** @private */ __setupRepositionCompletePromise() { this.repositionComplete = new Promise(resolve => { this.__repositionCompleteResolver = resolve; }); } get _arrowNode() { return /** @type {ShadowRoot} */ (this.shadowRoot).querySelector('[data-popper-arrow]'); } /** * @param {Partial<import('@popperjs/core/lib/popper.js').State>} data * @private */ __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);