@descope-ui/descope-tooltip 2.2.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # Changelog
2
+
3
+ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
4
+
5
+ ## [2.2.13](https://github.com/descope/web-components-ui/compare/web-components-ui-2.2.12...web-components-ui-2.2.13) (2025-12-21)
6
+
7
+ # Changelog
@@ -0,0 +1,98 @@
1
+ import { test, expect } from '@playwright/test';
2
+ import { getStoryUrl, loopConfig } from 'e2e-utils';
3
+
4
+ const componentAttributes = {
5
+ type: ['text', 'node'],
6
+ position: [
7
+ 'top',
8
+ 'top-start',
9
+ 'top-end',
10
+ 'bottom',
11
+ 'bottom-start',
12
+ 'bottom-end',
13
+ 'start',
14
+ 'start-top',
15
+ 'start-bottom',
16
+ 'end',
17
+ 'end-top',
18
+ 'end-bottom',
19
+ ],
20
+ opened: ['true', 'false'],
21
+ };
22
+
23
+ const storyName = 'descope-tooltip';
24
+
25
+ test.describe('theme', () => {
26
+ loopConfig(componentAttributes, (attr, value) => {
27
+ test(`${attr}: ${value}`, async ({ page }) => {
28
+ await page.goto(getStoryUrl(storyName, { [attr]: value }), {
29
+ waitUntil: 'networkidle',
30
+ });
31
+ const component = page.locator('.tooltip-anchor');
32
+ const container = page.locator('.story-wrapper');
33
+
34
+ await component.hover();
35
+ await page.waitForTimeout(500);
36
+
37
+ expect(await container.screenshot()).toMatchSnapshot();
38
+ });
39
+ });
40
+
41
+ ['ltr', 'rtl'].forEach((direction) => {
42
+ test(`direction: ${direction}`, async ({ page }) => {
43
+ await page.goto(getStoryUrl(storyName, { direction, text: 'test--' }));
44
+ const component = page.locator('.tooltip-anchor');
45
+ const container = page.locator('.story-wrapper');
46
+
47
+ await component.hover();
48
+
49
+ expect(await container.screenshot()).toMatchSnapshot();
50
+ });
51
+ });
52
+ });
53
+
54
+ test.describe('logic', () => {
55
+ test('show and hide on hover and blur', async ({ page }) => {
56
+ await page.goto(getStoryUrl(storyName));
57
+ const component = page.locator('.tooltip-anchor');
58
+ const container = page.locator('.story-wrapper');
59
+
60
+ expect(await container.screenshot()).toMatchSnapshot();
61
+
62
+ await component.hover();
63
+ expect(await container.screenshot()).toMatchSnapshot();
64
+
65
+ await container.click();
66
+ expect(await container.screenshot()).toMatchSnapshot();
67
+ });
68
+
69
+ test('hover-delay', async ({ page }) => {
70
+ await page.goto(getStoryUrl(storyName, { hoverDelay: 2000 }));
71
+ const component = page.locator('.tooltip-anchor');
72
+ const container = page.locator('.story-wrapper');
73
+
74
+ // hover component
75
+ await component.hover();
76
+
77
+ // no tooltip should appear yet
78
+ expect(await container.screenshot()).toMatchSnapshot();
79
+
80
+ // wait 2 seconds
81
+ await page.waitForTimeout(2000);
82
+
83
+ // tooltip should appear
84
+ expect(await container.screenshot()).toMatchSnapshot();
85
+ });
86
+
87
+ test('hide-delay', async ({ page }) => {
88
+ await page.goto(getStoryUrl(storyName, { hideDelay: 5000 }));
89
+ const component = page.locator('.tooltip-anchor');
90
+ const container = page.locator('.story-wrapper');
91
+
92
+ await component.hover();
93
+ expect(await container.screenshot()).toMatchSnapshot();
94
+
95
+ await page.waitForTimeout(3000);
96
+ expect(await container.screenshot()).toMatchSnapshot();
97
+ });
98
+ });
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@descope-ui/descope-tooltip",
3
+ "version": "2.2.13",
4
+ "exports": {
5
+ ".": {
6
+ "import": "./src/component/index.js"
7
+ },
8
+ "./theme": {
9
+ "import": "./src/theme.js"
10
+ },
11
+ "./class": {
12
+ "import": "./src/component/TooltipClass.js"
13
+ }
14
+ },
15
+ "devDependencies": {
16
+ "@playwright/test": "1.38.1",
17
+ "e2e-utils": "2.2.13"
18
+ },
19
+ "dependencies": {
20
+ "@vaadin/tooltip": "24.3.4",
21
+ "@descope-ui/common": "2.2.13",
22
+ "@descope-ui/theme-globals": "2.2.13",
23
+ "@descope-ui/descope-enriched-text": "2.2.13"
24
+ },
25
+ "publishConfig": {
26
+ "link-workspace-packages": false
27
+ },
28
+ "scripts": {
29
+ "test": "echo 'No tests defined' && exit 0",
30
+ "test:e2e": "echo 'No e2e tests defined' && exit 0"
31
+ }
32
+ }
package/project.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "@descope-ui/descope-tooltip",
3
+ "$schema": "../../node_modules/nx/schemas/project-schema.json",
4
+ "sourceRoot": "packages/web-components/components/descope-tooltip/src",
5
+ "projectType": "library",
6
+ "targets": {
7
+ "version": {
8
+ "executor": "@jscutlery/semver:version",
9
+ "options": {
10
+ "trackDeps": true,
11
+ "push": false,
12
+ "preset": "conventional"
13
+ }
14
+ }
15
+ },
16
+ "tags": []
17
+ }
@@ -0,0 +1,219 @@
1
+ import {
2
+ componentNameValidationMixin,
3
+ portalMixin,
4
+ } from '@descope-ui/common/components-mixins';
5
+ import { compose } from '@descope-ui/common/utils';
6
+ import {
7
+ forwardAttrs,
8
+ getComponentName,
9
+ } from '@descope-ui/common/components-helpers';
10
+ import { createBaseClass } from '@descope-ui/common/base-classes';
11
+ import { EnrichedTextClass } from '@descope-ui/descope-enriched-text/class';
12
+
13
+ export const componentName = getComponentName('tooltip');
14
+
15
+ const tooltipAttrs = [
16
+ 'text',
17
+ 'position',
18
+ 'hide-delay',
19
+ 'hover-delay',
20
+ 'opened',
21
+ ];
22
+
23
+ const BaseClass = createBaseClass({
24
+ componentName,
25
+ baseSelector: 'vaadin-tooltip',
26
+ });
27
+
28
+ class RawTooltip extends BaseClass {
29
+ static get observedAttributes() {
30
+ return tooltipAttrs.concat(BaseClass.observedAttributes || []);
31
+ }
32
+
33
+ get isOpened() {
34
+ return this.getAttribute('opened') === 'true';
35
+ }
36
+
37
+ get overlay() {
38
+ return this.tooltip?._overlayElement;
39
+ }
40
+
41
+ get overlayContentNode() {
42
+ return this.overlay?.shadowRoot.querySelector('[part="content"]');
43
+ }
44
+
45
+ get tooltipText() {
46
+ return this.getAttribute('text')?.trim() || '';
47
+ }
48
+
49
+ init() {
50
+ super.init();
51
+
52
+ // Create the vaadin-tooltip here instead of constructor (for React compatibility)
53
+ this.style.display = 'contents';
54
+ this.insertAdjacentHTML('beforeend', '<vaadin-tooltip></vaadin-tooltip>');
55
+ this.tooltip = this.querySelector('vaadin-tooltip');
56
+
57
+ this.tooltip.style.width = '0';
58
+ this.tooltip.style.height = '0';
59
+ this.tooltip.style.display = 'block';
60
+ this.tooltip.style.overflow = 'hidden';
61
+ this.tooltip.style.position = 'absolute';
62
+
63
+ this.#setTooltipTarget();
64
+
65
+ setTimeout(() => this.#onOverlayReady());
66
+ }
67
+
68
+ #onOverlayReady() {
69
+ this.#initTooltipTextComponent();
70
+ this.#overrideAttachOverlay();
71
+
72
+ forwardAttrs(this, this.tooltip, {
73
+ includeAttrs: ['position', 'opened'],
74
+ });
75
+
76
+ this.#handleTooltipVisibility();
77
+ }
78
+
79
+ #setTooltipTarget() {
80
+ if (!this.children?.length) return;
81
+
82
+ let ele = Array.from(this.children).find(
83
+ (child) => child !== this.tooltip,
84
+ );
85
+
86
+ if (!ele) return;
87
+
88
+ this.tooltip.target = ele;
89
+ }
90
+
91
+ #clearOverlayContentNode() {
92
+ this.overlayContentNode.innerHTML = '';
93
+ }
94
+
95
+ #createEnrichedTextComponent() {
96
+ const enrichedText = document.createElement('descope-enriched-text');
97
+
98
+ enrichedText.setAttribute('link-target-blank', 'true');
99
+ enrichedText.textContent = this.tooltipText;
100
+
101
+ return enrichedText;
102
+ }
103
+
104
+ #initTooltipTextComponent() {
105
+ if (!this.overlayContentNode) return;
106
+
107
+ setTimeout(() => {
108
+ this.#clearOverlayContentNode();
109
+
110
+ this.textComponent = this.#createEnrichedTextComponent();
111
+
112
+ this.overlayContentNode.appendChild(this.textComponent);
113
+
114
+ forwardAttrs(this, this.textComponent, {
115
+ includeAttrs: ['readonly'],
116
+ });
117
+ });
118
+ }
119
+
120
+ // the default vaadin behavior is to attach the overlay to the body when opened
121
+ // we do not want that because it's difficult to style the overlay in this way
122
+ // so we override it to open inside the shadow DOM
123
+ #overrideAttachOverlay() {
124
+ if (!this.overlay) return;
125
+
126
+ if (this.isOpened) {
127
+ // When `opened` attr is used, vaadin doesn't execute `_attachOverlay`,
128
+ // and the overlay element is rendered outside the component, on the top
129
+ // level. We need to move it back to the local component's DOM.
130
+ setTimeout(() => this.tooltip.shadowRoot.appendChild(this.overlay));
131
+ } else {
132
+ this.overlay._detachOverlay = () => {};
133
+
134
+ this.overlay._attachOverlay = () =>
135
+ this.tooltip.shadowRoot.appendChild(this.overlay);
136
+ }
137
+ }
138
+
139
+ #handleTooltipVisibility() {
140
+ // This is Vaadin's API for manual control of tooltip visibility
141
+ this.tooltip?.toggleAttribute('manual', this.isOpened);
142
+ }
143
+
144
+ #updateText(value) {
145
+ if (!this.textComponent) return;
146
+ this.textComponent.textContent = value?.trim();
147
+ }
148
+
149
+ attributeChangedCallback(attrName, oldValue, newValue) {
150
+ super.attributeChangedCallback?.(attrName, oldValue, newValue);
151
+
152
+ if (oldValue !== newValue) {
153
+ if (attrName === 'text') {
154
+ this.#updateText(newValue);
155
+ }
156
+
157
+ if (attrName === 'opened') {
158
+ this.#handleTooltipVisibility(attrName, newValue)
159
+ }
160
+ }
161
+ }
162
+ }
163
+
164
+ const { overlay, content } = {
165
+ overlay: { selector: () => 'vaadin-tooltip-overlay::part(overlay)' },
166
+ content: { selector: () => 'vaadin-tooltip-overlay::part(content)' },
167
+ };
168
+
169
+ /**
170
+ * This component has no Shadow DOM of its own, so we can't add styles to it
171
+ * (otherwise it would affect the rest of the DOM).
172
+ * Note that all styles are within PortalMixin.
173
+ */
174
+ export const TooltipClass = compose(
175
+ componentNameValidationMixin,
176
+ portalMixin({
177
+ selector: '',
178
+ mappings: {
179
+ fontFamily: {
180
+ ...content,
181
+ property: EnrichedTextClass.cssVarList.fontFamilyOverride,
182
+ },
183
+ fontSize: {
184
+ ...content,
185
+ property: EnrichedTextClass.cssVarList.fontSizeOverride,
186
+ },
187
+ fontWeight: {
188
+ ...content,
189
+ property: EnrichedTextClass.cssVarList.fontWeightOverride,
190
+ },
191
+ textColor: {
192
+ ...content,
193
+ property: EnrichedTextClass.cssVarList.textColorOverride,
194
+ },
195
+ hostDirection: {
196
+ ...content,
197
+ property: EnrichedTextClass.cssVarList.hostDirectionOverride,
198
+ },
199
+ backgroundColor: [{ ...overlay }, { ...content }],
200
+ borderColor: { ...overlay },
201
+ borderStyle: { ...overlay },
202
+ borderWidth: { ...overlay },
203
+ borderRadius: { ...overlay },
204
+ boxShadow: { ...overlay },
205
+ horizontalPadding: [
206
+ { ...content, property: 'padding-left' },
207
+ { ...content, property: 'padding-right' },
208
+ ],
209
+ verticalPadding: [
210
+ { ...content, property: 'padding-top' },
211
+ { ...content, property: 'padding-bottom' },
212
+ ],
213
+ },
214
+ forward: {
215
+ attributes: ['style'],
216
+ include: false,
217
+ },
218
+ }),
219
+ )(RawTooltip);
@@ -0,0 +1,7 @@
1
+ import '@descope-ui/descope-enriched-text';
2
+ import '@vaadin/tooltip';
3
+ import { componentName, TooltipClass } from './TooltipClass';
4
+
5
+ customElements.define(componentName, TooltipClass);
6
+
7
+ export { TooltipClass, componentName };
package/src/theme.js ADDED
@@ -0,0 +1,25 @@
1
+ import globals from '@descope-ui/theme-globals';
2
+ import { getThemeRefs } from '@descope-ui/common/theme-helpers';
3
+ import { TooltipClass } from './component/TooltipClass';
4
+
5
+ const globalRefs = getThemeRefs(globals);
6
+ const vars = TooltipClass.cssVarList;
7
+
8
+ const tooltip = {
9
+ [vars.fontFamily]: globalRefs.fonts.font1.family,
10
+ [vars.fontSize]: globals.typography.body2.size,
11
+ [vars.fontWeight]: globals.typography.body2.weight,
12
+ [vars.textColor]: globalRefs.colors.surface.contrast,
13
+ [vars.hostDirection]: globalRefs.direction,
14
+ [vars.backgroundColor]: globalRefs.colors.surface.main,
15
+ [vars.borderColor]: globalRefs.colors.surface.light,
16
+ [vars.borderStyle]: 'solid',
17
+ [vars.borderWidth]: globalRefs.border.xs,
18
+ [vars.borderRadius]: globalRefs.radius.xs,
19
+ [vars.horizontalPadding]: globalRefs.spacing.md,
20
+ [vars.verticalPadding]: globalRefs.spacing.sm,
21
+ [vars.boxShadow]: globalRefs.shadow.wide.sm,
22
+ };
23
+
24
+ export default tooltip;
25
+ export { vars };
@@ -0,0 +1,122 @@
1
+ import { componentName } from '../src/component';
2
+ import { directionControl } from '@descope-ui/common/sb-controls';
3
+
4
+ const Template = ({
5
+ text,
6
+ position,
7
+ direction,
8
+ type,
9
+ textContent,
10
+ hoverDelay,
11
+ hideDelay,
12
+ opened,
13
+ readonly,
14
+ }) => `
15
+ <descope-tooltip
16
+ text="${text || ''}"
17
+ position="${position || 'top'}"
18
+ hover-delay="${hoverDelay || 0}"
19
+ hide-delay="${hideDelay || 0}"
20
+ st-host-direction="${direction ?? ''}"
21
+ opened="${opened || false}"
22
+ readonly="${readonly || false}"
23
+ >
24
+ ${type === 'node' ? `<descope-button class="tooltip-anchor" size="md" variant="contained" mode="primary" full-width="true">Click</descope-button>` : ''}
25
+ ${type === 'text' ? `<span class="tooltip-anchor">${textContent}</span>` : ''}
26
+ </descope-tooltip>
27
+ `;
28
+
29
+ export default {
30
+ component: componentName,
31
+ title: 'descope-tooltip',
32
+ decorators: [
33
+ (story) => {
34
+ return `
35
+ <style nonce="${window.DESCOPE_NONCE}">
36
+ .story-wrapper {
37
+ display: flex;
38
+ width: 100%;
39
+ align-items: center;
40
+ justify-content: center;
41
+ }
42
+ .content {
43
+ padding: 6em;
44
+ width: 300px;
45
+ }
46
+ </style>
47
+ <div class="story-wrapper">
48
+ <div class="content">
49
+ ${story()}
50
+ </div>
51
+ </div>
52
+ `;
53
+ },
54
+ ],
55
+ parameters: {
56
+ panelPosition: 'right',
57
+ controls: { expanded: true },
58
+ },
59
+ argTypes: {
60
+ ...directionControl,
61
+ type: {
62
+ name: 'Content Type',
63
+ control: { type: 'select' },
64
+ options: ['node', 'text'],
65
+ },
66
+ text: {
67
+ name: 'Tooltip Text',
68
+ control: { type: 'text' },
69
+ },
70
+ textContent: {
71
+ name: 'Text Content',
72
+ control: { type: 'text' },
73
+ },
74
+ position: {
75
+ name: 'Position',
76
+ control: { type: 'select' },
77
+ options: [
78
+ 'top',
79
+ 'top-start',
80
+ 'top-end',
81
+ 'bottom',
82
+ 'bottom-start',
83
+ 'bottom-end',
84
+ 'start',
85
+ 'start-top',
86
+ 'start-bottom',
87
+ 'end',
88
+ 'end-top',
89
+ 'end-bottom',
90
+ ],
91
+ },
92
+ hoverDelay: {
93
+ name: 'Hover Delay',
94
+ control: { type: 'number' },
95
+ },
96
+ hideDelay: {
97
+ name: 'Hide Delay',
98
+ control: { type: 'number' },
99
+ },
100
+ opened: {
101
+ name: 'Opened',
102
+ control: { type: 'boolean' },
103
+ },
104
+ readonly: {
105
+ name: 'readonly',
106
+ control: { type: 'boolean' },
107
+ },
108
+ },
109
+ };
110
+
111
+ export const Default = Template.bind({});
112
+
113
+ Default.args = {
114
+ text: 'This is a [tooltip](http://descope.test) with **markdown** support!',
115
+ hoverDelay: 1,
116
+ hideDelay: 1,
117
+ opened: false,
118
+ readonly: false,
119
+ type: 'text',
120
+ textContent:
121
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
122
+ };