@kit-data-manager/pid-component
Version:
The PID-Component is a web component that can be used to evaluate and display FAIR Digital Objects, PIDs, ORCiDs, and possibly other identifiers in a user-friendly way. It is easily extensible to support other identifier types.
326 lines (325 loc) • 14.3 kB
JavaScript
/*!
*
* Copyright 2024 Karlsruhe Institute of Technology.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { h, Host } from "@stencil/core";
export class PidTooltip {
constructor() {
this.isVisible = false;
this.calculatedPosition = 'top';
this.needsRowExpansion = false;
this.tooltipId = `tooltip-${Math.random().toString(36).substring(2, 11)}`;
this.showTooltip = () => {
this.isVisible = true;
setTimeout(() => this.calculateOptimalPosition(), 10);
};
this.hideTooltip = () => {
this.isVisible = false;
this.restoreRowHeight();
};
this.toggleTooltip = (event) => {
event.preventDefault();
event.stopPropagation();
if (this.isVisible) {
this.hideTooltip();
}
else {
this.showTooltip();
}
};
this.handleButtonKeyDown = (event) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
this.toggleTooltip(event);
}
};
this.position = 'top';
this.maxWidth = '250px';
this.maxHeight = '150px';
this.fitContent = true;
}
handleKeyDown(event) {
var _a;
if (event.key === 'Escape' && this.isVisible) {
this.hideTooltip();
(_a = this.buttonRef) === null || _a === void 0 ? void 0 : _a.focus();
event.preventDefault();
event.stopPropagation();
}
}
componentDidLoad() {
this.calculatedPosition = this.position === 'bottom' ? 'bottom' : 'top';
this.tableRow = this.el.closest('tr');
}
calculateOptimalPosition() {
if (!this.tooltipRef || !this.isVisible)
return;
const hostRect = this.el.getBoundingClientRect();
const table = this.el.closest('table');
const tableContainer = (table === null || table === void 0 ? void 0 : table.closest('.overflow-auto')) || (table === null || table === void 0 ? void 0 : table.parentElement);
if (!tableContainer) {
this.calculatedPosition = this.position === 'bottom' ? 'bottom' : 'top';
return;
}
const containerRect = tableContainer.getBoundingClientRect();
const spaceAbove = hostRect.top - containerRect.top;
const spaceBelow = containerRect.bottom - hostRect.bottom;
const tooltipHeight = this.estimateTooltipHeight(this.text);
const requiredSpace = tooltipHeight + 20;
let useBottom;
let needsExpansion = false;
if (this.position === 'top' && spaceAbove >= requiredSpace) {
useBottom = false;
}
else if (this.position === 'bottom' && spaceBelow >= requiredSpace) {
useBottom = true;
}
else if (spaceAbove >= spaceBelow && spaceAbove >= requiredSpace) {
useBottom = false;
}
else if (spaceBelow >= requiredSpace) {
useBottom = true;
}
else {
useBottom = true;
needsExpansion = true;
}
this.calculatedPosition = useBottom ? 'bottom' : 'top';
this.needsRowExpansion = needsExpansion;
if (needsExpansion) {
this.expandRowForTooltip(tooltipHeight);
}
}
expandRowForTooltip(tooltipHeight) {
if (!this.tableRow)
return;
this.originalRowHeight = this.tableRow.style.height || 'auto';
const currentRowHeight = this.tableRow.offsetHeight;
const requiredAdditionalHeight = tooltipHeight + 40;
const newRowHeight = Math.max(currentRowHeight, requiredAdditionalHeight);
this.tableRow.style.transition = 'height 0.2s ease-in-out';
this.tableRow.style.height = `${newRowHeight}px`;
this.tooltipExpansionChange.emit({
expand: true,
requiredHeight: newRowHeight,
});
}
restoreRowHeight() {
if (!this.tableRow || !this.needsRowExpansion)
return;
this.tableRow.style.height = this.originalRowHeight || 'auto';
setTimeout(() => {
if (this.tableRow) {
this.tableRow.style.transition = '';
}
}, 200);
this.needsRowExpansion = false;
this.tooltipExpansionChange.emit({
expand: false,
requiredHeight: 0,
});
}
estimateTooltipHeight(content) {
const tempDiv = document.createElement('div');
tempDiv.setAttribute('aria-hidden', 'true');
tempDiv.setAttribute('tabindex', '-1');
tempDiv.setAttribute('role', 'presentation');
tempDiv.style.cssText = `
position: absolute;
visibility: hidden;
white-space: normal;
word-wrap: break-word;
max-width: ${this.maxWidth};
padding: 12px;
font-size: 12px;
line-height: 1.4;
border: 1px solid transparent;
pointer-events: none;
`;
tempDiv.textContent = content;
document.body.appendChild(tempDiv);
const height = tempDiv.offsetHeight;
document.body.removeChild(tempDiv);
return height;
}
render() {
const hasTooltipText = this.text && this.text.trim().length > 0;
const buttonLabel = `${this.isVisible ? 'Hide' : 'Show'} additional information`;
const parentComponent = this.el.closest('pid-component');
const isDarkMode = parentComponent === null || parentComponent === void 0 ? void 0 : parentComponent.classList.contains('bg-gray-800');
return (h(Host, { key: 'e5295dae6fd443a5e80d135856c15ffc9059c2a9', class: "relative inline-block w-full", onMouseEnter: this.showTooltip, onMouseLeave: this.hideTooltip }, this.isVisible && (h("span", { key: '0588790c20ca97b641ec56111699299e0482d789', class: "sr-only", "aria-live": "assertive" }, "Information tooltip opened")), h("div", { key: '940287de8471434bd5af374f1415c21708f5d463', class: "flex items-center justify-between" }, h("slot", { key: '9dbc804a3e610f7d9da8cba2e4d8e665d949dbf1', name: "trigger" }), hasTooltipText && (h("button", { key: '0c3a92bb782d9103459ffcee4dff8cfa9c33a6e5', ref: el => (this.buttonRef = el), type: "button", class: `flex items-center rounded-full p-0.5 transition-colors duration-200 ${isDarkMode ? 'text-gray-300 hover:bg-gray-700' : 'text-gray-600 hover:bg-gray-100'} focus:ring-2 focus:ring-blue-500 focus:ring-offset-1 focus:outline-none`, "aria-label": buttonLabel, "aria-expanded": this.isVisible ? 'true' : 'false', "aria-controls": this.tooltipId, "aria-describedby": this.isVisible ? this.tooltipId : undefined, onClick: this.toggleTooltip, onKeyDown: this.handleButtonKeyDown, onFocus: this.showTooltip, onBlur: this.hideTooltip, tabIndex: 0, title: buttonLabel }, h("svg", { key: '74331fad1f2b9fa53fffe60039abc8ac82059b27', "aria-hidden": "true", xmlns: "http://www.w3.org/2000/svg", class: "icon icon-tabler icon-tabler-info-circle min-h-[1rem] min-w-[1rem] flex-shrink-0", width: "20", height: "20", viewBox: "0 0 24 24", "stroke-width": "1.5", stroke: "currentColor", fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round", role: "img" }, h("title", { key: '428499e9aac7b8e13db66edc0c86a367e3ec2d33' }, "Information icon"), h("desc", { key: '887352ca3b10a544a85ba909fabc05eb21d49f81' }, "An icon indicating additional information is available"), h("path", { key: '197e29a49e404230c066b5d5ece4ec58b122696b', stroke: "none", d: "M0 0h24v24H0z" }), h("circle", { key: '537678517fc043d41c78dc0bf97e41fcc62b8503', cx: "12", cy: "12", r: "9" }), h("line", { key: '2878419c9d3a7c525ed9931400159efe664ecb00', x1: "12", y1: "8", x2: "12.01", y2: "8" }), h("polyline", { key: 'c5ac63bf54a34d688608118f9689c8fcaa7ec70b', points: "11 12 12 12 12 16 13 16" }))))), hasTooltipText && (h("div", { key: '6c2b225b019329683ed3254a2fd6237e20b2f4e4', ref: el => (this.tooltipRef = el), id: this.tooltipId, role: "tooltip", class: `${this.isVisible ? 'block' : 'hidden'} absolute z-50 ${this.getPositionClasses(this.calculatedPosition)} w-full rounded border ${isDarkMode ? 'border-gray-600 bg-gray-700 text-gray-200' : 'border-gray-300 bg-white text-gray-700'} p-3 text-xs whitespace-normal shadow-lg transition-opacity duration-200 ease-in-out`, style: this.getTooltipStyles(), "aria-live": "polite" }, h("p", { key: 'd7762cb0aa9405e3836839a39787545c86477df8', class: "m-0 p-0" }, this.text)))));
}
getPositionClasses(position) {
switch (position) {
case 'top':
return 'bottom-full left-0 mb-2';
case 'bottom':
return 'top-full left-0 mt-2';
default:
return 'top-full left-0 mt-2';
}
}
getTooltipStyles() {
const styles = {
maxWidth: this.maxWidth,
};
if (this.needsRowExpansion || !this.fitContent) {
styles.maxHeight = this.maxHeight;
styles.overflowY = 'auto';
}
return styles;
}
static get is() { return "pid-tooltip"; }
static get properties() {
return {
"text": {
"type": "string",
"attribute": "text",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": true,
"optional": false,
"docs": {
"tags": [],
"text": "The text to display in the tooltip"
},
"getter": false,
"setter": false,
"reflect": false
},
"position": {
"type": "string",
"attribute": "position",
"mutable": false,
"complexType": {
"original": "'top' | 'bottom'",
"resolved": "\"bottom\" | \"top\"",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "The preferred position of the tooltip (top or bottom)"
},
"getter": false,
"setter": false,
"reflect": false,
"defaultValue": "'top'"
},
"maxWidth": {
"type": "string",
"attribute": "max-width",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "The maximum width of the tooltip"
},
"getter": false,
"setter": false,
"reflect": false,
"defaultValue": "'250px'"
},
"maxHeight": {
"type": "string",
"attribute": "max-height",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "The maximum height of the tooltip"
},
"getter": false,
"setter": false,
"reflect": false,
"defaultValue": "'150px'"
},
"fitContent": {
"type": "boolean",
"attribute": "fit-content",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Whether the tooltip should fit its content height exactly"
},
"getter": false,
"setter": false,
"reflect": false,
"defaultValue": "true"
}
};
}
static get states() {
return {
"isVisible": {},
"calculatedPosition": {},
"needsRowExpansion": {}
};
}
static get events() {
return [{
"method": "tooltipExpansionChange",
"name": "tooltipExpansionChange",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [],
"text": "Event emitted when tooltip requires row expansion"
},
"complexType": {
"original": "{ expand: boolean; requiredHeight: number }",
"resolved": "{ expand: boolean; requiredHeight: number; }",
"references": {}
}
}];
}
static get elementRef() { return "el"; }
static get listeners() {
return [{
"name": "keydown",
"method": "handleKeyDown",
"target": undefined,
"capture": false,
"passive": false
}];
}
}
//# sourceMappingURL=pid-tooltip.js.map