html`
${BlrTooltipRenderFunction(params, html`
`)}
`;
-BlrTooltipLargeReference.storyName = 'Tooltip Large Reference';
-BlrTooltip2SmallReference.storyName = 'Tooltip Small Reference';
+export const BlrTooltipVirtualReference = (params: BlrTooltipType) => {
+ return html`
`;
+};
+
+BlrTooltip.storyName = 'Tooltip Large Reference';
+BlrTooltipSmallReference.storyName = 'Tooltip Small Reference';
+BlrTooltipVirtualReference.storyName = 'Tooltip Virtual Reference';
const args: BlrTooltipType = {
theme: 'Light',
@@ -46,5 +64,51 @@ const args: BlrTooltipType = {
offset: 4,
};
-BlrTooltip2SmallReference.args = args;
-BlrTooltipLargeReference.args = args;
+BlrTooltip.args = args;
+BlrTooltipSmallReference.args = args;
+BlrTooltipVirtualReference.args = args;
+
+@customElement('virtual-reference')
+export class VirtualReference extends LitElement {
+ @property() theme: ThemeType = 'Light';
+ @property() size: FormSizesType = 'sm';
+ @property() text!: string;
+ @property() placement: PlacementType = 'top';
+ @property() hasArrow = true;
+ @property() elevation = true;
+ @property() offset = 4;
+
+ @query('blr-tooltip-element')
+ protected _tooltip!: HTMLElement;
+
+ protected firstUpdated() {
+ document.addEventListener('mousemove', ({ clientX, clientY }) => {
+ const virtualReference = {
+ getBoundingClientRect() {
+ return {
+ width: 0,
+ height: 0,
+ x: clientX,
+ y: clientY,
+ left: clientX,
+ right: clientX,
+ top: clientY,
+ bottom: clientY,
+ };
+ },
+ };
+
+ setupTooltip(virtualReference, this._tooltip, this.placement, 4);
+ });
+ }
+
+ render() {
+ return html`${BlrTooltipElementRenderFunction({
+ theme: this.theme,
+ text: this.text,
+ size: this.size,
+ hasArrow: this.hasArrow,
+ elevation: this.elevation,
+ })}`;
+ }
+}
diff --git a/packages/ui-library/src/components/feedback/tooltip/index.ts b/packages/ui-library/src/components/feedback/tooltip/index.ts
index df1c30237..586c593da 100644
--- a/packages/ui-library/src/components/feedback/tooltip/index.ts
+++ b/packages/ui-library/src/components/feedback/tooltip/index.ts
@@ -1,108 +1,47 @@
-import { LitElement, TemplateResult, html, nothing } from 'lit';
+import { LitElement, TemplateResult, html } from 'lit';
import { customElement, property, query } from 'lit/decorators.js';
-import { tooltipLight, tooltipDark } from './index.css';
-import { computePosition, flip, offset, arrow, Placement, autoUpdate } from '@floating-ui/dom';
+import type { VirtualElement } from '@floating-ui/core';
+import { Placement as PlacementType } from '@floating-ui/dom';
import { FormSizesType } from '../../../globals/types';
import { ThemeType } from '../../../foundation/_tokens-generated/index.themes';
import { genericBlrComponentRenderer } from '../../../utils/typesafe-generic-component-renderer';
-import { classMap } from 'lit/directives/class-map.js';
-import { componentTokens } from '../../../foundation/_tokens-generated/__component-tokens.Light.generated.mjs';
+import { setupTooltip } from './setupTooltip';
+import { BlrTooltipElementRenderFunction } from './tooltipElement';
const TAG_NAME = 'blr-tooltip';
const enterEvents = ['pointerenter', 'focus'];
const leaveEvents = ['pointerleave', 'blur', 'keydown', 'click'];
-const {
- Feedback: { Tooltip },
-} = componentTokens;
-
@customElement('blr-tooltip')
export class BlrTooltip extends LitElement {
- @property() theme?: ThemeType = 'Light';
+ @property() theme: ThemeType = 'Light';
@property() size: FormSizesType = 'sm';
@property() text!: string;
- @property() placement?: Placement = 'top';
- @property() hasArrow? = true;
+ @property() placement: PlacementType = 'top';
+ @property() hasArrow = true;
@property() elevation = true;
@property() offset = 4;
- @query('.tooltip')
+ @query('blr-tooltip-element')
protected _tooltip!: HTMLElement;
- @query('.arrow')
- protected _arrow!: HTMLElement;
-
- protected arrowHeight = 4;
-
- protected _slotTrigger: Element | undefined = undefined;
-
- connectedCallback() {
- super.connectedCallback();
- }
+ protected _slotReference: Element | undefined = undefined;
protected firstUpdated() {
- const slot = this?.shadowRoot?.querySelector('slot');
- this._slotTrigger = slot?.assignedElements({ flatten: true })[0];
+ const slot = this.shadowRoot?.querySelector('slot');
+ this._slotReference = slot?.assignedElements({ flatten: true })[0];
- enterEvents.forEach((event) => this._slotTrigger?.addEventListener(event, this.show));
- leaveEvents.forEach((event) => this._slotTrigger?.addEventListener(event, this.hide));
+ enterEvents.forEach((event) => this._slotReference?.addEventListener(event, this.show));
+ leaveEvents.forEach((event) => this._slotReference?.addEventListener(event, this.hide));
}
- protected setupTooltip = (trigger: Element, tooltip: HTMLElement) => {
- autoUpdate(trigger, tooltip, () => {
- computePosition(trigger, tooltip, {
- placement: this.placement,
- middleware: [
- offset((this._arrow && this.arrowHeight) + this.offset),
- flip(),
- this._arrow && arrow({ element: this._arrow, padding: 8 }),
- ],
- }).then(({ x, y, middlewareData, placement }) => {
- Object.assign(this._tooltip.style, {
- left: `${x}px`,
- top: `${y}px`,
- });
-
- if (middlewareData.arrow) {
- const { x, y, centerOffset } = middlewareData.arrow;
-
- const isCentered = centerOffset === 0;
- const side = placement.split('-')[0];
-
- const staticSide = {
- top: 'bottom',
- right: 'left',
- bottom: 'top',
- left: 'right',
- }[side];
-
- const staticRotation = {
- top: 'rotate(0)',
- right: 'rotate(90deg)',
- bottom: 'rotate(180deg)',
- left: 'rotate(-90deg)',
- }[side];
-
- Object.assign(this._arrow.style, {
- top: isCentered && y !== null ? `${y}px` : '',
- right: !isCentered && x !== null ? `${x}px` : '',
- bottom: !isCentered && y !== null ? `${y}px` : '',
- left: isCentered && x !== null ? `${x}px` : '',
- transform: staticRotation,
- [staticSide]: `-${this.arrowHeight}px`,
- });
- }
- });
- });
- };
-
protected show = () => {
- if (!this._slotTrigger) {
+ if (!this._slotReference) {
return;
}
- this.setupTooltip(this._slotTrigger, this._tooltip);
+ setupTooltip(this._slotReference, this._tooltip, this.placement, this.offset);
this._tooltip.style.visibility = 'visible';
this._tooltip.style.opacity = '1';
@@ -114,32 +53,20 @@ export class BlrTooltip extends LitElement {
};
protected render() {
- const dynamicStyles = this.theme === 'Light' ? [tooltipLight] : [tooltipDark];
-
- const classes = classMap({
- tooltip: true,
- [this.size]: this.size,
- elevation: this.elevation,
- });
-
- return html`
+ return html`
-
-
${this.text}
- ${this.hasArrow
- ? html`
`
- : nothing}
-
`;
+ ${BlrTooltipElementRenderFunction({
+ theme: this.theme,
+ text: this.text,
+ size: this.size,
+ hasArrow: this.hasArrow,
+ elevation: this.elevation,
+ })}
+
`;
}
}
export type BlrTooltipType = Omit
;
-export const BlrTooltipRenderFunction = (params: BlrTooltipType, children?: TemplateResult<1>) =>
+export const BlrTooltipRenderFunction = (params: BlrTooltipType, children: TemplateResult<1>) =>
genericBlrComponentRenderer(TAG_NAME, { ...params }, children);
diff --git a/packages/ui-library/src/components/feedback/tooltip/indexReact.ts b/packages/ui-library/src/components/feedback/tooltip/indexReact.ts
new file mode 100644
index 000000000..4a7df2c36
--- /dev/null
+++ b/packages/ui-library/src/components/feedback/tooltip/indexReact.ts
@@ -0,0 +1,10 @@
+import React from 'react';
+import { createComponent } from '@lit-labs/react';
+
+import { BlrTooltip } from '.';
+
+export const BlrToolTipReact = createComponent({
+ tagName: 'blr-tooltip',
+ elementClass: BlrTooltip,
+ react: React,
+});
diff --git a/packages/ui-library/src/components/feedback/tooltip/setupTooltip.ts b/packages/ui-library/src/components/feedback/tooltip/setupTooltip.ts
new file mode 100644
index 000000000..24ca7c270
--- /dev/null
+++ b/packages/ui-library/src/components/feedback/tooltip/setupTooltip.ts
@@ -0,0 +1,81 @@
+import {
+ VirtualElement,
+ autoUpdate,
+ computePosition,
+ flip,
+ offset,
+ arrow,
+ Placement as PlacementType,
+} from '@floating-ui/dom';
+import { componentTokens } from '../../../foundation/_tokens-generated/__component-tokens.Light.generated.mjs';
+
+const {
+ Feedback: { Tooltip },
+} = componentTokens;
+
+export const setupTooltip = (
+ reference: Element | VirtualElement,
+ tooltip: HTMLElement,
+ placement: PlacementType,
+ offsetValue: number
+) => {
+ const side = placement.split('-')[0];
+
+ const arrowNode = tooltip.shadowRoot?.querySelector('.arrow');
+
+ const arrowNodeHeight = arrowNode ? 4 : 0;
+ const arrowNodePaddingTopBottom = parseFloat(Tooltip.NoseWrapper.PaddingTopBottom);
+ const arrowNodePaddingLeftRight = parseFloat(Tooltip.NoseWrapper.PaddingLeftRight);
+
+ const arrowNodePadding =
+ side === 'top' || 'bottom'
+ ? arrowNodePaddingTopBottom
+ : side === 'left' || 'right'
+ ? arrowNodePaddingLeftRight
+ : undefined;
+
+ autoUpdate(reference, tooltip, () => {
+ computePosition(reference, tooltip, {
+ placement,
+ middleware: [
+ offset(arrowNodeHeight + offsetValue),
+ flip(),
+ arrowNode && arrow({ element: arrowNode, padding: arrowNodePadding }),
+ ],
+ }).then(({ x, y, middlewareData }) => {
+ Object.assign(tooltip.style, {
+ left: `${x}px`,
+ top: `${y}px`,
+ });
+
+ if (middlewareData.arrow) {
+ const { x, y, centerOffset } = middlewareData.arrow;
+
+ const isCentered = centerOffset === 0;
+
+ const staticSide = {
+ top: 'bottom',
+ right: 'left',
+ bottom: 'top',
+ left: 'right',
+ }[side];
+
+ const staticRotation = {
+ top: 'rotate(0)',
+ right: 'rotate(90deg)',
+ bottom: 'rotate(180deg)',
+ left: 'rotate(-90deg)',
+ }[side];
+
+ Object.assign(arrowNode.style, {
+ top: isCentered && y !== null ? `${y}px` : '',
+ right: !isCentered && x !== null ? `${x}px` : '',
+ bottom: !isCentered && y !== null ? `${y}px` : '',
+ left: isCentered && x !== null ? `${x}px` : '',
+ transform: staticRotation,
+ [`${staticSide}`]: `-${arrowNodeHeight}px`,
+ });
+ }
+ });
+ });
+};
diff --git a/packages/ui-library/src/components/feedback/tooltip/tooltipElement.ts b/packages/ui-library/src/components/feedback/tooltip/tooltipElement.ts
new file mode 100644
index 000000000..1e2105ce0
--- /dev/null
+++ b/packages/ui-library/src/components/feedback/tooltip/tooltipElement.ts
@@ -0,0 +1,46 @@
+import { LitElement, html, nothing } from 'lit';
+import { customElement, property } from 'lit/decorators.js';
+import { tooltipLight, tooltipDark } from './index.css';
+import { FormSizesType } from '../../../globals/types';
+import { ThemeType } from '../../../foundation/_tokens-generated/index.themes';
+import { genericBlrComponentRenderer } from '../../../utils/typesafe-generic-component-renderer';
+import { classMap } from 'lit/directives/class-map.js';
+
+const TAG_NAME = 'blr-tooltip-element';
+
+@customElement('blr-tooltip-element')
+export class BlrTooltipElement extends LitElement {
+ @property() theme: ThemeType = 'Light';
+ @property() size: FormSizesType = 'sm';
+ @property() text!: string;
+ @property() hasArrow = true;
+ @property() elevation = true;
+
+ protected render() {
+ const dynamicStyles = this.theme === 'Light' ? [tooltipLight] : [tooltipDark];
+
+ const classes = classMap({
+ [this.size]: this.size,
+ elevation: this.elevation,
+ });
+
+ return html`
+
+
${this.text}
+ ${this.hasArrow
+ ? html`
`
+ : nothing}
+
`;
+ }
+}
+
+export type BlrTooltipElementType = Omit;
+
+export const BlrTooltipElementRenderFunction = (params: BlrTooltipElementType) =>
+ genericBlrComponentRenderer(TAG_NAME, { ...params });