sober
Version:
Sober is a library of UI components designed with reference to Material You.
156 lines (155 loc) • 5.76 kB
JavaScript
import { useElement, useProps } from './core/element.js';
import { mediaQueryList } from './core/utils/mediaQuery.js';
import { getStackingContext } from './core/utils/getStackingContext.js';
import { convertCSSDuration } from './core/utils/CSSUtils.js';
const name = 's-tooltip';
const props = useProps({
align: ['top', 'bottom', 'left', 'right'],
disabled: false
});
const style = /*css*/ `
:host{
display: inline-flex;
vertical-align: middle;
text-align: left;
}
.popup{
position: fixed;
display: none;
inset: 0;
margin: 0;
width: fit-content;
height: fit-content;
background: none;
border: none;
outline: none;
max-width: none;
max-height: none;
font-size: .875rem;
font-weight: 400;
padding: 6px 8px;
border-radius: 4px;
white-space: nowrap;
filter: opacity(.88);
background: var(--s-color-inverse-surface, ${"#2E3132" /* Theme.colorInverseSurface */});
color: var(--s-color-inverse-on-surface, ${"#EFF1F3" /* Theme.colorInverseOnSurface */});
}
::slotted(img){
display: block;
border-radius: 4px;
margin: 2px 0;
max-width: 280px;
}
`;
const template = /*html*/ `
<slot name="trigger" part="trigger"></slot>
<div class="popup" popover="manual" part="popup">
<slot></slot>
</div>
`;
export class Tooltip extends useElement({
style, template, props,
setup(shadowRoot) {
const trigger = shadowRoot.querySelector('slot[name=trigger]');
const popup = shadowRoot.querySelector('.popup');
const computedStyle = getComputedStyle(this);
let showed = false;
const getAnimateOptions = () => {
const easing = computedStyle.getPropertyValue('--s-motion-easing-standard') || "cubic-bezier(0.2, 0, 0, 1.0)" /* Theme.motionEasingStandard */;
const duration = computedStyle.getPropertyValue('--s-motion-duration-medium4') || "400ms" /* Theme.motionDurationMedium4 */;
return { easing: easing, duration: convertCSSDuration(duration) };
};
const show = () => {
if (!this.isConnected || this.disabled)
return;
popup.style.display = 'block';
showed = true;
if (popup.showPopover) {
popup.showPopover();
}
else {
const rect = getStackingContext(shadowRoot);
popup.style.marginLeft = `${-rect.left}px`;
popup.style.marginTop = `${-rect.top}px`;
popup.style.zIndex = '3';
}
const rect = this.getBoundingClientRect();
const gap = 4;
const cWidth = popup.offsetWidth;
const cHeight = popup.offsetHeight;
const position = { top: 0, left: 0 };
const calls = {
middle(align) {
position.left = rect.left - ((cWidth - rect.width) / 2);
const bottom = () => {
position.top = rect.top + rect.height + gap;
return position.top + cHeight > innerHeight;
};
const top = () => {
position.top = rect.top - cHeight - gap;
return position.top < 0;
};
if (position.left < 0)
position.left = rect.left;
if (position.left + cWidth > innerWidth)
position.left = rect.left + rect.width - cWidth;
if (align === 'top')
top() && bottom();
if (align === 'bottom')
bottom() && top();
},
left() {
position.left = rect.left - cWidth - gap;
position.top = rect.top - ((cHeight - rect.height) / 2);
return position.left < 0;
},
right() {
position.left = rect.left + rect.width + gap;
position.top = rect.top - ((cHeight - rect.height) / 2);
return position.left + cWidth > innerWidth;
}
};
switch (this.align) {
case 'bottom':
case 'top':
calls.middle(this.align);
break;
case 'left':
calls.left() && calls.right();
break;
case 'right':
calls.right() && calls.left();
break;
}
popup.style.top = `${position.top}px`;
popup.style.left = `${position.left}px`;
popup.animate({ opacity: [0, 1] }, getAnimateOptions());
};
let timer = 0;
const close = () => {
clearTimeout(timer);
showed = false;
const animation = popup.animate({ opacity: [1, 0] }, getAnimateOptions());
animation.finished.then(() => {
if (showed)
return;
popup.hidePopover && popup.hidePopover();
popup.style.removeProperty('display');
});
};
trigger.addEventListener('touchstart', () => {
if (mediaQueryList.pointerFine.matches)
return;
clearTimeout(timer);
timer = setTimeout(() => show(), 600);
}, { passive: true });
trigger.ontouchend = () => {
clearTimeout(timer);
close();
};
trigger.onmouseenter = () => mediaQueryList.pointerFine.matches && show();
trigger.onmouseleave = () => mediaQueryList.pointerFine.matches && close();
}
}) {
}
Tooltip.define(name);