Skip to content

Commit 5f3e582

Browse files
authored
feat: Allow to set a template element as tooltip content (#30)
The `contentClone` prop has been removed since it is not needed anymore
1 parent 70c5d2a commit 5f3e582

File tree

6 files changed

+265
-304
lines changed

6 files changed

+265
-304
lines changed

README.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ yarn add @untemps/svelte-use-tooltip
3838
3939
<div use:useTooltip={{
4040
contentSelector: '.tooltip__content',
41-
contentClone: false,
4241
contentActions: {
4342
'*': {
4443
eventType: 'click',
@@ -114,7 +113,6 @@ yarn add @untemps/svelte-use-tooltip
114113
|---------------------------|---------|-------------------|-----------------------------------------------------------------------------------------------------------------|
115114
| `content` | string | null | Text content to display in the tooltip. |
116115
| `contentSelector` | string | null | Selector of the content to display in the tooltip. |
117-
| `contentClone` | boolean | false | Flag to clone the content to display in the tooltip. If false, the content is removed from its previous parent. |
118116
| `contentActions` | object | null | Configuration of the tooltip actions (see [Content Actions](#content-actions)). |
119117
| `containerClassName` | string | '__tooltip' | Class name to apply to the tooltip container. |
120118
| `position` | string | 'top' | Position of the tooltip. Available values: 'top', 'bottom', 'left', 'right' |
@@ -126,6 +124,18 @@ yarn add @untemps/svelte-use-tooltip
126124
| `offset` | number | 10 | Distance between the tooltip and the target in pixels. |
127125
| `disabled` | boolean | false | Flag to disable the tooltip content. |
128126

127+
### Content and Content Selector
128+
129+
The tooltip content can be specified either by the `content` prop or the `contentSelector` prop.
130+
131+
`content` must be a text string that will be displayed as is in the tooltip.
132+
133+
It's useful for most of the use cases of a tooltip however sometimes you need to display some more complex content, with interactive elements or formatted text.
134+
135+
To do so, you may use the `contentSelector` prop that allows to specify the selector of an element from the DOM.
136+
137+
The best option is to use a [template](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template) HTML element although you may also use a plain element. In this case, **it will remain in the DOM and will be clones in the tooltip**.
138+
129139
### Content Actions
130140

131141
The `contentActions` prop allows to handle interactions within the tooltip content.
@@ -141,7 +151,6 @@ One event by element is possible so far as elements are referenced by selector.
141151
142152
<div use:useTooltip={{
143153
contentSelector: '#content',
144-
contentClone: false,
145154
contentActions: {
146155
'#button1': {
147156
eventType: 'mouseenter',

dev/src/App.svelte

Lines changed: 35 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,7 @@
2323
use:useTooltip={{
2424
position: tooltipPosition,
2525
content: tooltipTextContent,
26-
contentSelector: !tooltipTextContent?.length ? '.tooltip__content' : null,
27-
contentClone: true,
26+
contentSelector: !tooltipTextContent?.length ? '#tooltip-template' : null,
2827
contentActions: {
2928
'*': {
3029
eventType: 'click',
@@ -40,20 +39,17 @@
4039
enterDelay: tooltipEnterDelay,
4140
leaveDelay: tooltipLeaveDelay,
4241
offset: tooltipOffset,
43-
disabled: isTooltipDisabled
42+
disabled: isTooltipDisabled,
4443
}}
4544
class="target"
4645
>
4746
Hover me
4847
</div>
48+
<template id="tooltip-template">
49+
<span class="tooltip__content">Hi! I'm a <i>fancy</i> <strong>tooltip</strong>!</span>
50+
</template>
4951
<form class="settings__form">
5052
<h1>Settings</h1>
51-
<fieldset>
52-
<label>
53-
Default Tooltip Content:
54-
<span class="tooltip__content">Hi! I'm a <i>fancy</i> <strong>tooltip</strong>!</span>
55-
</label>
56-
</fieldset>
5753
<fieldset>
5854
<label>
5955
Tooltip Text Content:
@@ -204,36 +200,36 @@
204200
}
205201
206202
:global(.tooltip::after) {
207-
content: '';
208-
position: absolute;
209-
margin-left: -5px;
210-
border-width: 5px;
211-
border-style: solid;
212-
}
213-
214-
:global(.tooltip-top::after) {
215-
bottom: -10px;
216-
left: 50%;
217-
border-color: #ee7008 transparent transparent transparent;
218-
}
219-
220-
:global(.tooltip-bottom::after) {
221-
top: -10px;
222-
left: 50%;
223-
border-color: transparent transparent #ee7008 transparent;
224-
}
225-
226-
:global(.tooltip-left::after) {
227-
top: calc(50% - 5px);
228-
right: -10px;
229-
border-color: transparent transparent transparent #ee7008;
230-
}
231-
232-
:global(.tooltip-right::after) {
233-
top: calc(50% - 5px);
234-
left: -5px;
235-
border-color: transparent #ee7008 transparent transparent;
236-
}
203+
content: '';
204+
position: absolute;
205+
margin-left: -5px;
206+
border-width: 5px;
207+
border-style: solid;
208+
}
209+
210+
:global(.tooltip-top::after) {
211+
bottom: -10px;
212+
left: 50%;
213+
border-color: #ee7008 transparent transparent transparent;
214+
}
215+
216+
:global(.tooltip-bottom::after) {
217+
top: -10px;
218+
left: 50%;
219+
border-color: transparent transparent #ee7008 transparent;
220+
}
221+
222+
:global(.tooltip-left::after) {
223+
top: calc(50% - 5px);
224+
right: -10px;
225+
border-color: transparent transparent transparent #ee7008;
226+
}
227+
228+
:global(.tooltip-right::after) {
229+
top: calc(50% - 5px);
230+
left: -5px;
231+
border-color: transparent #ee7008 transparent transparent;
232+
}
237233
238234
@keyframes fadeIn {
239235
from {

jest/jest.setup.js

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
1+
import { fireEvent } from '@testing-library/svelte'
2+
13
const { toBeInTheDocument, toHaveAttribute, toHaveStyle } = require('@testing-library/jest-dom/matchers')
24
import '@testing-library/jest-dom/extend-expect'
35

46
expect.extend({ toBeInTheDocument, toHaveAttribute, toHaveStyle })
57

6-
global._createElement = (id = 'foo', parent, attrs) => {
7-
const el = document.createElement('div')
8-
el.setAttribute('id', id)
8+
global._createAndAddElement = (tagName, attrs, parent) => {
9+
const el = document.createElement(tagName)
10+
el.setAttribute('id', 'foo')
911
for (let key in attrs) {
1012
el.setAttribute(key, attrs[key])
1113
}
1214
;(parent || document.body).appendChild(el)
1315
return el
1416
}
1517

16-
global._removeElement = (selector) => {
18+
global._destroyElement = (selector) => {
1719
const el = document.querySelector(selector)
1820
if (!!el) {
1921
el.parentNode.removeChild(el)
@@ -27,6 +29,60 @@ global._modifyElement = (selector, attributeName, attributeValue) => {
2729
}
2830
}
2931

32+
global._getElement = (selector) => {
33+
return document.querySelector(selector)
34+
}
35+
3036
global._sleep = (ms = 100) => {
3137
return new Promise((resolve) => setTimeout(resolve, ms))
3238
}
39+
40+
global._enter = async (trigger) =>
41+
new Promise(async (resolve) => {
42+
await fireEvent.mouseOver(trigger) // fireEvent.mouseEnter only works if mouseOver is triggered before
43+
await fireEvent.mouseEnter(trigger)
44+
await _sleep(1)
45+
resolve()
46+
})
47+
48+
global._leave = async (trigger) =>
49+
new Promise(async (resolve) => {
50+
await fireEvent.mouseLeave(trigger)
51+
await _sleep(1)
52+
resolve()
53+
})
54+
55+
global._enterAndLeave = async (trigger) =>
56+
new Promise(async (resolve) => {
57+
await _enter(trigger)
58+
await _leave(trigger)
59+
resolve()
60+
})
61+
62+
global._focus = async (trigger) =>
63+
new Promise(async (resolve) => {
64+
await fireEvent.focusIn(trigger)
65+
await _sleep(1)
66+
resolve()
67+
})
68+
69+
global._blur = async (trigger) =>
70+
new Promise(async (resolve) => {
71+
await fireEvent.focusOut(trigger)
72+
await _sleep(1)
73+
resolve()
74+
})
75+
76+
global._focusAndBlur = async (trigger) =>
77+
new Promise(async (resolve) => {
78+
await _focus(trigger)
79+
await _blur(trigger)
80+
resolve()
81+
})
82+
83+
global._keyDown = async (trigger, key) =>
84+
new Promise(async (resolve) => {
85+
await fireEvent.keyDown(trigger, key || { key: 'Escape', code: 'Escape', charCode: 27 })
86+
await _sleep(1)
87+
resolve()
88+
})

src/Tooltip.js

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ class Tooltip {
1919
#content = null
2020
#contentSelector = null
2121
#contentActions = null
22-
#contentClone = false
2322
#containerClassName = null
2423
#position = null
2524
#animated = false
@@ -38,7 +37,6 @@ class Tooltip {
3837
target,
3938
content,
4039
contentSelector,
41-
contentClone,
4240
contentActions,
4341
containerClassName,
4442
position,
@@ -53,7 +51,6 @@ class Tooltip {
5351
this.#target = target
5452
this.#content = content
5553
this.#contentSelector = contentSelector
56-
this.#contentClone = contentClone || false
5754
this.#contentActions = contentActions
5855
this.#containerClassName = containerClassName
5956
this.#position = position || 'top'
@@ -80,7 +77,6 @@ class Tooltip {
8077
update(
8178
content,
8279
contentSelector,
83-
contentClone,
8480
contentActions,
8581
containerClassName,
8682
position,
@@ -101,7 +97,6 @@ class Tooltip {
10197

10298
this.#content = content
10399
this.#contentSelector = contentSelector
104-
this.#contentClone = contentClone || false
105100
this.#contentActions = contentActions
106101
this.#containerClassName = containerClassName
107102
this.#position = position || 'top'
@@ -195,9 +190,9 @@ class Tooltip {
195190
this.#observer
196191
.wait(this.#contentSelector, null, { events: [DOMObserver.EXIST, DOMObserver.ADD] })
197192
.then(({ node }) => {
198-
const child = this.#contentClone ? node.cloneNode(true) : node
193+
const child = node.content ? node.content.firstElementChild : node
199194
child.setAttribute('style', 'position: relative')
200-
this.#tooltip.appendChild(child)
195+
this.#tooltip.appendChild(child.cloneNode(true))
201196
})
202197
} else if (this.#content) {
203198
const child = document.createTextNode(this.#content)

0 commit comments

Comments
 (0)