@esri/calcite-components
Version:
Web Components for Esri's Calcite Design System.
471 lines (470 loc) • 14.9 kB
JavaScript
/*!
* All material copyright ESRI, All Rights Reserved, unless otherwise specified.
* See https://github.com/Esri/calcite-components/blob/master/LICENSE.md for details.
* v1.5.0-next.4
*/
import { h, Host } from "@stencil/core";
import { toAriaBoolean } from "../../utils/dom";
import { connectFloatingUI, defaultOffsetDistance, disconnectFloatingUI, FloatingCSS, reposition } from "../../utils/floating-ui";
import { guid } from "../../utils/guid";
import { connectOpenCloseComponent, disconnectOpenCloseComponent } from "../../utils/openCloseComponent";
import { ARIA_DESCRIBED_BY, CSS } from "./resources";
import TooltipManager from "./TooltipManager";
import { getEffectiveReferenceElement } from "./utils";
import { FloatingArrow } from "../functional/FloatingArrow";
const manager = new TooltipManager();
/**
* @slot - A slot for adding text.
*/
export class Tooltip {
constructor() {
this.guid = `calcite-tooltip-${guid()}`;
this.hasLoaded = false;
this.openTransitionProp = "opacity";
this.setTransitionEl = (el) => {
this.transitionEl = el;
connectOpenCloseComponent(this);
};
this.setUpReferenceElement = (warn = true) => {
this.removeReferences();
this.effectiveReferenceElement = getEffectiveReferenceElement(this.el);
connectFloatingUI(this, this.effectiveReferenceElement, this.el);
const { el, referenceElement, effectiveReferenceElement } = this;
if (warn && referenceElement && !effectiveReferenceElement) {
console.warn(`${el.tagName}: reference-element id "${referenceElement}" was not found.`, {
el
});
}
this.addReferences();
};
this.getId = () => {
return this.el.id || this.guid;
};
this.addReferences = () => {
const { effectiveReferenceElement } = this;
if (!effectiveReferenceElement) {
return;
}
const id = this.getId();
if ("setAttribute" in effectiveReferenceElement) {
effectiveReferenceElement.setAttribute(ARIA_DESCRIBED_BY, id);
}
manager.registerElement(effectiveReferenceElement, this.el);
};
this.removeReferences = () => {
const { effectiveReferenceElement } = this;
if (!effectiveReferenceElement) {
return;
}
if ("removeAttribute" in effectiveReferenceElement) {
effectiveReferenceElement.removeAttribute(ARIA_DESCRIBED_BY);
}
manager.unregisterElement(effectiveReferenceElement);
};
this.closeOnClick = false;
this.label = undefined;
this.offsetDistance = defaultOffsetDistance;
this.offsetSkidding = 0;
this.open = false;
this.overlayPositioning = "absolute";
this.placement = "auto";
this.referenceElement = undefined;
this.effectiveReferenceElement = undefined;
this.floatingLayout = "vertical";
}
offsetDistanceOffsetHandler() {
this.reposition(true);
}
offsetSkiddingHandler() {
this.reposition(true);
}
openHandler(value) {
if (value) {
this.reposition(true);
}
}
overlayPositioningHandler() {
this.reposition(true);
}
placementHandler() {
this.reposition(true);
}
referenceElementHandler() {
this.setUpReferenceElement();
}
// --------------------------------------------------------------------------
//
// Lifecycle
//
// --------------------------------------------------------------------------
connectedCallback() {
connectOpenCloseComponent(this);
this.setUpReferenceElement(this.hasLoaded);
}
componentDidLoad() {
if (this.referenceElement && !this.effectiveReferenceElement) {
this.setUpReferenceElement();
}
this.reposition(true);
this.hasLoaded = true;
}
disconnectedCallback() {
this.removeReferences();
disconnectFloatingUI(this, this.effectiveReferenceElement, this.el);
disconnectOpenCloseComponent(this);
}
// --------------------------------------------------------------------------
//
// Public Methods
//
// --------------------------------------------------------------------------
/**
* Updates the position of the component.
*
* @param delayed
*/
async reposition(delayed = false) {
const { el, effectiveReferenceElement, placement, overlayPositioning, offsetDistance, offsetSkidding, arrowEl } = this;
return reposition(this, {
floatingEl: el,
referenceEl: effectiveReferenceElement,
overlayPositioning,
placement,
offsetDistance,
offsetSkidding,
arrowEl,
type: "tooltip"
}, delayed);
}
// --------------------------------------------------------------------------
//
// Private Methods
//
// --------------------------------------------------------------------------
onBeforeOpen() {
this.calciteTooltipBeforeOpen.emit();
}
onOpen() {
this.calciteTooltipOpen.emit();
}
onBeforeClose() {
this.calciteTooltipBeforeClose.emit();
}
onClose() {
this.calciteTooltipClose.emit();
}
// --------------------------------------------------------------------------
//
// Render Methods
//
// --------------------------------------------------------------------------
render() {
const { effectiveReferenceElement, label, open, floatingLayout } = this;
const displayed = effectiveReferenceElement && open;
const hidden = !displayed;
return (h(Host, { "aria-hidden": toAriaBoolean(hidden), "aria-label": label, "aria-live": "polite", "calcite-hydrated-hidden": hidden, id: this.getId(), role: "tooltip" }, h("div", { class: {
[FloatingCSS.animation]: true,
[FloatingCSS.animationActive]: displayed
},
// eslint-disable-next-line react/jsx-sort-props
ref: this.setTransitionEl }, h(FloatingArrow, { floatingLayout: floatingLayout,
// eslint-disable-next-line react/jsx-sort-props
ref: (arrowEl) => (this.arrowEl = arrowEl) }), h("div", { class: CSS.container }, h("slot", null)))));
}
static get is() { return "calcite-tooltip"; }
static get encapsulation() { return "shadow"; }
static get originalStyleUrls() {
return {
"$": ["tooltip.scss"]
};
}
static get styleUrls() {
return {
"$": ["tooltip.css"]
};
}
static get properties() {
return {
"closeOnClick": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Closes the component when the `referenceElement` is clicked."
},
"attribute": "close-on-click",
"reflect": true,
"defaultValue": "false"
},
"label": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": true,
"optional": false,
"docs": {
"tags": [],
"text": "Accessible name for the component."
},
"attribute": "label",
"reflect": false
},
"offsetDistance": {
"type": "number",
"mutable": false,
"complexType": {
"original": "number",
"resolved": "number",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [{
"name": "default",
"text": "6"
}],
"text": "Offset the position of the component away from the `referenceElement`."
},
"attribute": "offset-distance",
"reflect": true,
"defaultValue": "defaultOffsetDistance"
},
"offsetSkidding": {
"type": "number",
"mutable": false,
"complexType": {
"original": "number",
"resolved": "number",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Offset the position of the component along the `referenceElement`."
},
"attribute": "offset-skidding",
"reflect": true,
"defaultValue": "0"
},
"open": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "When `true`, the component is open."
},
"attribute": "open",
"reflect": true,
"defaultValue": "false"
},
"overlayPositioning": {
"type": "string",
"mutable": false,
"complexType": {
"original": "OverlayPositioning",
"resolved": "\"absolute\" | \"fixed\"",
"references": {
"OverlayPositioning": {
"location": "import",
"path": "../../utils/floating-ui"
}
}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Determines the type of positioning to use for the overlaid content.\n\nUsing `\"absolute\"` will work for most cases. The component will be positioned inside of overflowing parent containers and will affect the container's layout.\n\nThe `\"fixed\"` value should be used to escape an overflowing parent container, or when the reference element's `position` CSS property is `\"fixed\"`."
},
"attribute": "overlay-positioning",
"reflect": true,
"defaultValue": "\"absolute\""
},
"placement": {
"type": "string",
"mutable": false,
"complexType": {
"original": "LogicalPlacement",
"resolved": "\"auto\" | \"top\" | \"right\" | \"bottom\" | \"left\" | \"top-start\" | \"top-end\" | \"right-start\" | \"right-end\" | \"bottom-start\" | \"bottom-end\" | \"left-start\" | \"left-end\" | \"auto-start\" | \"auto-end\" | \"leading-start\" | \"leading\" | \"leading-end\" | \"trailing-end\" | \"trailing\" | \"trailing-start\"",
"references": {
"LogicalPlacement": {
"location": "import",
"path": "../../utils/floating-ui"
}
}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Determines where the component will be positioned relative to the `referenceElement`."
},
"attribute": "placement",
"reflect": true,
"defaultValue": "\"auto\""
},
"referenceElement": {
"type": "string",
"mutable": false,
"complexType": {
"original": "ReferenceElement | string",
"resolved": "Element | VirtualElement | string",
"references": {
"ReferenceElement": {
"location": "import",
"path": "../../utils/floating-ui"
}
}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "The `referenceElement` to position the component according to its `\"placement\"` value.\n\nSetting to the `HTMLElement` is preferred so the component does not need to query the DOM for the `referenceElement`.\n\nHowever, a string ID of the reference element can be used."
},
"attribute": "reference-element",
"reflect": false
}
};
}
static get states() {
return {
"effectiveReferenceElement": {},
"floatingLayout": {}
};
}
static get events() {
return [{
"method": "calciteTooltipBeforeClose",
"name": "calciteTooltipBeforeClose",
"bubbles": true,
"cancelable": false,
"composed": true,
"docs": {
"tags": [],
"text": "Fires when the component is requested to be closed and before the closing transition begins."
},
"complexType": {
"original": "void",
"resolved": "void",
"references": {}
}
}, {
"method": "calciteTooltipClose",
"name": "calciteTooltipClose",
"bubbles": true,
"cancelable": false,
"composed": true,
"docs": {
"tags": [],
"text": "Fires when the component is closed and animation is complete."
},
"complexType": {
"original": "void",
"resolved": "void",
"references": {}
}
}, {
"method": "calciteTooltipBeforeOpen",
"name": "calciteTooltipBeforeOpen",
"bubbles": true,
"cancelable": false,
"composed": true,
"docs": {
"tags": [],
"text": "Fires when the component is added to the DOM but not rendered, and before the opening transition begins."
},
"complexType": {
"original": "void",
"resolved": "void",
"references": {}
}
}, {
"method": "calciteTooltipOpen",
"name": "calciteTooltipOpen",
"bubbles": true,
"cancelable": false,
"composed": true,
"docs": {
"tags": [],
"text": "Fires when the component is open and animation is complete."
},
"complexType": {
"original": "void",
"resolved": "void",
"references": {}
}
}];
}
static get methods() {
return {
"reposition": {
"complexType": {
"signature": "(delayed?: boolean) => Promise<void>",
"parameters": [{
"tags": [{
"name": "param",
"text": "delayed"
}],
"text": ""
}],
"references": {
"Promise": {
"location": "global"
}
},
"return": "Promise<void>"
},
"docs": {
"text": "Updates the position of the component.",
"tags": [{
"name": "param",
"text": "delayed"
}]
}
}
};
}
static get elementRef() { return "el"; }
static get watchers() {
return [{
"propName": "offsetDistance",
"methodName": "offsetDistanceOffsetHandler"
}, {
"propName": "offsetSkidding",
"methodName": "offsetSkiddingHandler"
}, {
"propName": "open",
"methodName": "openHandler"
}, {
"propName": "overlayPositioning",
"methodName": "overlayPositioningHandler"
}, {
"propName": "placement",
"methodName": "placementHandler"
}, {
"propName": "referenceElement",
"methodName": "referenceElementHandler"
}];
}
}