Skip to content

Commit

Permalink
feat: Allow to set a template element as tooltip content (#30)
Browse files Browse the repository at this point in the history
The `contentClone` prop has been removed since it is not needed anymore
  • Loading branch information
untemps authored Feb 8, 2022
1 parent 70c5d2a commit 5f3e582
Show file tree
Hide file tree
Showing 6 changed files with 265 additions and 304 deletions.
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ yarn add @untemps/svelte-use-tooltip
<div use:useTooltip={{
contentSelector: '.tooltip__content',
contentClone: false,
contentActions: {
'*': {
eventType: 'click',
Expand Down Expand Up @@ -114,7 +113,6 @@ yarn add @untemps/svelte-use-tooltip
|---------------------------|---------|-------------------|-----------------------------------------------------------------------------------------------------------------|
| `content` | string | null | Text content to display in the tooltip. |
| `contentSelector` | string | null | Selector of the content to display in the tooltip. |
| `contentClone` | boolean | false | Flag to clone the content to display in the tooltip. If false, the content is removed from its previous parent. |
| `contentActions` | object | null | Configuration of the tooltip actions (see [Content Actions](#content-actions)). |
| `containerClassName` | string | '__tooltip' | Class name to apply to the tooltip container. |
| `position` | string | 'top' | Position of the tooltip. Available values: 'top', 'bottom', 'left', 'right' |
Expand All @@ -126,6 +124,18 @@ yarn add @untemps/svelte-use-tooltip
| `offset` | number | 10 | Distance between the tooltip and the target in pixels. |
| `disabled` | boolean | false | Flag to disable the tooltip content. |

### Content and Content Selector

The tooltip content can be specified either by the `content` prop or the `contentSelector` prop.

`content` must be a text string that will be displayed as is in the tooltip.

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.

To do so, you may use the `contentSelector` prop that allows to specify the selector of an element from the DOM.

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**.

### Content Actions

The `contentActions` prop allows to handle interactions within the tooltip content.
Expand All @@ -141,7 +151,6 @@ One event by element is possible so far as elements are referenced by selector.
<div use:useTooltip={{
contentSelector: '#content',
contentClone: false,
contentActions: {
'#button1': {
eventType: 'mouseenter',
Expand Down
74 changes: 35 additions & 39 deletions dev/src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@
use:useTooltip={{
position: tooltipPosition,
content: tooltipTextContent,
contentSelector: !tooltipTextContent?.length ? '.tooltip__content' : null,
contentClone: true,
contentSelector: !tooltipTextContent?.length ? '#tooltip-template' : null,
contentActions: {
'*': {
eventType: 'click',
Expand All @@ -40,20 +39,17 @@
enterDelay: tooltipEnterDelay,
leaveDelay: tooltipLeaveDelay,
offset: tooltipOffset,
disabled: isTooltipDisabled
disabled: isTooltipDisabled,
}}
class="target"
>
Hover me
</div>
<template id="tooltip-template">
<span class="tooltip__content">Hi! I'm a <i>fancy</i> <strong>tooltip</strong>!</span>
</template>
<form class="settings__form">
<h1>Settings</h1>
<fieldset>
<label>
Default Tooltip Content:
<span class="tooltip__content">Hi! I'm a <i>fancy</i> <strong>tooltip</strong>!</span>
</label>
</fieldset>
<fieldset>
<label>
Tooltip Text Content:
Expand Down Expand Up @@ -204,36 +200,36 @@
}
:global(.tooltip::after) {
content: '';
position: absolute;
margin-left: -5px;
border-width: 5px;
border-style: solid;
}
:global(.tooltip-top::after) {
bottom: -10px;
left: 50%;
border-color: #ee7008 transparent transparent transparent;
}
:global(.tooltip-bottom::after) {
top: -10px;
left: 50%;
border-color: transparent transparent #ee7008 transparent;
}
:global(.tooltip-left::after) {
top: calc(50% - 5px);
right: -10px;
border-color: transparent transparent transparent #ee7008;
}
:global(.tooltip-right::after) {
top: calc(50% - 5px);
left: -5px;
border-color: transparent #ee7008 transparent transparent;
}
content: '';
position: absolute;
margin-left: -5px;
border-width: 5px;
border-style: solid;
}
:global(.tooltip-top::after) {
bottom: -10px;
left: 50%;
border-color: #ee7008 transparent transparent transparent;
}
:global(.tooltip-bottom::after) {
top: -10px;
left: 50%;
border-color: transparent transparent #ee7008 transparent;
}
:global(.tooltip-left::after) {
top: calc(50% - 5px);
right: -10px;
border-color: transparent transparent transparent #ee7008;
}
:global(.tooltip-right::after) {
top: calc(50% - 5px);
left: -5px;
border-color: transparent #ee7008 transparent transparent;
}
@keyframes fadeIn {
from {
Expand Down
64 changes: 60 additions & 4 deletions jest/jest.setup.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import { fireEvent } from '@testing-library/svelte'

const { toBeInTheDocument, toHaveAttribute, toHaveStyle } = require('@testing-library/jest-dom/matchers')
import '@testing-library/jest-dom/extend-expect'

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

global._createElement = (id = 'foo', parent, attrs) => {
const el = document.createElement('div')
el.setAttribute('id', id)
global._createAndAddElement = (tagName, attrs, parent) => {
const el = document.createElement(tagName)
el.setAttribute('id', 'foo')
for (let key in attrs) {
el.setAttribute(key, attrs[key])
}
;(parent || document.body).appendChild(el)
return el
}

global._removeElement = (selector) => {
global._destroyElement = (selector) => {
const el = document.querySelector(selector)
if (!!el) {
el.parentNode.removeChild(el)
Expand All @@ -27,6 +29,60 @@ global._modifyElement = (selector, attributeName, attributeValue) => {
}
}

global._getElement = (selector) => {
return document.querySelector(selector)
}

global._sleep = (ms = 100) => {
return new Promise((resolve) => setTimeout(resolve, ms))
}

global._enter = async (trigger) =>
new Promise(async (resolve) => {
await fireEvent.mouseOver(trigger) // fireEvent.mouseEnter only works if mouseOver is triggered before
await fireEvent.mouseEnter(trigger)
await _sleep(1)
resolve()
})

global._leave = async (trigger) =>
new Promise(async (resolve) => {
await fireEvent.mouseLeave(trigger)
await _sleep(1)
resolve()
})

global._enterAndLeave = async (trigger) =>
new Promise(async (resolve) => {
await _enter(trigger)
await _leave(trigger)
resolve()
})

global._focus = async (trigger) =>
new Promise(async (resolve) => {
await fireEvent.focusIn(trigger)
await _sleep(1)
resolve()
})

global._blur = async (trigger) =>
new Promise(async (resolve) => {
await fireEvent.focusOut(trigger)
await _sleep(1)
resolve()
})

global._focusAndBlur = async (trigger) =>
new Promise(async (resolve) => {
await _focus(trigger)
await _blur(trigger)
resolve()
})

global._keyDown = async (trigger, key) =>
new Promise(async (resolve) => {
await fireEvent.keyDown(trigger, key || { key: 'Escape', code: 'Escape', charCode: 27 })
await _sleep(1)
resolve()
})
9 changes: 2 additions & 7 deletions src/Tooltip.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ class Tooltip {
#content = null
#contentSelector = null
#contentActions = null
#contentClone = false
#containerClassName = null
#position = null
#animated = false
Expand All @@ -38,7 +37,6 @@ class Tooltip {
target,
content,
contentSelector,
contentClone,
contentActions,
containerClassName,
position,
Expand All @@ -53,7 +51,6 @@ class Tooltip {
this.#target = target
this.#content = content
this.#contentSelector = contentSelector
this.#contentClone = contentClone || false
this.#contentActions = contentActions
this.#containerClassName = containerClassName
this.#position = position || 'top'
Expand All @@ -80,7 +77,6 @@ class Tooltip {
update(
content,
contentSelector,
contentClone,
contentActions,
containerClassName,
position,
Expand All @@ -101,7 +97,6 @@ class Tooltip {

this.#content = content
this.#contentSelector = contentSelector
this.#contentClone = contentClone || false
this.#contentActions = contentActions
this.#containerClassName = containerClassName
this.#position = position || 'top'
Expand Down Expand Up @@ -195,9 +190,9 @@ class Tooltip {
this.#observer
.wait(this.#contentSelector, null, { events: [DOMObserver.EXIST, DOMObserver.ADD] })
.then(({ node }) => {
const child = this.#contentClone ? node.cloneNode(true) : node
const child = node.content ? node.content.firstElementChild : node
child.setAttribute('style', 'position: relative')
this.#tooltip.appendChild(child)
this.#tooltip.appendChild(child.cloneNode(true))
})
} else if (this.#content) {
const child = document.createTextNode(this.#content)
Expand Down
Loading

0 comments on commit 5f3e582

Please sign in to comment.