Showing preview only (579K chars total). Download the full file or copy to clipboard to get everything.
Repository: atomiks/tippyjs
Branch: master
Commit: ad85f6feb79c
Files: 199
Total size: 532.8 KB
Directory structure:
gitextract_flaxrdjs/
├── .config/
│ ├── .prettierignore
│ ├── babel.config.js
│ ├── eslint.config.js
│ ├── jest-puppeteer.config.js
│ ├── jest.config.js
│ └── rollup.config.js
├── .editorconfig
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ ├── config.yml
│ │ └── feature_request.md
│ └── workflows/
│ ├── cd.yml
│ └── ci.yml
├── .gitignore
├── LICENSE
├── MIGRATION_GUIDE.md
├── README.md
├── build/
│ ├── animations/
│ │ ├── perspective-extreme.js
│ │ ├── perspective-subtle.js
│ │ ├── perspective.js
│ │ ├── scale-extreme.js
│ │ ├── scale-subtle.js
│ │ ├── scale.js
│ │ ├── shift-away-extreme.js
│ │ ├── shift-away-subtle.js
│ │ ├── shift-away.js
│ │ ├── shift-toward-extreme.js
│ │ ├── shift-toward-subtle.js
│ │ └── shift-toward.js
│ ├── base-umd.js
│ ├── base.js
│ ├── bundle-umd.js
│ ├── css/
│ │ ├── backdrop.js
│ │ ├── border.js
│ │ ├── svg-arrow.js
│ │ └── tippy.js
│ ├── headless-umd.js
│ ├── headless.js
│ ├── index.js
│ └── themes/
│ ├── light-border.js
│ ├── light.js
│ ├── material.js
│ └── translucent.js
├── headless/
│ └── package.json
├── index.test-d.ts
├── package.json
├── src/
│ ├── _babel.d.ts
│ ├── addons/
│ │ ├── createSingleton.ts
│ │ └── delegate.ts
│ ├── bindGlobalEventListeners.ts
│ ├── browser.ts
│ ├── constants.ts
│ ├── createTippy.ts
│ ├── css.ts
│ ├── dom-utils.ts
│ ├── index.ts
│ ├── plugins/
│ │ ├── animateFill.ts
│ │ ├── followCursor.ts
│ │ ├── inlinePositioning.ts
│ │ └── sticky.ts
│ ├── props.ts
│ ├── scss/
│ │ ├── _mixins.scss
│ │ ├── _vars.scss
│ │ ├── animations/
│ │ │ ├── fade.scss
│ │ │ ├── perspective-extreme.scss
│ │ │ ├── perspective-subtle.scss
│ │ │ ├── perspective.scss
│ │ │ ├── scale-extreme.scss
│ │ │ ├── scale-subtle.scss
│ │ │ ├── scale.scss
│ │ │ ├── shift-away-extreme.scss
│ │ │ ├── shift-away-subtle.scss
│ │ │ ├── shift-away.scss
│ │ │ ├── shift-toward-extreme.scss
│ │ │ ├── shift-toward-subtle.scss
│ │ │ └── shift-toward.scss
│ │ ├── backdrop.scss
│ │ ├── border.scss
│ │ ├── index.scss
│ │ ├── svg-arrow.scss
│ │ └── themes/
│ │ ├── light-border.scss
│ │ ├── light.scss
│ │ ├── material.scss
│ │ └── translucent.scss
│ ├── template.ts
│ ├── types-internal.ts
│ ├── types.ts
│ ├── utils.ts
│ └── validation.ts
├── test/
│ ├── functional/
│ │ ├── border.test.js
│ │ ├── followCursor.test.js
│ │ ├── inlinePositioning.test.js
│ │ ├── sticky.test.js
│ │ └── themes.test.js
│ ├── image-reporter.js
│ ├── integration/
│ │ ├── __snapshots__/
│ │ │ ├── createTippy.test.js.snap
│ │ │ └── props.test.js.snap
│ │ ├── addons/
│ │ │ ├── createSingleton.test.js
│ │ │ └── delegate.test.js
│ │ ├── bindGlobalEventListeners.test.js
│ │ ├── createTippy.test.js
│ │ ├── plugins/
│ │ │ ├── __snapshots__/
│ │ │ │ └── inlinePositioning.test.js.snap
│ │ │ ├── animateFill.test.js
│ │ │ ├── followCursor.test.js
│ │ │ └── inlinePositioning.test.js
│ │ └── props.test.js
│ ├── setup.js
│ ├── unit/
│ │ ├── __snapshots__/
│ │ │ ├── props.test.js.snap
│ │ │ └── tippy.test.js.snap
│ │ ├── css.test.js
│ │ ├── dom-utils.test.js
│ │ ├── props.test.js
│ │ ├── tippy.test.js
│ │ ├── utils.test.js
│ │ └── validation.test.js
│ ├── utils.js
│ └── visual/
│ ├── index.css
│ ├── index.html
│ ├── index.js
│ └── tests.js
├── tsconfig.json
└── website/
├── .eslintignore
├── .gitignore
├── LICENSE
├── README.md
├── gatsby-config.js
├── gatsby-node.js
├── package.json
├── scripts/
│ ├── should-deploy-docs.js
│ └── should-deploy-docs.sh
└── src/
├── components/
│ ├── ElasticScroll.js
│ ├── Footer.js
│ ├── Framework.js
│ ├── Header.js
│ ├── Icon.js
│ ├── Image.js
│ ├── Layout.js
│ ├── Main.js
│ ├── MiniHeader.js
│ ├── Nav.js
│ ├── NavButtons.js
│ ├── PluginIcon.js
│ ├── RenderIcon.js
│ ├── SEO.js
│ ├── TextGradient.js
│ ├── Tippy.js
│ ├── TippyTransition.js
│ └── examples/
│ ├── Ajax.js
│ ├── ContextMenu.js
│ ├── Dropdown.js
│ ├── EventDelegation.js
│ ├── ImageTransition.js
│ ├── Nesting.js
│ ├── Singleton.js
│ ├── TextTransition.js
│ ├── TriggerTarget.js
│ └── mouseRestPlugin.js
├── css/
│ ├── index.js
│ └── theme.js
├── hooks/
│ └── index.js
├── pages/
│ ├── .prettierrc.json
│ ├── 404.js
│ ├── index.mdx
│ ├── v5/
│ │ ├── accessibility.mdx
│ │ ├── addons.mdx
│ │ ├── ajax.mdx
│ │ ├── all-props.mdx
│ │ ├── animations.mdx
│ │ ├── creating-tooltips.mdx
│ │ ├── customizing-tooltips.mdx
│ │ ├── faq.mdx
│ │ ├── getting-started.mdx
│ │ ├── html-content.mdx
│ │ ├── lifecycle-hooks.mdx
│ │ ├── methods.mdx
│ │ ├── misc.mdx
│ │ ├── motivation.mdx
│ │ ├── plugins.mdx
│ │ ├── themes.mdx
│ │ └── tippy-instance.mdx
│ └── v6/
│ ├── accessibility.mdx
│ ├── addons.mdx
│ ├── ajax.mdx
│ ├── all-props.mdx
│ ├── animations.mdx
│ ├── browser-support.mdx
│ ├── constructor.mdx
│ ├── customization.mdx
│ ├── faq.mdx
│ ├── getting-started.mdx
│ ├── headless-tippy.mdx
│ ├── html-content.mdx
│ ├── methods.mdx
│ ├── misc.mdx
│ ├── motivation.mdx
│ ├── plugins.mdx
│ ├── themes.mdx
│ └── tippy-instance.mdx
└── utils.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .config/.prettierignore
================================================
../build
../dist
../headless
../website/public
../website/.cache
../animations
../themes
================================================
FILE: .config/babel.config.js
================================================
module.exports = {
presets: [
['@babel/env', {loose: true, useBuiltIns: 'entry', corejs: 3}],
'@babel/typescript',
],
plugins: ['dev-expression'],
env: {
test: {
presets: [
['@babel/env', {targets: {node: 'current'}}],
'@babel/typescript',
],
},
},
};
================================================
FILE: .config/eslint.config.js
================================================
module.exports = {
env: {
browser: true,
node: true,
jest: true,
es6: true,
},
globals: {
__DEV__: true,
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
'prettier/@typescript-eslint',
],
plugins: ['@typescript-eslint'],
parser: '@typescript-eslint/parser',
parserOptions: {
project: './tsconfig.json',
},
rules: {
'no-prototype-builtins': 'off',
'@typescript-eslint/no-use-before-define': ['error', {functions: false}],
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/array-type': 'off',
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/ban-ts-ignore': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
},
ignorePatterns: [
'node_modules',
'build',
'animations',
'themes',
'test',
'headless',
'website',
'dist',
'coverage',
],
};
================================================
FILE: .config/jest-puppeteer.config.js
================================================
require('dotenv').config();
const getConfig = require('jest-puppeteer-docker/lib/config');
const baseConfig = getConfig();
module.exports = {
browser: 'chromium',
launch: {
dumpio: false,
headless: process.env.HEADLESS !== 'false',
args: ['--no-sandbox', '--disable-setuid-sandbox'],
},
server: {
command: 'yarn build:visual && yarn serve',
port: 5000,
launchTimeout: 20000,
},
...baseConfig,
};
================================================
FILE: .config/jest.config.js
================================================
// https://github.com/smooth-code/jest-puppeteer/issues/160#issuecomment-491975158
process.env.JEST_PUPPETEER_CONFIG = require.resolve(
'./jest-puppeteer.config.js'
);
const jestPuppeteerDocker = require('jest-puppeteer-docker/jest-preset');
module.exports = {
testMatch: ['<rootDir>/test/**/*.test.js'],
testTimeout: 30000,
globals: {
__DEV__: true,
},
setupFiles: ['dotenv/config'],
reporters: ['default', require.resolve('../test/image-reporter.js')],
...jestPuppeteerDocker,
testEnvironment: 'jest-environment-jsdom-fourteen',
setupFilesAfterEnv: [
require.resolve('../test/setup.js'),
...jestPuppeteerDocker.setupFilesAfterEnv,
],
};
================================================
FILE: .config/rollup.config.js
================================================
import babel from 'rollup-plugin-babel';
import json from 'rollup-plugin-json';
import resolve from 'rollup-plugin-node-resolve';
import cssOnly from 'rollup-plugin-css-only';
import replace from 'rollup-plugin-replace';
import sass from 'rollup-plugin-sass';
import serve from 'rollup-plugin-serve';
import livereload from 'rollup-plugin-livereload';
import {terser} from 'rollup-plugin-terser';
import pkg from '../package.json';
const NAMESPACE_PREFIX = process.env.NAMESPACE || 'tippy';
const plugins = {
babel: babel({extensions: ['.js', '.ts']}),
replaceNamespace: replace({
__NAMESPACE_PREFIX__: NAMESPACE_PREFIX,
}),
replaceEnvProduction: replace({
'process.env.NODE_ENV': JSON.stringify('production'),
}),
replaceEnvDevelopment: replace({
'process.env.NODE_ENV': JSON.stringify('development'),
}),
minify: terser(),
resolve: resolve({extensions: ['.js', '.ts']}),
css: cssOnly({output: false}),
json: json(),
};
const prodCommonPlugins = [
plugins.replaceNamespace,
plugins.resolve,
plugins.json,
];
const pluginConfigs = {
base: [plugins.babel, ...prodCommonPlugins],
bundle: [plugins.babel, ...prodCommonPlugins, plugins.css],
umdBase: [plugins.babel, plugins.replaceEnvDevelopment, ...prodCommonPlugins],
umdBaseMin: [
plugins.babel,
plugins.replaceEnvProduction,
...prodCommonPlugins,
plugins.minify,
],
umdBundle: [
plugins.babel,
plugins.replaceEnvDevelopment,
...prodCommonPlugins,
plugins.css,
],
umdBundleMin: [
plugins.babel,
plugins.replaceEnvProduction,
...prodCommonPlugins,
plugins.minify,
plugins.css,
],
};
const banner = `/**!
* tippy.js v${pkg.version}
* (c) 2017-${new Date().getFullYear()} atomiks
* MIT License
*/`;
const commonUMDOutputOptions = {
globals: {'@popperjs/core': 'Popper'},
format: 'umd',
name: 'tippy',
sourcemap: true,
};
const prodConfig = [
{
input: 'build/base-umd.js',
plugins: pluginConfigs.umdBase,
external: ['@popperjs/core'],
output: {
...commonUMDOutputOptions,
file: 'dist/tippy.umd.js',
banner,
},
},
{
input: 'build/bundle-umd.js',
plugins: pluginConfigs.umdBundle,
external: ['@popperjs/core'],
output: {
...commonUMDOutputOptions,
file: 'dist/tippy-bundle.umd.js',
banner,
},
},
{
input: 'build/base-umd.js',
plugins: pluginConfigs.umdBaseMin,
external: ['@popperjs/core'],
output: {
...commonUMDOutputOptions,
file: 'dist/tippy.umd.min.js',
},
},
{
input: 'build/bundle-umd.js',
plugins: pluginConfigs.umdBundleMin,
external: ['@popperjs/core'],
output: {
...commonUMDOutputOptions,
file: 'dist/tippy-bundle.umd.min.js',
},
},
{
input: 'build/base.js',
plugins: pluginConfigs.bundle,
external: ['@popperjs/core'],
output: {
file: 'dist/tippy.esm.js',
format: 'esm',
banner,
sourcemap: true,
},
},
{
input: 'build/headless.js',
plugins: pluginConfigs.base,
external: ['@popperjs/core'],
output: {
file: 'headless/dist/tippy-headless.esm.js',
format: 'esm',
banner,
sourcemap: true,
},
},
{
input: 'build/base.js',
plugins: pluginConfigs.bundle,
external: ['@popperjs/core'],
output: {
file: 'dist/tippy.cjs.js',
format: 'cjs',
exports: 'named',
banner,
sourcemap: true,
},
},
{
input: 'build/headless.js',
plugins: pluginConfigs.base,
external: ['@popperjs/core'],
output: {
file: 'headless/dist/tippy-headless.cjs.js',
format: 'cjs',
exports: 'named',
banner,
sourcemap: true,
},
},
{
input: 'build/headless-umd.js',
plugins: pluginConfigs.umdBase,
external: ['@popperjs/core'],
output: {
...commonUMDOutputOptions,
file: 'headless/dist/tippy-headless.umd.js',
},
},
{
input: 'build/headless-umd.js',
plugins: pluginConfigs.umdBaseMin,
external: ['@popperjs/core'],
output: {
...commonUMDOutputOptions,
file: 'headless/dist/tippy-headless.umd.min.js',
},
},
];
// Calling the `serve()` plugin causes the process to hang, so we need to delay
// its evaluation
const configs = {
dev: () => ({
input: 'test/visual/tests.js',
plugins: [
plugins.babel,
plugins.json,
plugins.resolve,
replace({__DEV__: 'true'}),
plugins.replaceEnvDevelopment,
sass({output: true}),
serve({
contentBase: 'test/visual',
port: 1234,
}),
livereload(),
],
output: {
file: 'test/visual/dist/bundle.js',
format: 'iife',
},
}),
test: () => ({
input: 'test/visual/tests.js',
plugins: [
plugins.babel,
plugins.json,
plugins.resolve,
replace({__DEV__: 'true'}),
plugins.replaceEnvDevelopment,
sass({output: true}),
],
output: {
file: 'test/visual/dist/bundle.js',
format: 'iife',
},
}),
};
const func = configs[process.env.NODE_ENV];
export default func ? func() : prodConfig;
================================================
FILE: .editorconfig
================================================
# https://editorconfig.org
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 2
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: [atomiks]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: 🐞 Bug report
about: Something is broken
title: ''
labels: "\U0001F41B bug, \U0001F6A7 unconfirmed"
assignees: ''
---
## Bug description
A clear and concise description of what the bug is.
## Reproduction
<!-- Please create a CodePen to reproduce the bug. It can be difficult to understand the problem otherwise. It should be reproduced exclusively using Tippy.js on CodePen. -->
CodePen link: https://codepen.io/atomiks/pen/yvwQyZ
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
- name: 👋 Stack Overflow
url: https://stackoverflow.com/questions/tagged/tippyjs
about: Having trouble with Tippy? Try asking on Stack Overflow!
- name: 💬 Discussions
url: https://github.com/atomiks/tippyjs/discussions
about: Talk with others about Tippy, its usage and future!
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: ✨ Feature request
about: Suggest a feature
title: ''
labels: "\U0001F48E enhancement"
assignees: ''
---
## Problem
A clear and concise description of what the problem is.
## Solution
A clear and concise description of what you want to happen.
================================================
FILE: .github/workflows/cd.yml
================================================
name: CD
on: [push]
env:
CI: true
jobs:
publish:
if:
${{ startsWith(github.event.commits[0].message, 'docs:') ||
startsWith(github.event.commits[0].message, 'release:') }}
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./website
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Build
run: |
npm install
npm run clean
npm run build:ci
- name: Deploy to GitHub Pages
uses: crazy-max/ghaction-github-pages@v2
with:
target_branch: gh-pages
build_dir: ./website/public
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on: [push, pull_request]
env:
CI: true
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true
jobs:
checks:
name: Linting and Type checking
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
- run: npm install
- run: npm run lint
- run: npm run test:types
dom-tests:
name: Unit and Integration
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
- uses: mujo-code/puppeteer-headful@master
- run: npm install
- run: npm run test:dom
functional-tests:
name: Chromium Functional
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
- uses: mujo-code/puppeteer-headful@master
- run: npm install
- run: npm run test:functional
env:
PUPPETEER_BROWSER: chromium
================================================
FILE: .gitignore
================================================
.DS_Store
coverage/
.devserver/
node_modules/
dist/
/themes
/animations
index.d.ts
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2017-present atomiks
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: MIGRATION_GUIDE.md
================================================
# Migration Guide
- [5.x to 6.x](#5x-to-6x)
- [4.x to 5.x](#4x-to-5x)
---
# 5.x to 6.x
Popper.js was updated to v2. The `instance.popperInstance` and `popperOptions`
APIs have changed. You can
[view its documentation here](https://popper.js.org/docs/v2/).
## Imports
`iife` was replaced with `umd` (Rollup chunking has been removed).
## HTML Content
To protect against XSS by default, `allowHTML` is now `false` by default. If
you're passing strings of HTML to `content`, you must set `allowHTML: true`.
## Themes
### If you were creating custom themes
<details>
<summary>View details</summary>
`.tippy-tooltip` has become `.tippy-box`, and theming is done via an attribute
now, to match the other props.
The following:
```css
.tippy-tooltip.tomato-theme {
background-color: tomato;
}
```
Has become:
```css
.tippy-box[data-theme~='tomato'] {
background-color: tomato;
}
```
> The `~=` attribute operator allows you to specify mutliple theme names like
> before with class names.
For `.tippy-arrow`, you'll need to specify its color on the `::before`
pseudo-element.
The following:
```css
.tippy-tooltip.tomato-theme[data-placement^='top'] > .tippy-arrow {
border-top-color: tomato;
}
```
Has become:
```css
.tippy-box[data-theme~='tomato'][data-placement^='top'] > .tippy-arrow::before {
border-top-color: tomato;
}
```
In addition, if you were altering the pixel values, it may need to be adjusted.
In addition, Popper 2 changed some attribute names.
This:
```css
.tippy-tooltip[data-out-of-boundaries] {
visibility: hidden;
}
```
Has become:
```css
.tippy-box[data-reference-hidden] {
visibility: hidden;
}
```
</details>
### If you were targeting `.tippy-popper`
<details>
<summary>View details</summary>
`.tippy-popper` is no longer a selector, and is considered an implementation
detail for the most part now. `[data-tippy-root]` attribute selector replaces it
if necessary.
</details>
## Instance
### If you were using `instance.popperChildren`
<details>
<summary>View details</summary>
This no longer exists due to the user's ability to specify any structured DOM
with `render()` (Headless Tippy).
To access the `.tippy-box` element with the default render function
(`.tippy-tooltip` in v5):
```js
const box = instance.popper.firstElementChild;
```
</details>
## Methods
### If you were using `.show()` or `.hide()` with a duration argument
<details>
<summary>View details</summary>
These no longer take a duration argument. Instead, use
`.setProps({duration: ...})` before calling them if necessary.
To replicate `.hide(0)`:
```js
instance.unmount();
```
</details>
## Props
### If you were using `boundary`
<details>
<summary>View details</summary>
Often, this was to solve a problem in Popper 1, where you set
`boundary: "window"`. This is no longer necessary. If you'd like to change it
anyway, you can set it in `popperOptions`:
```js
tippy(targets, {
popperOptions: {
modifiers: [
{
name: 'preventOverflow',
options: {
// equivalent to boundary: 'window' in v1, usually NOT necessary in v2
rootBoundary: 'document',
},
},
],
},
});
```
</details>
### If you were using `distance` or `offset`
<details>
<summary>View details</summary>
These have been merged into a single `offset` prop, to match Popper 2's new API.
The following:
```js
tippy(targets, {
offset: 5,
distance: 10,
});
```
Has become:
```js
tippy(targets, {
offset: [5, 10],
});
```
This tuple also directly replaces `offset: "5, 10"`.
</details>
### If you were using `flip`, `flipBehavior`, or `flipOnUpdate`
<details>
<summary>View details</summary>
All of these have been removed. To configure these, specify them in
`popperOptions`:
```js
tippy(targets, {
placement: 'bottom',
popperOptions: {
modifiers: [
{
name: 'flip',
// flip: false
enabled: false,
options: {
// flipBehavior: ['bottom', 'right', 'top']
fallbackPlacements: ['right', 'top'],
},
},
],
},
});
```
`flipOnUpdate` has no replacement yet. It's always `true`.
</details>
### If you were using `aria`
<details>
<summary>View details</summary>
This has become an object to allow for better configurability. By default Tippy
will infer what to use (`auto`), but this can be overridden.
Types:
```ts
interface Props {
// ...
aria: {
content?: 'auto' | 'describedby' | 'labelledby' | null;
expanded?: 'auto' | boolean;
};
}
```
```js
tippy(targets, {
aria: {
// `null` when interactive is enabled
content: 'auto', // `aria-*` attribute
// `true` when interactive is enabled
expanded: 'auto', // `aria-expanded` attribute
},
});
```
</details>
### If you were using `multiple` or relying on its behavior
<details>
<summary>View details</summary>
Due to static typing issues, it's been removed. Calling `tippy()` again on the
same element will now always create a new tippy for it. Avoid calling `tippy()`
multiple times on the same reference if you don't want multiple tippies created
for it.
</details>
### If you were using `updateDuration`
<details>
<summary>View details</summary>
It's now `moveTransition`, which is a whole transition string. This allows you
to specify the easing function.
```js
tippy(targets, {
moveTransition: 'transform 0.2s ease-out',
});
```
</details>
### If you were using `lazy`
<details>
<summary>View details</summary>
It's been removed. The `popperInstance` is now created and destroyed on
mount/unmount. If you were using this for ReferenceObjects, see below.
The following:
```js
tippy(targets, {
lazy: false,
onCreate(instance) {
instance.popperInstance.reference = {
clientWidth: 0,
clientHeight: 0,
getBoundingClientRect() {
return {
// ...
};
},
};
},
});
```
Has become a single prop:
```js
tippy(targets, {
getReferenceClientRect: () => ({
// ...
}),
});
```
This implements Popper 2's
[Virtual Elements API](https://popper.js.org/docs/v2/virtual-elements/).
</details>
## IE11
IE11 is not supported by default anymore, but can be polyfilled. View the
Browser Support page on the documentation for details.
---
# 4.x to 5.x
### Node
Make sure you have DEV warnings enabled by setting `NODE_ENV=development` and
ensuring your bundler replaces `process.env.NODE_ENV` with the string
`"development"`.
- **webpack**: via `mode` option
- **Rollup**: via `rollup-plugin-replace`
- **Parcel**: automatic
- **Browserify/Gulp/Grunt/others**:
[View details](https://vuejs.org/v2/guide/deployment.html#With-Build-Tools)
### Browser
```html
<script src="https://unpkg.com/popper.js@1"></script>
<!-- Specify development file -->
<script src="https://unpkg.com/tippy.js@5/dist/tippy-bundle.iife.js"></script>
<!--
When you're finished, you can remove everything after @5
(or when deploying for production)
<script src="https://unpkg.com/tippy.js@5"></script>
-->
```
## Imports
Previously, the default import injected the CSS stylesheet into `<head>`:
```js
import tippy from 'tippy.js';
```
In v5, this import is now side-effect free to work better with dependencies when
users have CSP enabled or using frameworks that control the `<head>`.
You should import the CSS separately:
```js
import tippy from 'tippy.js';
import 'tippy.js/dist/tippy.css';
```
You can however opt-in to use the injected CSS version, just like v4:
```js
// Just like v4
import tippy from 'tippy.js/dist/tippy-bundle.esm';
// Or CommonJS:
const tippy = require('tippy.js/dist/tippy-bundle.cjs');
```
## `data-tippy` attribute
This technique of auto initialization was removed to keep the import side-effect
free. Initializing via the `tippy()` constructor is required.
## Animations
### If you want the `animateFill` effect back (it's no longer default)
<details>
<summary>View details</summary>
Node:
```js
import tippy, {animateFill} from 'tippy.js';
import 'tippy.js/dist/tippy.css';
// These stylesheets are required for it to work
import 'tippy.js/dist/backdrop.css';
import 'tippy.js/animations/shift-away.css';
tippy(targets, {
animateFill: true,
plugins: [animateFill],
});
```
Browser:
```html
<link rel="stylesheet" href="https://unpkg.com/tippy.js@5/dist/backdrop.css" />
<link
rel="stylesheet"
href="https://unpkg.com/tippy.js@5/animations/shift-away.css"
/>
<script src="https://unpkg.com/popper.js@1"></script>
<script src="https://unpkg.com/tippy.js@5"></script>
<script>
tippy(targets, {
content: 'tooltip',
animateFill: true,
});
</script>
```
</details>
### If you were using default animations or creating custom animations
<details>
<summary>View details</summary>
- Make sure your `visible` state has no translation (of 0px, instead of 10px
like before).
- `shift-away`, `shift-toward`, `scale` and `perspective` need to be imported
separately now.
Node:
```js
import 'tippy.js/animations/scale.css';
```
Browser:
```html
<link
rel="stylesheet"
href="https://unpkg.com/tippy.js@5/animations/scale.css"
/>
```
</details>
## Props
### If you were using `interactive: true`
<details>
<summary>View details</summary>
When using `interactive: true`, the tippy may be invisible or appear cut off if
your reference element is in a container with:
- `position` (e.g. fixed, absolute, sticky)
- `overflow: hidden`
To fix add the following prop (recommended):
```js
tippy(reference, {
// ...
popperOptions: {
positionFixed: true,
},
});
```
Or, if the above causes issues:
```js
tippy(reference, {
// ...
appendTo: document.body,
});
```
⚠️ For the latter, you need to be employing focus mangement for accessibility.
</details>
### If you were using `arrowType: 'round'`
<details>
<summary>View details</summary>
Import the `svg-arrow` CSS, and the `roundArrow` string, and use the `arrow`
prop instead.
Node:
```js
import {roundArrow} from 'tippy.js';
import 'tippy.js/dist/svg-arrow.css';
tippy(targets, {
arrow: roundArrow,
});
```
Browser:
```html
<link rel="stylesheet" href="https://unpkg.com/tippy.js@5/dist/svg-arrow.css" />
<script>
tippy(targets, {
arrow: tippy.roundArrow,
});
</script>
```
</details>
### If you were using `followCursor`
<details>
<summary>View details</summary>
Node:
```js
import tippy, {followCursor} from 'tippy.js';
tippy('button', {
followCursor: true,
plugins: [followCursor],
});
```
Browser:
(Works as before.)
</details>
### If you were using `sticky`
<details>
<summary>View details</summary>
Node:
```js
import tippy, {sticky} from 'tippy.js';
tippy('button', {
sticky: true,
plugins: [sticky],
});
```
Browser:
(Works as before.)
</details>
### If you were using `target`
<details>
<summary>View details</summary>
Use `delegate()`.
Node:
```js
import tippy, {delegate} from 'tippy.js';
delegate('#parent', {target: 'button'});
```
Browser:
```html
<script src="https://unpkg.com/popper.js@1"></script>
<script src="https://unpkg.com/tippy.js@5"></script>
<script>
tippy.delegate('#parent', {target: 'button'});
</script>
```
</details>
### If you were using `showOnInit`
<details>
<summary>View details</summary>
It's now named `showOnCreate`, to match the `onCreate` lifecycle hook
</details>
### If you were using `size`
<details>
<summary>View details</summary>
It's been removed, as it's more flexible to just use a theme and specify the
`font-size` and `padding` properties.
</details>
### If you were using `touchHold`
<details>
<summary>View details</summary>
Use `touch: "hold"` instead.
</details>
### If you were using `a11y`
<details>
<summary>View details</summary>
Ensure non-focusable elements have `tabindex="0"` added to them. Otherwise, use
natively focusable elements everywhere possible.
</details>
### If you were using `wait`
<details>
<summary>View details</summary>
Use the `onTrigger` and `onUntrigger` lifecycles and temporarily disable the
instance.
```js
tippy(targets, {
onTrigger(instance) {
instance.disable();
// Make your async call...
// Once finished:
instance.enable();
instance.show();
},
onUntrigger(instance) {
// Re-enable the instance here depending on the async cancellation logic
instance.enable();
},
});
```
</details>
## Instances
### If you were using `instance.set()`
<details>
<summary>View details</summary>
```diff
- instance.set({});
+ instance.setProps({});
```
</details>
## Static methods
### If you were using `tippy.setDefaults()`
<details>
<summary>View details</summary>
```diff
- tippy.defaults;
+ tippy.defaultProps;
```
```diff
- tippy.setDefaults({});
+ tippy.setDefaultProps({});
```
</details>
### If you were using `tippy.hideAll()`
<details>
<summary>View details</summary>
In ESM/CJS contexts, it's no longer attached to `tippy`
Node:
```js
import {hideAll} from 'tippy.js';
hideAll();
```
Browser:
(Works as before.)
</details>
### If you were using `tippy.group()`
<details>
<summary>View details</summary>
Use `createSingleton()`.
Node:
```js
import tippy, {createSingleton} from 'tippy.js';
createSingleton(tippy('button'), {delay: 1000});
```
Browser:
```html
<script src="https://unpkg.com/popper.js@1"></script>
<script src="https://unpkg.com/tippy.js@5"></script>
<script>
tippy.createSingleton(tippy('button'), {delay: 1000});
</script>
```
</details>
## Themes
### If you were using the included themes
<details>
<summary>View details</summary>
- `google` is now `material`
</details>
### If you were creating custom themes
<details>
<summary>View details</summary>
- `[x-placement]` attribute is now `[data-placement]`
- `[x-out-of-boundaries]` is now `[data-out-of-boundaries]`
- `.tippy-roundarrow` is now `.tippy-svg-arrow`
- `.tippy-tooltip` no longer has `padding` on it, rather the `.tippy-content`
selector does.
- `.tippy-tooltip` no longer has `text-align: center`
</details>
## Other
### If you were using virtual reference objects
<details>
<summary>View details</summary>
Set `instance.popperInstance.reference = ReferenceObject` in the `onTrigger`
lifecycle, or `onCreate` with `lazy: false`.
</details>
## Types
<details>
<summary>View details</summary>
- `Props` is not `Partial` anymore, it's `Required`
- `Options` removed (use `Partial<Props>`)
- `BasicPlacement` renamed to `BasePlacement`
</details>
================================================
FILE: README.md
================================================
<div align="center">
<img alt="Tippy.js logo" src="https://github.com/atomiks/tippyjs/raw/master/logo.png" height="117" />
</div>
<div align="center">
<h1>Tippy.js</h1>
<p>The complete tooltip, popover, dropdown, and menu solution for the web</p>
<a href="https://www.npmjs.com/package/tippy.js">
<img src="https://img.shields.io/npm/dm/tippy.js.svg?color=%235599ff&style=for-the-badge" alt="npm Downloads per Month">
<a>
<a href="https://github.com/atomiks/tippyjs/blob/master/LICENSE">
<img src="https://img.shields.io/npm/l/tippy.js.svg?color=%23c677cf&style=for-the-badge" alt="MIT License">
</a>
<br>
<br>
</div>
## Demo and Documentation
➡️ **[View the latest demo & docs here](https://atomiks.github.io/tippyjs/)**
[Migration Guide](https://github.com/atomiks/tippyjs/blob/master/MIGRATION_GUIDE.md)
## Installation
### Package Managers
```bash
# npm
npm i tippy.js
# Yarn
yarn add tippy.js
```
Import the `tippy` constructor and the core CSS:
```js
import tippy from 'tippy.js';
import 'tippy.js/dist/tippy.css';
```
### CDN
```html
<script src="https://unpkg.com/@popperjs/core@2"></script>
<script src="https://unpkg.com/tippy.js@6"></script>
```
The core CSS comes bundled with the default unpkg import.
## Usage
For detailed usage information,
[visit the docs](https://atomiks.github.io/tippyjs/v6/getting-started/).
## Component Wrappers
- React: [@tippyjs/react](https://github.com/atomiks/tippyjs-react) (official)
- Ember: [ember-tippy](https://github.com/nag5000/ember-tippy) (unofficial)
## License
MIT
================================================
FILE: build/animations/perspective-extreme.js
================================================
import '../../src/scss/animations/perspective-extreme.scss';
================================================
FILE: build/animations/perspective-subtle.js
================================================
import '../../src/scss/animations/perspective-subtle.scss';
================================================
FILE: build/animations/perspective.js
================================================
import '../../src/scss/animations/perspective.scss';
================================================
FILE: build/animations/scale-extreme.js
================================================
import '../../src/scss/animations/scale-extreme.scss';
================================================
FILE: build/animations/scale-subtle.js
================================================
import '../../src/scss/animations/scale-subtle.scss';
================================================
FILE: build/animations/scale.js
================================================
import '../../src/scss/animations/scale.scss';
================================================
FILE: build/animations/shift-away-extreme.js
================================================
import '../../src/scss/animations/shift-away-extreme.scss';
================================================
FILE: build/animations/shift-away-subtle.js
================================================
import '../../src/scss/animations/shift-away-subtle.scss';
================================================
FILE: build/animations/shift-away.js
================================================
import '../../src/scss/animations/shift-away.scss';
================================================
FILE: build/animations/shift-toward-extreme.js
================================================
import '../../src/scss/animations/shift-toward-extreme.scss';
================================================
FILE: build/animations/shift-toward-subtle.js
================================================
import '../../src/scss/animations/shift-toward-subtle.scss';
================================================
FILE: build/animations/shift-toward.js
================================================
import '../../src/scss/animations/shift-toward.scss';
================================================
FILE: build/base-umd.js
================================================
import tippy, {hideAll} from '../src';
import createSingleton from '../src/addons/createSingleton';
import delegate from '../src/addons/delegate';
import animateFill from '../src/plugins/animateFill';
import followCursor from '../src/plugins/followCursor';
import inlinePositioning from '../src/plugins/inlinePositioning';
import sticky from '../src/plugins/sticky';
import {ROUND_ARROW} from '../src/constants';
import {render} from '../src/template';
tippy.setDefaultProps({
plugins: [animateFill, followCursor, inlinePositioning, sticky],
render,
});
tippy.createSingleton = createSingleton;
tippy.delegate = delegate;
tippy.hideAll = hideAll;
tippy.roundArrow = ROUND_ARROW;
export default tippy;
================================================
FILE: build/base.js
================================================
import tippy from '../src';
import {render} from '../src/template';
tippy.setDefaultProps({render});
export {default, hideAll} from '../src';
export {default as createSingleton} from '../src/addons/createSingleton';
export {default as delegate} from '../src/addons/delegate';
export {default as animateFill} from '../src/plugins/animateFill';
export {default as followCursor} from '../src/plugins/followCursor';
export {default as inlinePositioning} from '../src/plugins/inlinePositioning';
export {default as sticky} from '../src/plugins/sticky';
export {ROUND_ARROW as roundArrow} from '../src/constants';
================================================
FILE: build/bundle-umd.js
================================================
import css from '../dist/tippy.css';
import {injectCSS} from '../src/css';
import {isBrowser} from '../src/browser';
import tippy, {hideAll} from '../src';
import createSingleton from '../src/addons/createSingleton';
import delegate from '../src/addons/delegate';
import animateFill from '../src/plugins/animateFill';
import followCursor from '../src/plugins/followCursor';
import inlinePositioning from '../src/plugins/inlinePositioning';
import sticky from '../src/plugins/sticky';
import {ROUND_ARROW} from '../src/constants';
import {render} from '../src/template';
if (isBrowser) {
injectCSS(css);
}
tippy.setDefaultProps({
plugins: [animateFill, followCursor, inlinePositioning, sticky],
render,
});
tippy.createSingleton = createSingleton;
tippy.delegate = delegate;
tippy.hideAll = hideAll;
tippy.roundArrow = ROUND_ARROW;
export default tippy;
================================================
FILE: build/css/backdrop.js
================================================
import '../../src/scss/backdrop.scss';
================================================
FILE: build/css/border.js
================================================
import '../../src/scss/border.scss';
================================================
FILE: build/css/svg-arrow.js
================================================
import '../../src/scss/svg-arrow.scss';
================================================
FILE: build/css/tippy.js
================================================
import '../../src/scss/index.scss';
================================================
FILE: build/headless-umd.js
================================================
import tippy, {hideAll} from '../src';
import createSingleton from '../src/addons/createSingleton';
import delegate from '../src/addons/delegate';
import animateFill from '../src/plugins/animateFill';
import followCursor from '../src/plugins/followCursor';
import inlinePositioning from '../src/plugins/inlinePositioning';
import sticky from '../src/plugins/sticky';
import {ROUND_ARROW} from '../src/constants';
tippy.setDefaultProps({
plugins: [animateFill, followCursor, inlinePositioning, sticky],
animation: false,
});
tippy.createSingleton = createSingleton;
tippy.delegate = delegate;
tippy.hideAll = hideAll;
tippy.roundArrow = ROUND_ARROW;
export default tippy;
================================================
FILE: build/headless.js
================================================
import tippy from '../src';
export {hideAll} from '../src';
export {default as createSingleton} from '../src/addons/createSingleton';
export {default as delegate} from '../src/addons/delegate';
export {default as animateFill} from '../src/plugins/animateFill';
export {default as followCursor} from '../src/plugins/followCursor';
export {default as inlinePositioning} from '../src/plugins/inlinePositioning';
export {default as sticky} from '../src/plugins/sticky';
export {ROUND_ARROW as roundArrow} from '../src/constants';
tippy.setDefaultProps({animation: false});
export default tippy;
================================================
FILE: build/index.js
================================================
// This file builds the CSS dist files. The main `rollup.config.js` builds the
// JS dist files.
/* eslint-disable @typescript-eslint/no-var-requires */
const fs = require('fs');
const {rollup} = require('rollup');
const babel = require('rollup-plugin-babel');
const sass = require('rollup-plugin-sass');
const postcss = require('postcss');
const autoprefixer = require('autoprefixer');
const cssnano = require('cssnano');
const resolve = require('rollup-plugin-node-resolve');
const json = require('rollup-plugin-json');
const cssOnly = require('rollup-plugin-css-only');
const replace = require('rollup-plugin-replace');
const NAMESPACE_PREFIX = process.env.NAMESPACE || 'tippy';
const THEME = process.env.THEME;
const BASE_OUTPUT_CONFIG = {
name: 'tippy',
globals: {'popper.js': 'Popper'},
sourcemap: true,
};
const PLUGINS = {
babel: babel({
exclude: 'node_modules/**',
extensions: ['.js', '.ts'],
}),
replaceNamespace: replace({
__NAMESPACE_PREFIX__: NAMESPACE_PREFIX,
}),
resolve: resolve({extensions: ['.js', '.ts']}),
css: cssOnly({output: false}),
json: json(),
};
const PLUGIN_CONFIG = [
PLUGINS.babel,
PLUGINS.replaceNamespace,
PLUGINS.resolve,
PLUGINS.json,
PLUGINS.css,
];
function createPluginSCSS(output, shouldInjectNodeEnvTheme = false) {
let data = `$namespace-prefix: ${NAMESPACE_PREFIX};`;
if (shouldInjectNodeEnvTheme && THEME) {
data += `@import './themes/${THEME}.scss';`;
}
return sass({
output,
options: {data},
processor(css) {
return postcss([autoprefixer, cssnano])
.process(css, {from: undefined})
.then((result) => result.css);
},
});
}
function createRollupConfig(inputFile, plugins) {
return {
input: `./build/${inputFile}`,
external: ['popper.js'],
plugins,
};
}
async function build() {
// Create `index.d.ts` file from `src/types.ts`
fs.copyFileSync('./src/types.ts', './index.d.ts');
// Create base CSS files
for (const filename of fs.readdirSync(`./build/css`)) {
const cssConfig = createRollupConfig(
`css/${filename}`,
PLUGIN_CONFIG.concat(
createPluginSCSS(`./dist/${filename.replace('.js', '.css')}`, true)
)
);
const cssBundle = await rollup(cssConfig);
await cssBundle.write({
...BASE_OUTPUT_CONFIG,
sourcemap: false,
format: 'umd',
file: './index.js',
});
}
// Themes + animations
for (const folder of ['themes', 'animations']) {
for (const filename of fs.readdirSync(`./build/${folder}`)) {
const filenameWithCSSExtension = filename.replace('.js', '.css');
const outputFile = `./${folder}/${filenameWithCSSExtension}`;
const config = createRollupConfig(
`${folder}/${filename}`,
PLUGIN_CONFIG.concat(createPluginSCSS(outputFile))
);
const bundle = await rollup(config);
await bundle.write({
...BASE_OUTPUT_CONFIG,
format: 'umd',
sourcemap: false,
file: 'index.js',
});
}
}
fs.unlinkSync('./index.js');
}
build();
================================================
FILE: build/themes/light-border.js
================================================
import '../../src/scss/themes/light-border.scss';
================================================
FILE: build/themes/light.js
================================================
import '../../src/scss/themes/light.scss';
================================================
FILE: build/themes/material.js
================================================
import '../../src/scss/themes/material.scss';
================================================
FILE: build/themes/translucent.js
================================================
import '../../src/scss/themes/translucent.scss';
================================================
FILE: headless/package.json
================================================
{
"name": "tippy-headless",
"private": true,
"version": "0.1.0",
"description": "Headless rendering for Tippy.js",
"types": "../index.d.ts",
"main": "dist/tippy-headless.cjs.js",
"module": "dist/tippy-headless.esm.js",
"unpkg": "dist/tippy-headless.umd.min.js",
"sideEffects": false,
"files": [
"dist/"
],
"author": "atomiks",
"license": "MIT"
}
================================================
FILE: index.test-d.ts
================================================
import {expectType} from 'tsd';
import tippy, {
Instance,
Props,
Tippy,
LifecycleHooks,
delegate,
DelegateInstance,
createSingleton,
Plugin,
animateFill,
followCursor,
inlinePositioning,
sticky,
hideAll,
roundArrow,
CreateSingletonInstance,
} from './src/types';
interface CustomProps {
custom: number;
}
type FilteredProps = CustomProps &
Omit<Props, keyof CustomProps | keyof LifecycleHooks>;
type ExtendedProps = FilteredProps & LifecycleHooks<FilteredProps>;
declare const tippyExtended: Tippy<ExtendedProps>;
const singleTarget = document.createElement('div');
const mulitpleTargets = document.querySelectorAll('div');
expectType<Instance>(tippy(singleTarget));
expectType<Instance>(tippy(singleTarget, {content: 'hello'}));
expectType<Instance[]>(tippy(mulitpleTargets));
expectType<Instance[]>(tippy(mulitpleTargets, {content: 'hello'}));
expectType<DelegateInstance>(delegate(singleTarget, {target: '.child'}));
expectType<DelegateInstance>(
delegate(singleTarget, {target: '.child', content: 'hello'})
);
expectType<DelegateInstance[]>(delegate(mulitpleTargets, {target: '.child'}));
expectType<DelegateInstance[]>(
delegate(mulitpleTargets, {target: '.child', content: 'hello'})
);
const tippyInstances = [tippy(singleTarget), tippy(singleTarget)];
const singleton = createSingleton(tippyInstances);
expectType<CreateSingletonInstance>(createSingleton(tippyInstances));
expectType<CreateSingletonInstance>(
createSingleton(tippyInstances, {content: 'hello'})
);
expectType<CreateSingletonInstance>(
createSingleton(tippyInstances, {overrides: ['content']})
);
expectType<(instances: Instance<any>[]) => void>(singleton.setInstances);
// TODO: I want to assert that these *don't* error, but `tsd` does not provide
// such a function(?)
createSingleton(tippyExtended('button'));
singleton.setInstances(tippyExtended('button'));
expectType<Instance>(
tippy(singleTarget, {
plugins: [animateFill, followCursor, inlinePositioning, sticky],
})
);
const customPlugin: Plugin<ExtendedProps> = {
name: 'custom',
defaultValue: 42,
fn(instance) {
expectType<Instance<ExtendedProps>>(instance);
expectType<number>(instance.props.custom);
return {};
},
};
expectType<Instance<ExtendedProps>>(
tippyExtended(singleTarget, {
custom: 42,
plugins: [customPlugin],
})
);
expectType<void>(hideAll({duration: 50, exclude: tippy(singleTarget)}));
expectType<string>(roundArrow);
================================================
FILE: package.json
================================================
{
"name": "tippy.js",
"version": "6.3.7",
"description": "The complete tooltip, popover, dropdown, and menu solution for the web",
"main": "dist/tippy.cjs.js",
"module": "dist/tippy.esm.js",
"unpkg": "dist/tippy-bundle.umd.min.js",
"types": "index.d.ts",
"sideEffects": [
"**/*.css"
],
"author": "atomiks",
"contributors": [
"Brett Zamir"
],
"license": "MIT",
"bugs": "https://github.com/atomiks/tippyjs/issues",
"homepage": "https://atomiks.github.io/tippyjs/",
"keywords": [
"tooltip",
"popover",
"popper",
"dropdown",
"popup",
"tippy",
"tippy.js"
],
"files": [
"dist/",
"animations/",
"themes/",
"headless/",
"index.d.ts"
],
"repository": {
"type": "git",
"url": "git+https://github.com/atomiks/tippyjs.git"
},
"scripts": {
"dev": "cross-env NODE_ENV=dev rollup -c .config/rollup.config.js --watch",
"build": "node build/index.js && rollup -c .config/rollup.config.js && bundlesize",
"build:visual": "cross-env NODE_ENV=test rollup -c .config/rollup.config.js",
"serve": "serve test/visual",
"test:dom": "jest unit integration --coverage",
"test:functional": "jest functional",
"test:types": "tsc && tsd",
"test": "yarn test:types && yarn test:dom && yarn test:functional",
"lint": "eslint . --ext .ts,.js",
"format": "prettier --write \"**/*.{js,ts,json,md,mdx,scss,css}\" --ignore-path .config/.prettierignore",
"clean": "rimraf dist/ themes/ animations/ coverage/ .devserver/ .cache/ ./index.d.ts",
"prepare": "yarn clean && yarn build"
},
"babel": {
"extends": "./.config/babel.config"
},
"jest": {
"preset": "./.config/jest.config"
},
"eslintConfig": {
"extends": "./.config/eslint.config"
},
"prettier": {
"singleQuote": true,
"bracketSpacing": false,
"proseWrap": "always"
},
"browserslist": [
"> 0.5%",
"not dead",
"not safari < 8"
],
"bundlesize": [
{
"path": "dist/tippy-bundle.umd.min.js",
"maxSize": "10 kB"
},
{
"path": "headless/dist/tippy-headless.umd.min.js",
"maxSize": "10 kB"
},
{
"path": "dist/tippy.umd.min.js",
"maxSize": "10 kB"
},
{
"path": "dist/tippy.css",
"maxSize": "5 kB"
}
],
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"src/**/*.ts": [
"jest --findRelatedTests",
"eslint . --ext .ts,.js",
"git add"
],
"{build,src,test,website/src}/**/*.{ts,js,json,css,scss,md}": [
"prettier --write",
"git add"
]
},
"devDependencies": {
"@babel/core": "^7.8.3",
"@babel/preset-env": "^7.8.3",
"@babel/preset-typescript": "^7.13.0",
"@testing-library/dom": "^6.11.0",
"@types/node": "^12.12.25",
"@typescript-eslint/eslint-plugin": "^4.16.1",
"@typescript-eslint/parser": "^4.16.1",
"autoprefixer": "^9.7.4",
"babel-jest": "^25.3.0",
"babel-plugin-dev-expression": "^0.2.2",
"bundlesize": "^0.18.0",
"colorette": "^1.1.0",
"core-js": "^3.6.4",
"cross-env": "^7.0.0",
"cssnano": "^4.1.10",
"dotenv": "^8.2.0",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.9.0",
"husky": "^3.1.0",
"jest": "^25.3.0",
"jest-environment-jsdom-fourteen": "^1.0.1",
"jest-image-snapshot": "^2.12.0",
"jest-puppeteer": "^4.4.0",
"jest-puppeteer-docker": "^1.3.2",
"lint-staged": "^9.5.0",
"postcss": "^7.0.26",
"poster": "0.0.9",
"prettier": "^2.0.1",
"promise": "^8.0.3",
"puppeteer": "^2.1.1",
"rimraf": "^3.0.0",
"rollup": "^1.29.1",
"rollup-plugin-babel": "^4.3.3",
"rollup-plugin-commonjs": "^10.0.2",
"rollup-plugin-css-only": "^1.0.0",
"rollup-plugin-json": "^4.0.0",
"rollup-plugin-livereload": "^1.0.4",
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-replace": "^2.2.0",
"rollup-plugin-sass": "^1.2.2",
"rollup-plugin-serve": "^1.0.1",
"rollup-plugin-terser": "^5.2.0",
"sass": "^1.25.0",
"serve": "^11.3.0",
"tsd": "^0.14.0",
"typescript": "^4.2.2"
},
"dependencies": {
"@popperjs/core": "^2.9.0"
}
}
================================================
FILE: src/_babel.d.ts
================================================
declare const __DEV__: boolean;
================================================
FILE: src/addons/createSingleton.ts
================================================
import tippy from '..';
import {div} from '../dom-utils';
import {
CreateSingleton,
Plugin,
CreateSingletonProps,
ReferenceElement,
CreateSingletonInstance,
Instance,
Props,
} from '../types';
import {normalizeToArray, removeProperties} from '../utils';
import {errorWhen} from '../validation';
import {applyStyles, Modifier} from '@popperjs/core';
// The default `applyStyles` modifier has a cleanup function that gets called
// every time the popper is destroyed (i.e. a new target), removing the styles
// and causing transitions to break for singletons when the console is open, but
// most notably for non-transform styles being used, `gpuAcceleration: false`.
const applyStylesModifier: Modifier<'applyStyles', Record<string, unknown>> = {
...applyStyles,
effect({state}) {
const initialStyles = {
popper: {
position: state.options.strategy,
left: '0',
top: '0',
margin: '0',
},
arrow: {
position: 'absolute',
},
reference: {},
};
Object.assign(state.elements.popper.style, initialStyles.popper);
state.styles = initialStyles;
if (state.elements.arrow) {
Object.assign(state.elements.arrow.style, initialStyles.arrow);
}
// intentionally return no cleanup function
// return () => { ... }
},
};
const createSingleton: CreateSingleton = (
tippyInstances,
optionalProps = {}
) => {
/* istanbul ignore else */
if (__DEV__) {
errorWhen(
!Array.isArray(tippyInstances),
[
'The first argument passed to createSingleton() must be an array of',
'tippy instances. The passed value was',
String(tippyInstances),
].join(' ')
);
}
let individualInstances = tippyInstances;
let references: Array<ReferenceElement> = [];
let triggerTargets: Array<Element> = [];
let currentTarget: Element | null;
let overrides = optionalProps.overrides;
let interceptSetPropsCleanups: Array<() => void> = [];
let shownOnCreate = false;
function setTriggerTargets(): void {
triggerTargets = individualInstances
.map((instance) =>
normalizeToArray(instance.props.triggerTarget || instance.reference)
)
.reduce((acc, item) => acc.concat(item), []);
}
function setReferences(): void {
references = individualInstances.map((instance) => instance.reference);
}
function enableInstances(isEnabled: boolean): void {
individualInstances.forEach((instance) => {
if (isEnabled) {
instance.enable();
} else {
instance.disable();
}
});
}
function interceptSetProps(singleton: Instance): Array<() => void> {
return individualInstances.map((instance) => {
const originalSetProps = instance.setProps;
instance.setProps = (props): void => {
originalSetProps(props);
if (instance.reference === currentTarget) {
singleton.setProps(props);
}
};
return (): void => {
instance.setProps = originalSetProps;
};
});
}
// have to pass singleton, as it maybe undefined on first call
function prepareInstance(
singleton: Instance,
target: ReferenceElement
): void {
const index = triggerTargets.indexOf(target);
// bail-out
if (target === currentTarget) {
return;
}
currentTarget = target;
const overrideProps: Partial<Props> = (overrides || [])
.concat('content')
.reduce((acc, prop) => {
(acc as any)[prop] = individualInstances[index].props[prop];
return acc;
}, {});
singleton.setProps({
...overrideProps,
getReferenceClientRect:
typeof overrideProps.getReferenceClientRect === 'function'
? overrideProps.getReferenceClientRect
: (): ClientRect => references[index]?.getBoundingClientRect(),
});
}
enableInstances(false);
setReferences();
setTriggerTargets();
const plugin: Plugin = {
fn() {
return {
onDestroy(): void {
enableInstances(true);
},
onHidden(): void {
currentTarget = null;
},
onClickOutside(instance): void {
if (instance.props.showOnCreate && !shownOnCreate) {
shownOnCreate = true;
currentTarget = null;
}
},
onShow(instance): void {
if (instance.props.showOnCreate && !shownOnCreate) {
shownOnCreate = true;
prepareInstance(instance, references[0]);
}
},
onTrigger(instance, event): void {
prepareInstance(instance, event.currentTarget as Element);
},
};
},
};
const singleton = tippy(div(), {
...removeProperties(optionalProps, ['overrides']),
plugins: [plugin, ...(optionalProps.plugins || [])],
triggerTarget: triggerTargets,
popperOptions: {
...optionalProps.popperOptions,
modifiers: [
...(optionalProps.popperOptions?.modifiers || []),
applyStylesModifier,
],
},
}) as CreateSingletonInstance<CreateSingletonProps>;
const originalShow = singleton.show;
singleton.show = (target?: ReferenceElement | Instance | number): void => {
originalShow();
// first time, showOnCreate or programmatic call with no params
// default to showing first instance
if (!currentTarget && target == null) {
return prepareInstance(singleton, references[0]);
}
// triggered from event (do nothing as prepareInstance already called by onTrigger)
// programmatic call with no params when already visible (do nothing again)
if (currentTarget && target == null) {
return;
}
// target is index of instance
if (typeof target === 'number') {
return (
references[target] && prepareInstance(singleton, references[target])
);
}
// target is a child tippy instance
if (individualInstances.indexOf(target as Instance) >= 0) {
const ref = (target as Instance).reference;
return prepareInstance(singleton, ref);
}
// target is a ReferenceElement
if (references.indexOf(target as ReferenceElement) >= 0) {
return prepareInstance(singleton, target as ReferenceElement);
}
};
singleton.showNext = (): void => {
const first = references[0];
if (!currentTarget) {
return singleton.show(0);
}
const index = references.indexOf(currentTarget);
singleton.show(references[index + 1] || first);
};
singleton.showPrevious = (): void => {
const last = references[references.length - 1];
if (!currentTarget) {
return singleton.show(last);
}
const index = references.indexOf(currentTarget);
const target = references[index - 1] || last;
singleton.show(target);
};
const originalSetProps = singleton.setProps;
singleton.setProps = (props): void => {
overrides = props.overrides || overrides;
originalSetProps(props);
};
singleton.setInstances = (nextInstances): void => {
enableInstances(true);
interceptSetPropsCleanups.forEach((fn) => fn());
individualInstances = nextInstances;
enableInstances(false);
setReferences();
setTriggerTargets();
interceptSetPropsCleanups = interceptSetProps(singleton);
singleton.setProps({triggerTarget: triggerTargets});
};
interceptSetPropsCleanups = interceptSetProps(singleton);
return singleton;
};
export default createSingleton;
================================================
FILE: src/addons/delegate.ts
================================================
import tippy from '..';
import {TOUCH_OPTIONS} from '../constants';
import {defaultProps} from '../props';
import {Instance, Props, Targets} from '../types';
import {ListenerObject} from '../types-internal';
import {normalizeToArray, removeProperties} from '../utils';
import {errorWhen} from '../validation';
const BUBBLING_EVENTS_MAP = {
mouseover: 'mouseenter',
focusin: 'focus',
click: 'click',
};
/**
* Creates a delegate instance that controls the creation of tippy instances
* for child elements (`target` CSS selector).
*/
function delegate(
targets: Targets,
props: Partial<Props> & {target: string}
): Instance | Instance[] {
/* istanbul ignore else */
if (__DEV__) {
errorWhen(
!(props && props.target),
[
'You must specity a `target` prop indicating a CSS selector string matching',
'the target elements that should receive a tippy.',
].join(' ')
);
}
let listeners: ListenerObject[] = [];
let childTippyInstances: Instance[] = [];
let disabled = false;
const {target} = props;
const nativeProps = removeProperties(props, ['target']);
const parentProps = {...nativeProps, trigger: 'manual', touch: false};
const childProps = {
touch: defaultProps.touch,
...nativeProps,
showOnCreate: true,
};
const returnValue = tippy(targets, parentProps);
const normalizedReturnValue = normalizeToArray(returnValue);
function onTrigger(event: Event): void {
if (!event.target || disabled) {
return;
}
const targetNode = (event.target as Element).closest(target);
if (!targetNode) {
return;
}
// Get relevant trigger with fallbacks:
// 1. Check `data-tippy-trigger` attribute on target node
// 2. Fallback to `trigger` passed to `delegate()`
// 3. Fallback to `defaultProps.trigger`
const trigger =
targetNode.getAttribute('data-tippy-trigger') ||
props.trigger ||
defaultProps.trigger;
// @ts-ignore
if (targetNode._tippy) {
return;
}
if (event.type === 'touchstart' && typeof childProps.touch === 'boolean') {
return;
}
if (
event.type !== 'touchstart' &&
trigger.indexOf((BUBBLING_EVENTS_MAP as any)[event.type]) < 0
) {
return;
}
const instance = tippy(targetNode, childProps);
if (instance) {
childTippyInstances = childTippyInstances.concat(instance);
}
}
function on(
node: Element,
eventType: string,
handler: EventListener,
options: boolean | Record<string, unknown> = false
): void {
node.addEventListener(eventType, handler, options);
listeners.push({node, eventType, handler, options});
}
function addEventListeners(instance: Instance): void {
const {reference} = instance;
on(reference, 'touchstart', onTrigger, TOUCH_OPTIONS);
on(reference, 'mouseover', onTrigger);
on(reference, 'focusin', onTrigger);
on(reference, 'click', onTrigger);
}
function removeEventListeners(): void {
listeners.forEach(({node, eventType, handler, options}: ListenerObject) => {
node.removeEventListener(eventType, handler, options);
});
listeners = [];
}
function applyMutations(instance: Instance): void {
const originalDestroy = instance.destroy;
const originalEnable = instance.enable;
const originalDisable = instance.disable;
instance.destroy = (shouldDestroyChildInstances = true): void => {
if (shouldDestroyChildInstances) {
childTippyInstances.forEach((instance) => {
instance.destroy();
});
}
childTippyInstances = [];
removeEventListeners();
originalDestroy();
};
instance.enable = (): void => {
originalEnable();
childTippyInstances.forEach((instance) => instance.enable());
disabled = false;
};
instance.disable = (): void => {
originalDisable();
childTippyInstances.forEach((instance) => instance.disable());
disabled = true;
};
addEventListeners(instance);
}
normalizedReturnValue.forEach(applyMutations);
return returnValue;
}
export default delegate;
================================================
FILE: src/bindGlobalEventListeners.ts
================================================
import {TOUCH_OPTIONS} from './constants';
import {isReferenceElement} from './dom-utils';
export const currentInput = {isTouch: false};
let lastMouseMoveTime = 0;
/**
* When a `touchstart` event is fired, it's assumed the user is using touch
* input. We'll bind a `mousemove` event listener to listen for mouse input in
* the future. This way, the `isTouch` property is fully dynamic and will handle
* hybrid devices that use a mix of touch + mouse input.
*/
export function onDocumentTouchStart(): void {
if (currentInput.isTouch) {
return;
}
currentInput.isTouch = true;
if (window.performance) {
document.addEventListener('mousemove', onDocumentMouseMove);
}
}
/**
* When two `mousemove` event are fired consecutively within 20ms, it's assumed
* the user is using mouse input again. `mousemove` can fire on touch devices as
* well, but very rarely that quickly.
*/
export function onDocumentMouseMove(): void {
const now = performance.now();
if (now - lastMouseMoveTime < 20) {
currentInput.isTouch = false;
document.removeEventListener('mousemove', onDocumentMouseMove);
}
lastMouseMoveTime = now;
}
/**
* When an element is in focus and has a tippy, leaving the tab/window and
* returning causes it to show again. For mouse users this is unexpected, but
* for keyboard use it makes sense.
* TODO: find a better technique to solve this problem
*/
export function onWindowBlur(): void {
const activeElement = document.activeElement as HTMLElement | null;
if (isReferenceElement(activeElement)) {
const instance = activeElement._tippy!;
if (activeElement.blur && !instance.state.isVisible) {
activeElement.blur();
}
}
}
export default function bindGlobalEventListeners(): void {
document.addEventListener('touchstart', onDocumentTouchStart, TOUCH_OPTIONS);
window.addEventListener('blur', onWindowBlur);
}
================================================
FILE: src/browser.ts
================================================
export const isBrowser =
typeof window !== 'undefined' && typeof document !== 'undefined';
export const isIE11 = isBrowser
? // @ts-ignore
!!window.msCrypto
: false;
================================================
FILE: src/constants.ts
================================================
export const ROUND_ARROW =
'<svg width="16" height="6" xmlns="http://www.w3.org/2000/svg"><path d="M0 6s1.796-.013 4.67-3.615C5.851.9 6.93.006 8 0c1.07-.006 2.148.887 3.343 2.385C14.233 6.005 16 6 16 6H0z"></svg>';
export const BOX_CLASS = `__NAMESPACE_PREFIX__-box`;
export const CONTENT_CLASS = `__NAMESPACE_PREFIX__-content`;
export const BACKDROP_CLASS = `__NAMESPACE_PREFIX__-backdrop`;
export const ARROW_CLASS = `__NAMESPACE_PREFIX__-arrow`;
export const SVG_ARROW_CLASS = `__NAMESPACE_PREFIX__-svg-arrow`;
export const TOUCH_OPTIONS = {passive: true, capture: true};
export const TIPPY_DEFAULT_APPEND_TO = () => document.body;
================================================
FILE: src/createTippy.ts
================================================
import {createPopper, StrictModifiers, Modifier} from '@popperjs/core';
import {currentInput} from './bindGlobalEventListeners';
import {isIE11} from './browser';
import {TIPPY_DEFAULT_APPEND_TO, TOUCH_OPTIONS} from './constants';
import {
actualContains,
div,
getOwnerDocument,
isCursorOutsideInteractiveBorder,
isMouseEvent,
setTransitionDuration,
setVisibilityState,
updateTransitionEndListener,
} from './dom-utils';
import {defaultProps, evaluateProps, getExtendedPassedProps} from './props';
import {getChildren} from './template';
import {
Content,
Instance,
LifecycleHooks,
PopperElement,
Props,
ReferenceElement,
} from './types';
import {ListenerObject, PopperTreeData, PopperChildren} from './types-internal';
import {
arrayFrom,
debounce,
getValueAtIndexOrReturn,
invokeWithArgsOrReturn,
normalizeToArray,
pushIfUnique,
splitBySpaces,
unique,
removeUndefinedProps,
} from './utils';
import {createMemoryLeakWarning, errorWhen, warnWhen} from './validation';
let idCounter = 1;
let mouseMoveListeners: ((event: MouseEvent) => void)[] = [];
// Used by `hideAll()`
export let mountedInstances: Instance[] = [];
export default function createTippy(
reference: ReferenceElement,
passedProps: Partial<Props>
): Instance {
const props = evaluateProps(reference, {
...defaultProps,
...getExtendedPassedProps(removeUndefinedProps(passedProps)),
});
// ===========================================================================
// 🔒 Private members
// ===========================================================================
let showTimeout: any;
let hideTimeout: any;
let scheduleHideAnimationFrame: number;
let isVisibleFromClick = false;
let didHideDueToDocumentMouseDown = false;
let didTouchMove = false;
let ignoreOnFirstUpdate = false;
let lastTriggerEvent: Event | undefined;
let currentTransitionEndListener: (event: TransitionEvent) => void;
let onFirstUpdate: () => void;
let listeners: ListenerObject[] = [];
let debouncedOnMouseMove = debounce(onMouseMove, props.interactiveDebounce);
let currentTarget: Element;
// ===========================================================================
// 🔑 Public members
// ===========================================================================
const id = idCounter++;
const popperInstance = null;
const plugins = unique(props.plugins);
const state = {
// Is the instance currently enabled?
isEnabled: true,
// Is the tippy currently showing and not transitioning out?
isVisible: false,
// Has the instance been destroyed?
isDestroyed: false,
// Is the tippy currently mounted to the DOM?
isMounted: false,
// Has the tippy finished transitioning in?
isShown: false,
};
const instance: Instance = {
// properties
id,
reference,
popper: div(),
popperInstance,
props,
state,
plugins,
// methods
clearDelayTimeouts,
setProps,
setContent,
show,
hide,
hideWithInteractivity,
enable,
disable,
unmount,
destroy,
};
// TODO: Investigate why this early return causes a TDZ error in the tests —
// it doesn't seem to happen in the browser
/* istanbul ignore if */
if (!props.render) {
if (__DEV__) {
errorWhen(true, 'render() function has not been supplied.');
}
return instance;
}
// ===========================================================================
// Initial mutations
// ===========================================================================
const {popper, onUpdate} = props.render(instance);
popper.setAttribute('data-__NAMESPACE_PREFIX__-root', '');
popper.id = `__NAMESPACE_PREFIX__-${instance.id}`;
instance.popper = popper;
reference._tippy = instance;
popper._tippy = instance;
const pluginsHooks = plugins.map((plugin) => plugin.fn(instance));
const hasAriaExpanded = reference.hasAttribute('aria-expanded');
addListeners();
handleAriaExpandedAttribute();
handleStyles();
invokeHook('onCreate', [instance]);
if (props.showOnCreate) {
scheduleShow();
}
// Prevent a tippy with a delay from hiding if the cursor left then returned
// before it started hiding
popper.addEventListener('mouseenter', () => {
if (instance.props.interactive && instance.state.isVisible) {
instance.clearDelayTimeouts();
}
});
popper.addEventListener('mouseleave', () => {
if (
instance.props.interactive &&
instance.props.trigger.indexOf('mouseenter') >= 0
) {
getDocument().addEventListener('mousemove', debouncedOnMouseMove);
}
});
return instance;
// ===========================================================================
// 🔒 Private methods
// ===========================================================================
function getNormalizedTouchSettings(): [string | boolean, number] {
const {touch} = instance.props;
return Array.isArray(touch) ? touch : [touch, 0];
}
function getIsCustomTouchBehavior(): boolean {
return getNormalizedTouchSettings()[0] === 'hold';
}
function getIsDefaultRenderFn(): boolean {
// @ts-ignore
return !!instance.props.render?.$$tippy;
}
function getCurrentTarget(): Element {
return currentTarget || reference;
}
function getDocument(): Document {
const parent = getCurrentTarget().parentNode as Element;
return parent ? getOwnerDocument(parent) : document;
}
function getDefaultTemplateChildren(): PopperChildren {
return getChildren(popper);
}
function getDelay(isShow: boolean): number {
// For touch or keyboard input, force `0` delay for UX reasons
// Also if the instance is mounted but not visible (transitioning out),
// ignore delay
if (
(instance.state.isMounted && !instance.state.isVisible) ||
currentInput.isTouch ||
(lastTriggerEvent && lastTriggerEvent.type === 'focus')
) {
return 0;
}
return getValueAtIndexOrReturn(
instance.props.delay,
isShow ? 0 : 1,
defaultProps.delay
);
}
function handleStyles(fromHide = false): void {
popper.style.pointerEvents =
instance.props.interactive && !fromHide ? '' : 'none';
popper.style.zIndex = `${instance.props.zIndex}`;
}
function invokeHook(
hook: keyof LifecycleHooks,
args: [Instance, any?],
shouldInvokePropsHook = true
): void {
pluginsHooks.forEach((pluginHooks) => {
if (pluginHooks[hook]) {
pluginHooks[hook]!(...args);
}
});
if (shouldInvokePropsHook) {
instance.props[hook](...args);
}
}
function handleAriaContentAttribute(): void {
const {aria} = instance.props;
if (!aria.content) {
return;
}
const attr = `aria-${aria.content}`;
const id = popper.id;
const nodes = normalizeToArray(instance.props.triggerTarget || reference);
nodes.forEach((node) => {
const currentValue = node.getAttribute(attr);
if (instance.state.isVisible) {
node.setAttribute(attr, currentValue ? `${currentValue} ${id}` : id);
} else {
const nextValue = currentValue && currentValue.replace(id, '').trim();
if (nextValue) {
node.setAttribute(attr, nextValue);
} else {
node.removeAttribute(attr);
}
}
});
}
function handleAriaExpandedAttribute(): void {
if (hasAriaExpanded || !instance.props.aria.expanded) {
return;
}
const nodes = normalizeToArray(instance.props.triggerTarget || reference);
nodes.forEach((node) => {
if (instance.props.interactive) {
node.setAttribute(
'aria-expanded',
instance.state.isVisible && node === getCurrentTarget()
? 'true'
: 'false'
);
} else {
node.removeAttribute('aria-expanded');
}
});
}
function cleanupInteractiveMouseListeners(): void {
getDocument().removeEventListener('mousemove', debouncedOnMouseMove);
mouseMoveListeners = mouseMoveListeners.filter(
(listener) => listener !== debouncedOnMouseMove
);
}
function onDocumentPress(event: MouseEvent | TouchEvent): void {
// Moved finger to scroll instead of an intentional tap outside
if (currentInput.isTouch) {
if (didTouchMove || event.type === 'mousedown') {
return;
}
}
const actualTarget =
(event.composedPath && event.composedPath()[0]) || event.target;
// Clicked on interactive popper
if (
instance.props.interactive &&
actualContains(popper, actualTarget as Element)
) {
return;
}
// Clicked on the event listeners target
if (
normalizeToArray(instance.props.triggerTarget || reference).some((el) =>
actualContains(el, actualTarget as Element)
)
) {
if (currentInput.isTouch) {
return;
}
if (
instance.state.isVisible &&
instance.props.trigger.indexOf('click') >= 0
) {
return;
}
} else {
invokeHook('onClickOutside', [instance, event]);
}
if (instance.props.hideOnClick === true) {
instance.clearDelayTimeouts();
instance.hide();
// `mousedown` event is fired right before `focus` if pressing the
// currentTarget. This lets a tippy with `focus` trigger know that it
// should not show
didHideDueToDocumentMouseDown = true;
setTimeout(() => {
didHideDueToDocumentMouseDown = false;
});
// The listener gets added in `scheduleShow()`, but this may be hiding it
// before it shows, and hide()'s early bail-out behavior can prevent it
// from being cleaned up
if (!instance.state.isMounted) {
removeDocumentPress();
}
}
}
function onTouchMove(): void {
didTouchMove = true;
}
function onTouchStart(): void {
didTouchMove = false;
}
function addDocumentPress(): void {
const doc = getDocument();
doc.addEventListener('mousedown', onDocumentPress, true);
doc.addEventListener('touchend', onDocumentPress, TOUCH_OPTIONS);
doc.addEventListener('touchstart', onTouchStart, TOUCH_OPTIONS);
doc.addEventListener('touchmove', onTouchMove, TOUCH_OPTIONS);
}
function removeDocumentPress(): void {
const doc = getDocument();
doc.removeEventListener('mousedown', onDocumentPress, true);
doc.removeEventListener('touchend', onDocumentPress, TOUCH_OPTIONS);
doc.removeEventListener('touchstart', onTouchStart, TOUCH_OPTIONS);
doc.removeEventListener('touchmove', onTouchMove, TOUCH_OPTIONS);
}
function onTransitionedOut(duration: number, callback: () => void): void {
onTransitionEnd(duration, () => {
if (
!instance.state.isVisible &&
popper.parentNode &&
popper.parentNode.contains(popper)
) {
callback();
}
});
}
function onTransitionedIn(duration: number, callback: () => void): void {
onTransitionEnd(duration, callback);
}
function onTransitionEnd(duration: number, callback: () => void): void {
const box = getDefaultTemplateChildren().box;
function listener(event: TransitionEvent): void {
if (event.target === box) {
updateTransitionEndListener(box, 'remove', listener);
callback();
}
}
// Make callback synchronous if duration is 0
// `transitionend` won't fire otherwise
if (duration === 0) {
return callback();
}
updateTransitionEndListener(box, 'remove', currentTransitionEndListener);
updateTransitionEndListener(box, 'add', listener);
currentTransitionEndListener = listener;
}
function on(
eventType: string,
handler: EventListener,
options: boolean | Record<string, unknown> = false
): void {
const nodes = normalizeToArray(instance.props.triggerTarget || reference);
nodes.forEach((node) => {
node.addEventListener(eventType, handler, options);
listeners.push({node, eventType, handler, options});
});
}
function addListeners(): void {
if (getIsCustomTouchBehavior()) {
on('touchstart', onTrigger, {passive: true});
on('touchend', onMouseLeave as EventListener, {passive: true});
}
splitBySpaces(instance.props.trigger).forEach((eventType) => {
if (eventType === 'manual') {
return;
}
on(eventType, onTrigger);
switch (eventType) {
case 'mouseenter':
on('mouseleave', onMouseLeave as EventListener);
break;
case 'focus':
on(isIE11 ? 'focusout' : 'blur', onBlurOrFocusOut as EventListener);
break;
case 'focusin':
on('focusout', onBlurOrFocusOut as EventListener);
break;
}
});
}
function removeListeners(): void {
listeners.forEach(({node, eventType, handler, options}: ListenerObject) => {
node.removeEventListener(eventType, handler, options);
});
listeners = [];
}
function onTrigger(event: Event): void {
let shouldScheduleClickHide = false;
if (
!instance.state.isEnabled ||
isEventListenerStopped(event) ||
didHideDueToDocumentMouseDown
) {
return;
}
const wasFocused = lastTriggerEvent?.type === 'focus';
lastTriggerEvent = event;
currentTarget = event.currentTarget as Element;
handleAriaExpandedAttribute();
if (!instance.state.isVisible && isMouseEvent(event)) {
// If scrolling, `mouseenter` events can be fired if the cursor lands
// over a new target, but `mousemove` events don't get fired. This
// causes interactive tooltips to get stuck open until the cursor is
// moved
mouseMoveListeners.forEach((listener) => listener(event));
}
// Toggle show/hide when clicking click-triggered tooltips
if (
event.type === 'click' &&
(instance.props.trigger.indexOf('mouseenter') < 0 ||
isVisibleFromClick) &&
instance.props.hideOnClick !== false &&
instance.state.isVisible
) {
shouldScheduleClickHide = true;
} else {
scheduleShow(event);
}
if (event.type === 'click') {
isVisibleFromClick = !shouldScheduleClickHide;
}
if (shouldScheduleClickHide && !wasFocused) {
scheduleHide(event);
}
}
function onMouseMove(event: MouseEvent): void {
const target = event.target as Node;
const isCursorOverReferenceOrPopper =
getCurrentTarget().contains(target) || popper.contains(target);
if (event.type === 'mousemove' && isCursorOverReferenceOrPopper) {
return;
}
const popperTreeData = getNestedPopperTree()
.concat(popper)
.map((popper) => {
const instance = popper._tippy!;
const state = instance.popperInstance?.state;
if (state) {
return {
popperRect: popper.getBoundingClientRect(),
popperState: state,
props,
};
}
return null;
})
.filter(Boolean) as PopperTreeData[];
if (isCursorOutsideInteractiveBorder(popperTreeData, event)) {
cleanupInteractiveMouseListeners();
scheduleHide(event);
}
}
function onMouseLeave(event: MouseEvent): void {
const shouldBail =
isEventListenerStopped(event) ||
(instance.props.trigger.indexOf('click') >= 0 && isVisibleFromClick);
if (shouldBail) {
return;
}
if (instance.props.interactive) {
instance.hideWithInteractivity(event);
return;
}
scheduleHide(event);
}
function onBlurOrFocusOut(event: FocusEvent): void {
if (
instance.props.trigger.indexOf('focusin') < 0 &&
event.target !== getCurrentTarget()
) {
return;
}
// If focus was moved to within the popper
if (
instance.props.interactive &&
event.relatedTarget &&
popper.contains(event.relatedTarget as Element)
) {
return;
}
scheduleHide(event);
}
function isEventListenerStopped(event: Event): boolean {
return currentInput.isTouch
? getIsCustomTouchBehavior() !== event.type.indexOf('touch') >= 0
: false;
}
function createPopperInstance(): void {
destroyPopperInstance();
const {
popperOptions,
placement,
offset,
getReferenceClientRect,
moveTransition,
} = instance.props;
const arrow = getIsDefaultRenderFn() ? getChildren(popper).arrow : null;
const computedReference = getReferenceClientRect
? {
getBoundingClientRect: getReferenceClientRect,
contextElement:
getReferenceClientRect.contextElement || getCurrentTarget(),
}
: reference;
const tippyModifier: Modifier<'$$tippy', Record<string, unknown>> = {
name: '$$tippy',
enabled: true,
phase: 'beforeWrite',
requires: ['computeStyles'],
fn({state}) {
if (getIsDefaultRenderFn()) {
const {box} = getDefaultTemplateChildren();
['placement', 'reference-hidden', 'escaped'].forEach((attr) => {
if (attr === 'placement') {
box.setAttribute('data-placement', state.placement);
} else {
if (state.attributes.popper[`data-popper-${attr}`]) {
box.setAttribute(`data-${attr}`, '');
} else {
box.removeAttribute(`data-${attr}`);
}
}
});
state.attributes.popper = {};
}
},
};
type TippyModifier = Modifier<'$$tippy', Record<string, unknown>>;
type ExtendedModifiers = StrictModifiers | Partial<TippyModifier>;
const modifiers: Array<ExtendedModifiers> = [
{
name: 'offset',
options: {
offset,
},
},
{
name: 'preventOverflow',
options: {
padding: {
top: 2,
bottom: 2,
left: 5,
right: 5,
},
},
},
{
name: 'flip',
options: {
padding: 5,
},
},
{
name: 'computeStyles',
options: {
adaptive: !moveTransition,
},
},
tippyModifier,
];
if (getIsDefaultRenderFn() && arrow) {
modifiers.push({
name: 'arrow',
options: {
element: arrow,
padding: 3,
},
});
}
modifiers.push(...(popperOptions?.modifiers || []));
instance.popperInstance = createPopper<ExtendedModifiers>(
computedReference,
popper,
{
...popperOptions,
placement,
onFirstUpdate,
modifiers,
}
);
}
function destroyPopperInstance(): void {
if (instance.popperInstance) {
instance.popperInstance.destroy();
instance.popperInstance = null;
}
}
function mount(): void {
const {appendTo} = instance.props;
let parentNode: any;
// By default, we'll append the popper to the triggerTargets's parentNode so
// it's directly after the reference element so the elements inside the
// tippy can be tabbed to
// If there are clipping issues, the user can specify a different appendTo
// and ensure focus management is handled correctly manually
const node = getCurrentTarget();
if (
(instance.props.interactive && appendTo === TIPPY_DEFAULT_APPEND_TO) ||
appendTo === 'parent'
) {
parentNode = node.parentNode;
} else {
parentNode = invokeWithArgsOrReturn(appendTo, [node]);
}
// The popper element needs to exist on the DOM before its position can be
// updated as Popper needs to read its dimensions
if (!parentNode.contains(popper)) {
parentNode.appendChild(popper);
}
instance.state.isMounted = true;
createPopperInstance();
/* istanbul ignore else */
if (__DEV__) {
// Accessibility check
warnWhen(
instance.props.interactive &&
appendTo === defaultProps.appendTo &&
node.nextElementSibling !== popper,
[
'Interactive tippy element may not be accessible via keyboard',
'navigation because it is not directly after the reference element',
'in the DOM source order.',
'\n\n',
'Using a wrapper <div> or <span> tag around the reference element',
'solves this by creating a new parentNode context.',
'\n\n',
'Specifying `appendTo: document.body` silences this warning, but it',
'assumes you are using a focus management solution to handle',
'keyboard navigation.',
'\n\n',
'See: https://atomiks.github.io/tippyjs/v6/accessibility/#interactivity',
].join(' ')
);
}
}
function getNestedPopperTree(): PopperElement[] {
return arrayFrom(
popper.querySelectorAll('[data-__NAMESPACE_PREFIX__-root]')
);
}
function scheduleShow(event?: Event): void {
instance.clearDelayTimeouts();
if (event) {
invokeHook('onTrigger', [instance, event]);
}
addDocumentPress();
let delay = getDelay(true);
const [touchValue, touchDelay] = getNormalizedTouchSettings();
if (currentInput.isTouch && touchValue === 'hold' && touchDelay) {
delay = touchDelay;
}
if (delay) {
showTimeout = setTimeout(() => {
instance.show();
}, delay);
} else {
instance.show();
}
}
function scheduleHide(event: Event): void {
instance.clearDelayTimeouts();
invokeHook('onUntrigger', [instance, event]);
if (!instance.state.isVisible) {
removeDocumentPress();
return;
}
// For interactive tippies, scheduleHide is added to a document.body handler
// from onMouseLeave so must intercept scheduled hides from mousemove/leave
// events when trigger contains mouseenter and click, and the tip is
// currently shown as a result of a click.
if (
instance.props.trigger.indexOf('mouseenter') >= 0 &&
instance.props.trigger.indexOf('click') >= 0 &&
['mouseleave', 'mousemove'].indexOf(event.type) >= 0 &&
isVisibleFromClick
) {
return;
}
const delay = getDelay(false);
if (delay) {
hideTimeout = setTimeout(() => {
if (instance.state.isVisible) {
instance.hide();
}
}, delay);
} else {
// Fixes a `transitionend` problem when it fires 1 frame too
// late sometimes, we don't want hide() to be called.
scheduleHideAnimationFrame = requestAnimationFrame(() => {
instance.hide();
});
}
}
// ===========================================================================
// 🔑 Public methods
// ===========================================================================
function enable(): void {
instance.state.isEnabled = true;
}
function disable(): void {
// Disabling the instance should also hide it
// https://github.com/atomiks/tippy.js-react/issues/106
instance.hide();
instance.state.isEnabled = false;
}
function clearDelayTimeouts(): void {
clearTimeout(showTimeout);
clearTimeout(hideTimeout);
cancelAnimationFrame(scheduleHideAnimationFrame);
}
function setProps(partialProps: Partial<Props>): void {
/* istanbul ignore else */
if (__DEV__) {
warnWhen(instance.state.isDestroyed, createMemoryLeakWarning('setProps'));
}
if (instance.state.isDestroyed) {
return;
}
invokeHook('onBeforeUpdate', [instance, partialProps]);
removeListeners();
const prevProps = instance.props;
const nextProps = evaluateProps(reference, {
...prevProps,
...removeUndefinedProps(partialProps),
ignoreAttributes: true,
});
instance.props = nextProps;
addListeners();
if (prevProps.interactiveDebounce !== nextProps.interactiveDebounce) {
cleanupInteractiveMouseListeners();
debouncedOnMouseMove = debounce(
onMouseMove,
nextProps.interactiveDebounce
);
}
// Ensure stale aria-expanded attributes are removed
if (prevProps.triggerTarget && !nextProps.triggerTarget) {
normalizeToArray(prevProps.triggerTarget).forEach((node) => {
node.removeAttribute('aria-expanded');
});
} else if (nextProps.triggerTarget) {
reference.removeAttribute('aria-expanded');
}
handleAriaExpandedAttribute();
handleStyles();
if (onUpdate) {
onUpdate(prevProps, nextProps);
}
if (instance.popperInstance) {
createPopperInstance();
// Fixes an issue with nested tippies if they are all getting re-rendered,
// and the nested ones get re-rendered first.
// https://github.com/atomiks/tippyjs-react/issues/177
// TODO: find a cleaner / more efficient solution(!)
getNestedPopperTree().forEach((nestedPopper) => {
// React (and other UI libs likely) requires a rAF wrapper as it flushes
// its work in one
requestAnimationFrame(nestedPopper._tippy!.popperInstance!.forceUpdate);
});
}
invokeHook('onAfterUpdate', [instance, partialProps]);
}
function setContent(content: Content): void {
instance.setProps({content});
}
function show(): void {
/* istanbul ignore else */
if (__DEV__) {
warnWhen(instance.state.isDestroyed, createMemoryLeakWarning('show'));
}
// Early bail-out
const isAlreadyVisible = instance.state.isVisible;
const isDestroyed = instance.state.isDestroyed;
const isDisabled = !instance.state.isEnabled;
const isTouchAndTouchDisabled =
currentInput.isTouch && !instance.props.touch;
const duration = getValueAtIndexOrReturn(
instance.props.duration,
0,
defaultProps.duration
);
if (
isAlreadyVisible ||
isDestroyed ||
isDisabled ||
isTouchAndTouchDisabled
) {
return;
}
// Normalize `disabled` behavior across browsers.
// Firefox allows events on disabled elements, but Chrome doesn't.
// Using a wrapper element (i.e. <span>) is recommended.
if (getCurrentTarget().hasAttribute('disabled')) {
return;
}
invokeHook('onShow', [instance], false);
if (instance.props.onShow(instance) === false) {
return;
}
instance.state.isVisible = true;
if (getIsDefaultRenderFn()) {
popper.style.visibility = 'visible';
}
handleStyles();
addDocumentPress();
if (!instance.state.isMounted) {
popper.style.transition = 'none';
}
// If flipping to the opposite side after hiding at least once, the
// animation will use the wrong placement without resetting the duration
if (getIsDefaultRenderFn()) {
const {box, content} = getDefaultTemplateChildren();
setTransitionDuration([box, content], 0);
}
onFirstUpdate = (): void => {
if (!instance.state.isVisible || ignoreOnFirstUpdate) {
return;
}
ignoreOnFirstUpdate = true;
// reflow
void popper.offsetHeight;
popper.style.transition = instance.props.moveTransition;
if (getIsDefaultRenderFn() && instance.props.animation) {
const {box, content} = getDefaultTemplateChildren();
setTransitionDuration([box, content], duration);
setVisibilityState([box, content], 'visible');
}
handleAriaContentAttribute();
handleAriaExpandedAttribute();
pushIfUnique(mountedInstances, instance);
// certain modifiers (e.g. `maxSize`) require a second update after the
// popper has been positioned for the first time
instance.popperInstance?.forceUpdate();
invokeHook('onMount', [instance]);
if (instance.props.animation && getIsDefaultRenderFn()) {
onTransitionedIn(duration, () => {
instance.state.isShown = true;
invokeHook('onShown', [instance]);
});
}
};
mount();
}
function hide(): void {
/* istanbul ignore else */
if (__DEV__) {
warnWhen(instance.state.isDestroyed, createMemoryLeakWarning('hide'));
}
// Early bail-out
const isAlreadyHidden = !instance.state.isVisible;
const isDestroyed = instance.state.isDestroyed;
const isDisabled = !instance.state.isEnabled;
const duration = getValueAtIndexOrReturn(
instance.props.duration,
1,
defaultProps.duration
);
if (isAlreadyHidden || isDestroyed || isDisabled) {
return;
}
invokeHook('onHide', [instance], false);
if (instance.props.onHide(instance) === false) {
return;
}
instance.state.isVisible = false;
instance.state.isShown = false;
ignoreOnFirstUpdate = false;
isVisibleFromClick = false;
if (getIsDefaultRenderFn()) {
popper.style.visibility = 'hidden';
}
cleanupInteractiveMouseListeners();
removeDocumentPress();
handleStyles(true);
if (getIsDefaultRenderFn()) {
const {box, content} = getDefaultTemplateChildren();
if (instance.props.animation) {
setTransitionDuration([box, content], duration);
setVisibilityState([box, content], 'hidden');
}
}
handleAriaContentAttribute();
handleAriaExpandedAttribute();
if (instance.props.animation) {
if (getIsDefaultRenderFn()) {
onTransitionedOut(duration, instance.unmount);
}
} else {
instance.unmount();
}
}
function hideWithInteractivity(event: MouseEvent): void {
/* istanbul ignore else */
if (__DEV__) {
warnWhen(
instance.state.isDestroyed,
createMemoryLeakWarning('hideWithInteractivity')
);
}
getDocument().addEventListener('mousemove', debouncedOnMouseMove);
pushIfUnique(mouseMoveListeners, debouncedOnMouseMove);
debouncedOnMouseMove(event);
}
function unmount(): void {
/* istanbul ignore else */
if (__DEV__) {
warnWhen(instance.state.isDestroyed, createMemoryLeakWarning('unmount'));
}
if (instance.state.isVisible) {
instance.hide();
}
if (!instance.state.isMounted) {
return;
}
destroyPopperInstance();
// If a popper is not interactive, it will be appended outside the popper
// tree by default. This seems mainly for interactive tippies, but we should
// find a workaround if possible
getNestedPopperTree().forEach((nestedPopper) => {
nestedPopper._tippy!.unmount();
});
if (popper.parentNode) {
popper.parentNode.removeChild(popper);
}
mountedInstances = mountedInstances.filter((i) => i !== instance);
instance.state.isMounted = false;
invokeHook('onHidden', [instance]);
}
function destroy(): void {
/* istanbul ignore else */
if (__DEV__) {
warnWhen(instance.state.isDestroyed, createMemoryLeakWarning('destroy'));
}
if (instance.state.isDestroyed) {
return;
}
instance.clearDelayTimeouts();
instance.unmount();
removeListeners();
delete reference._tippy;
instance.state.isDestroyed = true;
invokeHook('onDestroy', [instance]);
}
}
================================================
FILE: src/css.ts
================================================
export function injectCSS(css: string): void {
const style = document.createElement('style');
style.textContent = css;
style.setAttribute('data-__NAMESPACE_PREFIX__-stylesheet', '');
const head = document.head;
const firstStyleOrLinkTag = document.querySelector('head>style,head>link');
if (firstStyleOrLinkTag) {
head.insertBefore(style, firstStyleOrLinkTag);
} else {
head.appendChild(style);
}
}
================================================
FILE: src/dom-utils.ts
================================================
import {ReferenceElement, Targets} from './types';
import {PopperTreeData} from './types-internal';
import {arrayFrom, isType, normalizeToArray, getBasePlacement} from './utils';
export function div(): HTMLDivElement {
return document.createElement('div');
}
export function isElement(value: unknown): value is Element | DocumentFragment {
return ['Element', 'Fragment'].some((type) => isType(value, type));
}
export function isNodeList(value: unknown): value is NodeList {
return isType(value, 'NodeList');
}
export function isMouseEvent(value: unknown): value is MouseEvent {
return isType(value, 'MouseEvent');
}
export function isReferenceElement(value: any): value is ReferenceElement {
return !!(value && value._tippy && value._tippy.reference === value);
}
export function getArrayOfElements(value: Targets): Element[] {
if (isElement(value)) {
return [value];
}
if (isNodeList(value)) {
return arrayFrom(value);
}
if (Array.isArray(value)) {
return value;
}
return arrayFrom(document.querySelectorAll(value));
}
export function setTransitionDuration(
els: (HTMLDivElement | null)[],
value: number
): void {
els.forEach((el) => {
if (el) {
el.style.transitionDuration = `${value}ms`;
}
});
}
export function setVisibilityState(
els: (HTMLDivElement | null)[],
state: 'visible' | 'hidden'
): void {
els.forEach((el) => {
if (el) {
el.setAttribute('data-state', state);
}
});
}
export function getOwnerDocument(
elementOrElements: Element | Element[]
): Document {
const [element] = normalizeToArray(elementOrElements);
// Elements created via a <template> have an ownerDocument with no reference to the body
return element?.ownerDocument?.body ? element.ownerDocument : document;
}
export function isCursorOutsideInteractiveBorder(
popperTreeData: PopperTreeData[],
event: MouseEvent
): boolean {
const {clientX, clientY} = event;
return popperTreeData.every(({popperRect, popperState, props}) => {
const {interactiveBorder} = props;
const basePlacement = getBasePlacement(popperState.placement);
const offsetData = popperState.modifiersData.offset;
if (!offsetData) {
return true;
}
const topDistance = basePlacement === 'bottom' ? offsetData.top!.y : 0;
const bottomDistance = basePlacement === 'top' ? offsetData.bottom!.y : 0;
const leftDistance = basePlacement === 'right' ? offsetData.left!.x : 0;
const rightDistance = basePlacement === 'left' ? offsetData.right!.x : 0;
const exceedsTop =
popperRect.top - clientY + topDistance > interactiveBorder;
const exceedsBottom =
clientY - popperRect.bottom - bottomDistance > interactiveBorder;
const exceedsLeft =
popperRect.left - clientX + leftDistance > interactiveBorder;
const exceedsRight =
clientX - popperRect.right - rightDistance > interactiveBorder;
return exceedsTop || exceedsBottom || exceedsLeft || exceedsRight;
});
}
export function updateTransitionEndListener(
box: HTMLDivElement,
action: 'add' | 'remove',
listener: (event: TransitionEvent) => void
): void {
if (listener) {
const method = `${action}EventListener` as
| 'addEventListener'
| 'removeEventListener';
// some browsers apparently support `transition` (unprefixed) but only fire
// `webkitTransitionEnd`...
['transitionend', 'webkitTransitionEnd'].forEach((event) => {
box[method](event, listener as EventListener);
});
}
}
/**
* Compared to xxx.contains, this function works for dom structures with shadow
* dom
*/
export function actualContains(parent: Element, child: Element): boolean {
let target = child;
while (target) {
if (parent.contains(target)) {
return true;
}
target = (target.getRootNode?.() as any)?.host;
}
return false;
}
================================================
FILE: src/index.ts
================================================
import bindGlobalEventListeners, {
currentInput,
} from './bindGlobalEventListeners';
import createTippy, {mountedInstances} from './createTippy';
import {getArrayOfElements, isElement, isReferenceElement} from './dom-utils';
import {defaultProps, setDefaultProps, validateProps} from './props';
import {HideAll, HideAllOptions, Instance, Props, Targets} from './types';
import {validateTargets, warnWhen} from './validation';
function tippy(
targets: Targets,
optionalProps: Partial<Props> = {}
): Instance | Instance[] {
const plugins = defaultProps.plugins.concat(optionalProps.plugins || []);
/* istanbul ignore else */
if (__DEV__) {
validateTargets(targets);
validateProps(optionalProps, plugins);
}
bindGlobalEventListeners();
const passedProps: Partial<Props> = {...optionalProps, plugins};
const elements = getArrayOfElements(targets);
/* istanbul ignore else */
if (__DEV__) {
const isSingleContentElement = isElement(passedProps.content);
const isMoreThanOneReferenceElement = elements.length > 1;
warnWhen(
isSingleContentElement && isMoreThanOneReferenceElement,
[
'tippy() was passed an Element as the `content` prop, but more than',
'one tippy instance was created by this invocation. This means the',
'content element will only be appended to the last tippy instance.',
'\n\n',
'Instead, pass the .innerHTML of the element, or use a function that',
'returns a cloned version of the element instead.',
'\n\n',
'1) content: element.innerHTML\n',
'2) content: () => element.cloneNode(true)',
].join(' ')
);
}
const instances = elements.reduce<Instance[]>(
(acc, reference): Instance[] => {
const instance = reference && createTippy(reference, passedProps);
if (instance) {
acc.push(instance);
}
return acc;
},
[]
);
return isElement(targets) ? instances[0] : instances;
}
tippy.defaultProps = defaultProps;
tippy.setDefaultProps = setDefaultProps;
tippy.currentInput = currentInput;
export default tippy;
export const hideAll: HideAll = ({
exclude: excludedReferenceOrInstance,
duration,
}: HideAllOptions = {}) => {
mountedInstances.forEach((instance) => {
let isExcluded = false;
if (excludedReferenceOrInstance) {
isExcluded = isReferenceElement(excludedReferenceOrInstance)
? instance.reference === excludedReferenceOrInstance
: instance.popper === (excludedReferenceOrInstance as Instance).popper;
}
if (!isExcluded) {
const originalDuration = instance.props.duration;
instance.setProps({duration});
instance.hide();
if (!instance.state.isDestroyed) {
instance.setProps({duration: originalDuration});
}
}
});
};
================================================
FILE: src/plugins/animateFill.ts
================================================
import {BACKDROP_CLASS} from '../constants';
import {div, setVisibilityState} from '../dom-utils';
import {getChildren} from '../template';
import {AnimateFill} from '../types';
import {errorWhen} from '../validation';
const animateFill: AnimateFill = {
name: 'animateFill',
defaultValue: false,
fn(instance) {
// @ts-ignore
if (!instance.props.render?.$$tippy) {
if (__DEV__) {
errorWhen(
instance.props.animateFill,
'The `animateFill` plugin requires the default render function.'
);
}
return {};
}
const {box, content} = getChildren(instance.popper);
const backdrop = instance.props.animateFill
? createBackdropElement()
: null;
return {
onCreate(): void {
if (backdrop) {
box.insertBefore(backdrop, box.firstElementChild!);
box.setAttribute('data-animatefill', '');
box.style.overflow = 'hidden';
instance.setProps({arrow: false, animation: 'shift-away'});
}
},
onMount(): void {
if (backdrop) {
const {transitionDuration} = box.style;
const duration = Number(transitionDuration.replace('ms', ''));
// The content should fade in after the backdrop has mostly filled the
// tooltip element. `clip-path` is the other alternative but is not
// well-supported and is buggy on some devices.
content.style.transitionDelay = `${Math.round(duration / 10)}ms`;
backdrop.style.transitionDuration = transitionDuration;
setVisibilityState([backdrop], 'visible');
}
},
onShow(): void {
if (backdrop) {
backdrop.style.transitionDuration = '0ms';
}
},
onHide(): void {
if (backdrop) {
setVisibilityState([backdrop], 'hidden');
}
},
};
},
};
export default animateFill;
function createBackdropElement(): HTMLDivElement {
const backdrop = div();
backdrop.className = BACKDROP_CLASS;
setVisibilityState([backdrop], 'hidden');
return backdrop;
}
================================================
FILE: src/plugins/followCursor.ts
================================================
import {getOwnerDocument, isMouseEvent} from '../dom-utils';
import {FollowCursor, Instance} from '../types';
let mouseCoords = {clientX: 0, clientY: 0};
let activeInstances: Array<{instance: Instance; doc: Document}> = [];
function storeMouseCoords({clientX, clientY}: MouseEvent): void {
mouseCoords = {clientX, clientY};
}
function addMouseCoordsListener(doc: Document): void {
doc.addEventListener('mousemove', storeMouseCoords);
}
function removeMouseCoordsListener(doc: Document): void {
doc.removeEventListener('mousemove', storeMouseCoords);
}
const followCursor: FollowCursor = {
name: 'followCursor',
defaultValue: false,
fn(instance) {
const reference = instance.reference;
const doc = getOwnerDocument(instance.props.triggerTarget || reference);
let isInternalUpdate = false;
let wasFocusEvent = false;
let isUnmounted = true;
let prevProps = instance.props;
function getIsInitialBehavior(): boolean {
return (
instance.props.followCursor === 'initial' && instance.state.isVisible
);
}
function addListener(): void {
doc.addEventListener('mousemove', onMouseMove);
}
function removeListener(): void {
doc.removeEventListener('mousemove', onMouseMove);
}
function unsetGetReferenceClientRect(): void {
isInternalUpdate = true;
instance.setProps({getReferenceClientRect: null});
isInternalUpdate = false;
}
function onMouseMove(event: MouseEvent): void {
// If the instance is interactive, avoid updating the position unless it's
// over the reference element
const isCursorOverReference = event.target
? reference.contains(event.target as Node)
: true;
const {followCursor} = instance.props;
const {clientX, clientY} = event;
const rect = reference.getBoundingClientRect();
const relativeX = clientX - rect.left;
const relativeY = clientY - rect.top;
if (isCursorOverReference || !instance.props.interactive) {
instance.setProps({
// @ts-ignore - unneeded DOMRect properties
getReferenceClientRect() {
const rect = reference.getBoundingClientRect();
let x = clientX;
let y = clientY;
if (followCursor === 'initial') {
x = rect.left + relativeX;
y = rect.top + relativeY;
}
const top = followCursor === 'horizontal' ? rect.top : y;
const right = followCursor === 'vertical' ? rect.right : x;
const bottom = followCursor === 'horizontal' ? rect.bottom : y;
const left = followCursor === 'vertical' ? rect.left : x;
return {
width: right - left,
height: bottom - top,
top,
right,
bottom,
left,
};
},
});
}
}
function create(): void {
if (instance.props.followCursor) {
activeInstances.push({instance, doc});
addMouseCoordsListener(doc);
}
}
function destroy(): void {
activeInstances = activeInstances.filter(
(data) => data.instance !== instance
);
if (activeInstances.filter((data) => data.doc === doc).length === 0) {
removeMouseCoordsListener(doc);
}
}
return {
onCreate: create,
onDestroy: destroy,
onBeforeUpdate(): void {
prevProps = instance.props;
},
onAfterUpdate(_, {followCursor}): void {
if (isInternalUpdate) {
return;
}
if (
followCursor !== undefined &&
prevProps.followCursor !== followCursor
) {
destroy();
if (followCursor) {
create();
if (
instance.state.isMounted &&
!wasFocusEvent &&
!getIsInitialBehavior()
) {
addListener();
}
} else {
removeListener();
unsetGetReferenceClientRect();
}
}
},
onMount(): void {
if (instance.props.followCursor && !wasFocusEvent) {
if (isUnmounted) {
onMouseMove(mouseCoords as MouseEvent);
isUnmounted = false;
}
if (!getIsInitialBehavior()) {
addListener();
}
}
},
onTrigger(_, event): void {
if (isMouseEvent(event)) {
mouseCoords = {clientX: event.clientX, clientY: event.clientY};
}
wasFocusEvent = event.type === 'focus';
},
onHidden(): void {
if (instance.props.followCursor) {
unsetGetReferenceClientRect();
removeListener();
isUnmounted = true;
}
},
};
},
};
export default followCursor;
================================================
FILE: src/plugins/inlinePositioning.ts
================================================
import {Modifier, Placement} from '@popperjs/core';
import {isMouseEvent} from '../dom-utils';
import {BasePlacement, InlinePositioning, Props} from '../types';
import {arrayFrom, getBasePlacement} from '../utils';
function getProps(props: Props, modifier: Modifier<any, any>): Partial<Props> {
return {
popperOptions: {
...props.popperOptions,
modifiers: [
...(props.popperOptions?.modifiers || []).filter(
({name}) => name !== modifier.name
),
modifier,
],
},
};
}
const inlinePositioning: InlinePositioning = {
name: 'inlinePositioning',
defaultValue: false,
fn(instance) {
const {reference} = instance;
function isEnabled(): boolean {
return !!instance.props.inlinePositioning;
}
let placement: Placement;
let cursorRectIndex = -1;
let isInternalUpdate = false;
let triedPlacements: Array<string> = [];
const modifier: Modifier<
'tippyInlinePositioning',
Record<string, unknown>
> = {
name: 'tippyInlinePositioning',
enabled: true,
phase: 'afterWrite',
fn({state}) {
if (isEnabled()) {
if (triedPlacements.indexOf(state.placement) !== -1) {
triedPlacements = [];
}
if (
placement !== state.placement &&
triedPlacements.indexOf(state.placement) === -1
) {
triedPlacements.push(state.placement);
instance.setProps({
// @ts-ignore - unneeded DOMRect properties
getReferenceClientRect: () =>
getReferenceClientRect(state.placement),
});
}
placement = state.placement;
}
},
};
function getReferenceClientRect(placement: Placement): Partial<DOMRect> {
return getInlineBoundingClientRect(
getBasePlacement(placement),
reference.getBoundingClientRect(),
arrayFrom(reference.getClientRects()),
cursorRectIndex
);
}
function setInternalProps(partialProps: Partial<Props>): void {
isInternalUpdate = true;
instance.setProps(partialProps);
isInternalUpdate = false;
}
function addModifier(): void {
if (!isInternalUpdate) {
setInternalProps(getProps(instance.props, modifier));
}
}
return {
onCreate: addModifier,
onAfterUpdate: addModifier,
onTrigger(_, event): void {
if (isMouseEvent(event)) {
const rects = arrayFrom(instance.reference.getClientRects());
const cursorRect = rects.find(
(rect) =>
rect.left - 2 <= event.clientX &&
rect.right + 2 >= event.clientX &&
rect.top - 2 <= event.clientY &&
rect.bottom + 2 >= event.clientY
);
const index = rects.indexOf(cursorRect);
cursorRectIndex = index > -1 ? index : cursorRectIndex;
}
},
onHidden(): void {
cursorRectIndex = -1;
},
};
},
};
export default inlinePositioning;
export function getInlineBoundingClientRect(
currentBasePlacement: BasePlacement | null,
boundingRect: DOMRect,
clientRects: DOMRect[],
cursorRectIndex: number
): {
top: number;
bottom: number;
left: number;
right: number;
width: number;
height: number;
} {
// Not an inline element, or placement is not yet known
if (clientRects.length < 2 || currentBasePlacement === null) {
return boundingRect;
}
// There are two rects and they are disjoined
if (
clientRects.length === 2 &&
cursorRectIndex >= 0 &&
clientRects[0].left > clientRects[1].right
) {
return clientRects[cursorRectIndex] || boundingRect;
}
switch (currentBasePlacement) {
case 'top':
case 'bottom': {
const firstRect = clientRects[0];
const lastRect = clientRects[clientRects.length - 1];
const isTop = currentBasePlacement === 'top';
const top = firstRect.top;
const bottom = lastRect.bottom;
const left = isTop ? firstRect.left : lastRect.left;
const right = isTop ? firstRect.right : lastRect.right;
const width = right - left;
const height = bottom - top;
return {top, bottom, left, right, width, height};
}
case 'left':
case 'right': {
const minLeft = Math.min(...clientRects.map((rects) => rects.left));
const maxRight = Math.max(...clientRects.map((rects) => rects.right));
const measureRects = clientRects.filter((rect) =>
currentBasePlacement === 'left'
? rect.left === minLeft
: rect.right === maxRight
);
const top = measureRects[0].top;
const bottom = measureRects[measureRects.length - 1].bottom;
const left = minLeft;
const right = maxRight;
const width = right - left;
const height = bottom - top;
return {top, bottom, left, right, width, height};
}
default: {
return boundingRect;
}
}
}
================================================
FILE: src/plugins/sticky.ts
================================================
import {VirtualElement} from '@popperjs/core';
import {ReferenceElement, Sticky} from '../types';
const sticky: Sticky = {
name: 'sticky',
defaultValue: false,
fn(instance) {
const {reference, popper} = instance;
function getReference(): ReferenceElement | VirtualElement {
return instance.popperInstance
? instance.popperInstance.state.elements.reference
: reference;
}
function shouldCheck(value: 'reference' | 'popper'): boolean {
return instance.props.sticky === true || instance.props.sticky === value;
}
let prevRefRect: ClientRect | null = null;
let prevPopRect: ClientRect | null = null;
function updatePosition(): void {
const currentRefRect = shouldCheck('reference')
? getReference().getBoundingClientRect()
: null;
const currentPopRect = shouldCheck('popper')
? popper.getBoundingClientRect()
: null;
if (
(currentRefRect && areRectsDifferent(prevRefRect, currentRefRect)) ||
(currentPopRect && areRectsDifferent(prevPopRect, currentPopRect))
) {
if (instance.popperInstance) {
instance.popperInstance.update();
}
}
prevRefRect = currentRefRect;
prevPopRect = currentPopRect;
if (instance.state.isMounted) {
requestAnimationFrame(updatePosition);
}
}
return {
onMount(): void {
if (instance.props.sticky) {
updatePosition();
}
},
};
},
};
export default sticky;
function areRectsDifferent(
rectA: ClientRect | null,
rectB: ClientRect | null
): boolean {
if (rectA && rectB) {
return (
rectA.top !== rectB.top ||
rectA.right !== rectB.right ||
rectA.bottom !== rectB.bottom ||
rectA.left !== rectB.left
);
}
return true;
}
================================================
FILE: src/props.ts
================================================
import {DefaultProps, Plugin, Props, ReferenceElement, Tippy} from './types';
import {
hasOwnProperty,
removeProperties,
invokeWithArgsOrReturn,
} from './utils';
import {warnWhen} from './validation';
import {TIPPY_DEFAULT_APPEND_TO} from './constants';
const pluginProps = {
animateFill: false,
followCursor: false,
inlinePositioning: false,
sticky: false,
};
const renderProps = {
allowHTML: false,
animation: 'fade',
arrow: true,
content: '',
inertia: false,
maxWidth: 350,
role: 'tooltip',
theme: '',
zIndex: 9999,
};
export const defaultProps: DefaultProps = {
appendTo: TIPPY_DEFAULT_APPEND_TO,
aria: {
content: 'auto',
expanded: 'auto',
},
delay: 0,
duration: [300, 250],
getReferenceClientRect: null,
hideOnClick: true,
ignoreAttributes: false,
interactive: false,
interactiveBorder: 2,
interactiveDebounce: 0,
moveTransition: '',
offset: [0, 10],
onAfterUpdate() {},
onBeforeUpdate() {},
onCreate() {},
onDestroy() {},
onHidden() {},
onHide() {},
onMount() {},
onShow() {},
onShown() {},
onTrigger() {},
onUntrigger() {},
onClickOutside() {},
placement: 'top',
plugins: [],
popperOptions: {},
render: null,
showOnCreate: false,
touch: true,
trigger: 'mouseenter focus',
triggerTarget: null,
...pluginProps,
...renderProps,
};
const defaultKeys = Object.keys(defaultProps);
export const setDefaultProps: Tippy['setDefaultProps'] = (partialProps) => {
/* istanbul ignore else */
if (__DEV__) {
const plugins = defaultProps.plugins.concat(partialProps.plugins || []);
validateProps(partialProps, plugins);
}
const keys = Object.keys(partialProps) as Array<keyof DefaultProps>;
keys.forEach((key) => {
(defaultProps as any)[key] = partialProps[key];
});
};
export function getExtendedPassedProps(
passedProps: Partial<Props> & Record<string, unknown>
): Partial<Props> {
const plugins = passedProps.plugins || [];
const pluginProps = plugins.reduce<Record<string, unknown>>((acc, plugin) => {
const {name, defaultValue} = plugin;
if (name) {
acc[name] =
passedProps[name] !== undefined
? passedProps[name]
: (defaultProps as any)[name] ?? defaultValue;
}
return acc;
}, {});
return {
...passedProps,
...pluginProps,
};
}
export function getDataAttributeProps(
reference: ReferenceElement,
plugins: Plugin[]
): Record<string, unknown> {
const propKeys = plugins
? Object.keys(getExtendedPassedProps({...defaultProps, plugins}))
: defaultKeys;
const props = propKeys.reduce(
(acc: Partial<Props> & Record<string, unknown>, key) => {
const valueAsString = (
reference.getAttribute(`data-tippy-${key}`) || ''
).trim();
if (!valueAsString) {
return acc;
}
if (key === 'content') {
acc[key] = valueAsString;
} else {
try {
acc[key] = JSON.parse(valueAsString);
} catch (e) {
acc[key] = valueAsString;
}
}
return acc;
},
{}
);
return props;
}
export function evaluateProps(
reference: ReferenceElement,
props: Props
): Props {
const out = {
...props,
content: invokeWithArgsOrReturn(props.content, [reference]),
...(props.ignoreAttributes
? {}
: getDataAttributeProps(reference, props.plugins)),
};
out.aria = {
...defaultProps.aria,
...out.aria,
};
out.aria = {
expanded:
out.aria.expanded === 'auto' ? props.interactive : out.aria.expanded,
content:
out.aria.content === 'auto'
? props.interactive
? null
: 'describedby'
: out.aria.content,
};
return out;
}
export function validateProps(
partialProps: Partial<Props> = {},
plugins: Plugin[] = []
): void {
const keys = Object.keys(partialProps) as Array<keyof Props>;
keys.forEach((prop) => {
const nonPluginProps = removeProperties(
defaultProps,
Object.keys(pluginProps)
);
let didPassUnknownProp = !hasOwnProperty(nonPluginProps, prop);
// Check if the prop exists in `plugins`
if (didPassUnknownProp) {
didPassUnknownProp =
plugins.filter((plugin) => plugin.name === prop).length === 0;
}
warnWhen(
didPassUnknownProp,
[
`\`${prop}\``,
"is not a valid prop. You may have spelled it incorrectly, or if it's",
'a plugin, forgot to pass it in an array as props.plugins.',
'\n\n',
'All props: https://atomiks.github.io/tippyjs/v6/all-props/\n',
'Plugins: https://atomiks.github.io/tippyjs/v6/plugins/',
].join(' ')
);
});
}
================================================
FILE: src/scss/_mixins.scss
================================================
@mixin backdrop-transform-enter($placement) {
$scale: 1;
@if ($placement == 'top') {
transform: scale($scale) translate(-50%, -55%);
} @else if ($placement == 'bottom') {
transform: scale($scale) translate(-50%, -45%);
} @else if ($placement == 'left') {
transform: scale($scale) translate(-50%, -50%);
} @else if ($placement == 'right') {
transform: scale($scale) translate(-50%, -50%);
}
}
@mixin backdrop-transform-leave($placement) {
$scale: 0.2;
@if ($placement == 'top') {
transform: scale($scale) translate(-50%, -45%);
} @else if ($placement == 'bottom') {
transform: scale($scale) translate(-50%, 0);
} @else if ($placement == 'left') {
transform: scale($scale) translate(-75%, -50%);
} @else if ($placement == 'right') {
transform: scale($scale) translate(-25%, -50%);
}
}
================================================
FILE: src/scss/_vars.scss
================================================
$namespace-prefix: '__NAMESPACE_PREFIX__' !default;
$placements: 'top', 'bottom', 'left', 'right';
$origins: bottom, top, right, left;
$backdrop-origins: 0% 25%, 0% -50%, 50% 0%, -50% 0%;
$backdrop-border-radii: 40% 40% 0 0, 0 0 30% 30%, 50% 0 0 50%, 0 50% 50% 0;
$arrow-size: 16px;
================================================
FILE: src/scss/animations/fade.scss
================================================
@import '../_mixins.scss';
@import '../_vars.scss';
.#{$namespace-prefix}-box {
&[data-animation='fade'][data-state='hidden'] {
opacity: 0;
}
}
================================================
FILE: src/scss/animations/perspective-extreme.scss
================================================
@import '../_mixins.scss';
@import '../_vars.scss';
@mixin visible-transform($placement) {
@if ($placement == 'top') {
transform: perspective(700px);
} @else if ($placement == 'bottom') {
transform: perspective(700px);
} @else if ($placement == 'left') {
transform: perspective(700px);
} @else if ($placement == 'right') {
transform: perspective(700px);
}
}
@mixin hidden-transform($placement) {
@if ($placement == 'top') {
transform: perspective(700px) translateY(10px) rotateX(90deg);
} @else if ($placement == 'bottom') {
transform: perspective(700px) translateY(-10px) rotateX(-90deg);
} @else if ($placement == 'left') {
transform: perspective(700px) translateX(10px) rotateY(-90deg);
} @else if ($placement == 'right') {
transform: perspective(700px) translateX(-10px) rotateY(90deg);
}
}
.#{$namespace-prefix}-box {
&[data-animation='perspective-extreme'] {
@each $placement in $placements {
&[data-placement^='#{$placement}'] {
transform-origin: nth($origins, index($placements, $placement));
&[data-state='visible'] {
@include visible-transform($placement);
}
&[data-state='hidden'] {
@include hidden-transform($placement);
}
}
}
&[data-state='hidden'] {
opacity: 0.5;
}
}
}
================================================
FILE: src/scss/animations/perspective-subtle.scss
================================================
@import '../_mixins.scss';
@import '../_vars.scss';
@mixin visible-transform($placement) {
@if ($placement == 'top') {
transform: perspective(700px);
} @else if ($placement == 'bottom') {
transform: perspective(700px);
} @else if ($placement == 'left') {
transform: perspective(700px);
} @else if ($placement == 'right') {
transform: perspective(700px);
}
}
@mixin hidden-transform($placement) {
@if ($placement == 'top') {
transform: perspective(700px) translateY(5px) rotateX(30deg);
} @else if ($placement == 'bottom') {
transform: perspective(700px) translateY(-5px) rotateX(-30deg);
} @else if ($placement == 'left') {
transform: perspective(700px) translateX(5px) rotateY(-30deg);
} @else if ($placement == 'right') {
transform: perspective(700px) translateX(-5px) rotateY(30deg);
}
}
.#{$namespace-prefix}-box {
&[data-animation='perspective-subtle'] {
@each $placement in $placements {
&[data-placement^='#{$placement}'] {
transform-origin: nth($origins, index($placements, $placement));
&[data-state='visible'] {
@include visible-transform($placement);
}
&[data-state='hidden'] {
@include hidden-transform($placement);
}
}
}
&[data-state='hidden'] {
opacity: 0;
}
}
}
================================================
FILE: src/scss/animations/perspective.scss
================================================
@import '../_mixins.scss';
@import '../_vars.scss';
@mixin visible-transform($placement) {
@if ($placement == 'top') {
transform: perspective(700px);
} @else if ($placement == 'bottom') {
transform: perspective(700px);
} @else if ($placement == 'left') {
transform: perspective(700px);
} @else if ($placement == 'right') {
transform: perspective(700px);
}
}
@mixin hidden-transform($placement) {
@if ($placement == 'top') {
transform: perspective(700px) translateY(8px) rotateX(60deg);
} @else if ($placement == 'bottom') {
transform: perspective(700px) translateY(-8px) rotateX(-60deg);
} @else if ($placement == 'left') {
transform: perspective(700px) translateX(8px) rotateY(-60deg);
} @else if ($placement == 'right') {
transform: perspective(700px) translateX(-8px) rotateY(60deg);
}
}
.#{$namespace-prefix}-box {
&[data-animation='perspective'] {
@each $placement in $placements {
&[data-placement^='#{$placement}'] {
transform-origin: nth($origins, index($placements, $placement));
&[data-state='visible'] {
@include visible-transform($placement);
}
&[data-state='hidden'] {
@include hidden-transform($placement);
}
}
}
&[data-state='hidden'] {
opacity: 0;
}
}
}
================================================
FILE: src/scss/animations/scale-extreme.scss
================================================
@import '../_mixins.scss';
@import '../_vars.scss';
.#{$namespace-prefix}-box {
&[data-animation='scale-extreme'] {
@each $placement in $placements {
&[data-placement^='#{$placement}'] {
transform-origin: nth($origins, index($placements, $placement));
}
}
&[data-state='hidden'] {
transform: scale(0);
opacity: 0.25;
}
}
}
================================================
FILE: src/scss/animations/scale-subtle.scss
================================================
@import '../_mixins.scss';
@import '../_vars.scss';
.#{$namespace-prefix}-box {
&[data-animation='scale-subtle'] {
@each $placement in $placements {
&[data-placement^='#{$placement}'] {
transform-origin: nth($origins, index($placements, $placement));
}
}
&[data-state='hidden'] {
transform: scale(0.8);
opacity: 0;
}
}
}
================================================
FILE: src/scss/animations/scale.scss
================================================
@import '../_mixins.scss';
@import '../_vars.scss';
.#{$namespace-prefix}-box {
&[data-animation='scale'] {
@each $placement in $placements {
&[data-placement^='#{$placement}'] {
transform-origin: nth($origins, index($placements, $placement));
}
}
&[data-state='hidden'] {
transform: scale(0.5);
opacity: 0;
}
}
}
================================================
FILE: src/scss/animations/shift-away-extreme.scss
================================================
@import '../_mixins.scss';
@import '../_vars.scss';
@mixin hidden-transform($placement) {
@if ($placement == 'top') {
transform: translateY(20px);
} @else if ($placement == 'bottom') {
transform: translateY(-20px);
} @else if ($placement == 'left') {
transform: translateX(20px);
} @else if ($placement == 'right') {
transform: translateX(-20px);
}
}
.#{$namespace-prefix}-box {
&[data-animation='shift-away-extreme'] {
&[data-state='hidden'] {
opacity: 0;
@each $placement in $placements {
&[data-placement^='#{$placement}'] {
@include hidden-transform($placement);
}
}
}
}
}
================================================
FILE: src/scss/animations/shift-away-subtle.scss
================================================
@import '../_mixins.scss';
@import '../_vars.scss';
@mixin hidden-transform($placement) {
@if ($placement == 'top') {
transform: translateY(5px);
} @else if ($placement == 'bottom') {
transform: translateY(-5px);
} @else if ($placement == 'left') {
transform: translateX(5px);
} @else if ($placement == 'right') {
transform: translateX(-5px);
}
}
.#{$namespace-prefix}-box {
&[data-animation='shift-away-subtle'] {
&[data-state='hidden'] {
opacity: 0;
@each $placement in $placements {
&[data-placement^='#{$placement}'] {
@include hidden-transform($placement);
}
}
}
}
}
================================================
FILE: src/scss/animations/shift-away.scss
================================================
@import '../_mixins.scss';
@import '../_vars.scss';
@mixin hidden-transform($placement) {
@if ($placement == 'top') {
transform: translateY(10px);
} @else if ($placement == 'bottom') {
transform: translateY(-10px);
} @else if ($placement == 'left') {
transform: translateX(10px);
} @else if ($placement == 'right') {
transform: translateX(-10px);
}
}
.#{$namespace-prefix}-box {
&[data-animation='shift-away'][data-state='hidden'] {
opacity: 0;
@each $placement in $placements {
&[data-placement^='#{$placement}'] {
@include hidden-transform($placement);
}
}
}
}
================================================
FILE: src/scss/animations/shift-toward-extreme.scss
================================================
@import '../_mixins.scss';
@import '../_vars.scss';
@mixin hidden-transform($placement) {
@if ($placement == 'top') {
transform: translateY(-20px);
} @else if ($placement == 'bottom') {
transform: translateY(20px);
} @else if ($placement == 'left') {
transform: translateX(-20px);
} @else if ($placement == 'right') {
transform: translateX(20px);
}
}
.#{$namespace-prefix}-box {
&[data-animation='shift-toward-extreme'][data-state='hidden'] {
opacity: 0;
@each $placement in $placements {
&[data-placement^='#{$placement}'] {
@include hidden-transform($placement);
}
}
}
}
================================================
FILE: src/scss/animations/shift-toward-subtle.scss
================================================
@import '../_mixins.scss';
@import '../_vars.scss';
@mixin hidden-transform($placement) {
@if ($placement == 'top') {
transform: translateY(-5px);
} @else if ($placement == 'bottom') {
transform: translateY(5px);
} @else if ($placement == 'left') {
transform: translateX(-5px);
} @else if ($placement == 'right') {
transform: translateX(5px);
}
}
.#{$namespace-prefix}-box {
&[data-animation='shift-toward-subtle'][data-state='hidden'] {
opacity: 0;
@each $placement in $placements {
&[data-placement^='#{$placement}'] {
&[data-state='hidden'] {
@include hidden-transform($placement);
}
}
}
}
}
================================================
FILE: src/scss/animations/shift-toward.scss
================================================
@import '../_mixins.scss';
@import '../_vars.scss';
@mixin hidden-transform($placement) {
@if ($placement == 'top') {
transform: translateY(-10px);
} @else if ($placement == 'bottom') {
transform: translateY(10px);
} @else if ($placement == 'left') {
transform: translateX(-10px);
} @else if ($placement == 'right') {
transform: translateX(10px);
}
}
.#{$namespace-prefix}-box {
&[data-animation='shift-toward'][data-state='hidden'] {
opacity: 0;
@each $placement in $placements {
&[data-placement^='#{$placement}'] {
@include hidden-transform($placement);
}
}
}
}
================================================
FILE: src/scss/backdrop.scss
================================================
@import './_mixins.scss';
@import './_vars.scss';
.#{$namespace-prefix}-box {
@each $placement in $placements {
&[data-placement^='#{$placement}'] {
> .#{$namespace-prefix}-backdrop {
transform-origin: nth(
$backdrop-origins,
index($placements, $placement)
);
border-radius: nth(
$backdrop-border-radii,
index($placements, $placement)
);
&[data-state='visible'] {
@include backdrop-transform-enter($placement);
}
&[data-state='hidden'] {
@include backdrop-transform-leave($placement);
}
}
}
}
}
.#{$namespace-prefix}-box {
&[data-animatefill] {
// Declared with !important so that custom themes don't need to specify
// this property.
background-color: transparent !important;
}
}
.#{$namespace-prefix}-backdrop {
position: absolute;
background-color: #333;
border-radius: 50%;
width: calc(110% + 32px);
left: 50%;
top: 50%;
z-index: -1;
transition: all cubic-bezier(0.46, 0.1, 0.52, 0.98);
backface-visibility: hidden;
&[data-state='hidden'] {
opacity: 0;
}
&::after {
content: '';
float: left;
padding-top: 100%;
}
}
.#{$namespace-prefix}-backdrop + .#{$namespace-prefix}-content {
transition-property: opacity;
will-change: opacity;
&[data-state='hidden'] {
opacity: 0;
}
}
================================================
FILE: src/scss/border.scss
================================================
@import './_vars.scss';
.#{$namespace-prefix}-box {
border: 1px transparent;
&[data-placement^='top'] > .#{$namespace-prefix}-arrow {
&::after {
border-top-color: inherit;
border-width: 8px 8px 0;
bottom: -8px;
left: 0;
}
}
&[data-placement^='bottom'] > .#{$namespace-prefix}-arrow {
&::after {
border-bottom-color: inherit;
border-width: 0 8px 8px;
top: -8px;
left: 0;
}
}
&[data-placement^='left'] > .#{$namespace-prefix}-arrow {
&::after {
border-left-color: inherit;
border-width: 8px 0 8px 8px;
right: -8px;
top: 0;
}
}
&[data-placement^='right'] > .#{$namespace-prefix}-arrow {
&::after {
border-width: 8px 8px 8px 0;
left: -8px;
top: 0;
border-right-color: inherit;
}
}
&[data-placement^='top']
> .#{$namespace-prefix}-svg-arrow
> svg:first-child:not(:last-child) {
top: 17px;
}
&[data-placement^='bottom']
> .#{$namespace-prefix}-svg-arrow
> svg:first-child:not(:last-child) {
bottom: 17px;
}
&[data-placement^='left']
> .#{$namespace-prefix}-svg-arrow
> svg:first-child:not(:last-child) {
left: 12px;
}
&[data-placement^='right']
> .#{$namespace-prefix}-svg-arrow
> svg:first-child:not(:last-child) {
right: 12px;
}
}
.#{$namespace-prefix}-arrow {
& {
border-color: inherit;
}
&::after {
content: '';
z-index: -1;
position: absolute;
border-color: transparent;
border-style: solid;
}
}
================================================
FILE: src/scss/index.scss
================================================
@import './_vars.scss';
@import './animations/fade.scss';
$color: #333;
[data-#{$namespace-prefix}-root] {
max-width: calc(100vw - 10px);
}
.#{$namespace-prefix}-box {
position: relative;
background-color: $color;
color: white;
border-radius: 4px;
font-size: 14px;
line-height: 1.4;
white-space: initial;
outline: 0;
transition-property: transform, visibility, opacity;
&[data-placement^='top'] > .#{$namespace-prefix}-arrow {
bottom: 0;
&::before {
bottom: -7px;
left: 0;
border-width: 8px 8px 0;
border-top-color: initial;
transform-origin: center top;
}
}
&[data-placement^='bottom'] > .#{$namespace-prefix}-arrow {
top: 0;
&::before {
top: -7px;
left: 0;
border-width: 0 8px 8px;
border-bottom-color: initial;
transform-origin: center bottom;
}
}
&[data-placement^='left'] > .#{$namespace-prefix}-arrow {
right: 0;
&::before {
border-width: 8px 0 8px 8px;
border-left-color: initial;
right: -7px;
transform-origin: center left;
}
}
&[data-placement^='right'] > .#{$namespace-prefix}-arrow {
left: 0;
&::before {
left: -7px;
border-width: 8px 8px 8px 0;
border-right-color: initial;
transform-origin: center right;
}
}
&[data-inertia][data-state='visible'] {
transition-timing-function: cubic-bezier(0.54, 1.5, 0.38, 1.11);
}
}
.#{$namespace-prefix}-arrow {
& {
width: $arrow-size;
height: $arrow-size;
color: $color;
}
&::before {
content: '';
position: absolute;
border-color: transparent;
border-style: solid;
}
}
.#{$namespace-prefix}-content {
position: relative;
padding: 5px 9px;
z-index: 1;
}
================================================
FILE: src/scss/svg-arrow.scss
================================================
@import './_vars.scss';
.#{$namespace-prefix}-box {
&[data-placement^='top'] > .#{$namespace-prefix}-svg-arrow {
bottom: 0;
&::after,
> svg {
top: 16px;
transform: rotate(180deg);
}
}
&[data-placement^='bottom'] > .#{$namespace-prefix}-svg-arrow {
top: 0;
> svg {
bottom: 16px;
}
}
&[data-placement^='left'] > .#{$namespace-prefix}-svg-arrow {
right: 0;
&::after,
> svg {
transform: rotate(90deg);
top: calc(50% - 3px);
left: 11px;
}
}
&[data-placement^='right'] > .#{$namespace-prefix}-svg-arrow {
left: 0;
&::after,
> svg {
transform: rotate(-90deg);
top: calc(50% - 3px);
right: 11px;
}
}
}
.#{$namespace-prefix}-svg-arrow {
position: absolute;
width: $arrow-size;
height: $arrow-size;
fill: #333;
text-align: initial;
> svg {
position: absolute;
}
}
================================================
FILE: src/scss/themes/light-border.scss
================================================
@import '../_mixins.scss';
@import '../_vars.scss';
$color: white;
$transparent-light: rgba(0, 8, 16, 0.08);
$transparent-dark: rgba(0, 8, 16, 0.15);
$transparent-darker: rgba(0, 8, 16, 0.2);
.#{$namespace-prefix}-box[data-theme~='light-border'] {
background-color: $color;
background-clip: padding-box;
border: 1px solid $transparent-dark;
color: #333;
box-shadow: 0 4px 14px -2px $transparent-light;
> .#{$namespace-prefix}-backdrop {
background-color: $color;
}
> .#{$namespace-prefix}-arrow,
> .#{$namespace-prefix}-svg-arrow {
&::after {
content: '';
position: absolute;
z-index: -1;
}
}
> .#{$namespace-prefix}-arrow::after {
border-color: transparent;
border-style: solid;
}
&[data-placement^='top'] {
> .#{$namespace-prefix}-arrow {
&::before {
border-top-color: $color;
}
&::after {
border-top-color: $transparent-darker;
border-width: 7px 7px 0;
top: $arrow-size + 1;
left: 1px;
}
}
> .#{$namespace-prefix}-svg-arrow {
> svg {
top: $arrow-size;
}
&::after {
top: $arrow-size + 1;
}
}
}
&[data-placement^='bottom'] {
> .#{$namespace-prefix}-arrow {
&::before {
border-bottom-color: $color;
bottom: $arrow-size;
}
&::after {
border-bottom-color: $transparent-darker;
border-width: 0 7px 7px;
bottom: $arrow-size + 1;
left: 1px;
}
}
> .#{$namespace-prefix}-svg-arrow {
> svg {
bottom: $arrow-size;
}
&::after {
bottom: $arrow-size + 1;
}
}
}
&[data-placement^='left'] {
> .#{$namespace-prefix}-arrow {
&::before {
border-left-color: $color;
}
&::after {
border-left-color: $transparent-darker;
border-width: 7px 0 7px 7px;
left: $arrow-size + 1;
top: 1px;
}
}
> .#{$namespace-prefix}-svg-arrow {
> svg {
left: 11px;
}
&::after {
left: 12px;
}
}
}
&[data-placement^='right'] {
> .#{$namespace-prefix}-arrow {
&::before {
border-right-color: $color;
right: $arrow-size;
}
&::after {
border-width: 7px 7px 7px 0;
right: $arrow-size + 1;
top: 1px;
border-right-color: $transparent-darker;
}
}
> .#{$namespace-prefix}-svg-arrow {
> svg {
right: 11px;
}
&::after {
right: 12px;
}
}
}
> .#{$namespace-prefix}-svg-arrow {
fill: white;
&::after {
background-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMCA2czEuNzk2LS4wMTMgNC42Ny0zLjYxNUM1Ljg1MS45IDYuOTMuMDA2IDggMGMxLjA3LS4wMDYgMi4xNDguODg3IDMuMzQzIDIuMzg1QzE0LjIzMyA2LjAwNSAxNiA2IDE2IDZIMHoiIGZpbGw9InJnYmEoMCwgOCwgMTYsIDAuMikiIC8+PC9zdmc+);
background-size: $arrow-size 6px;
width: $arrow-size;
height: 6px;
}
}
}
================================================
FILE: src/scss/themes/light.scss
================================================
@import '../_mixins.scss';
@import '../_vars.scss';
$color: white;
.#{$namespace-prefix}-box[data-theme~='light'] {
color: #26323d;
box-shadow: 0 0 20px 4px rgba(154, 161, 177, 0.15),
0 4px 80px -8px rgba(36, 40, 47, 0.25),
0 4px 4px -2px rgba(91, 94, 105, 0.15);
background-color: $color;
&[data-placement^='top'] > .#{$namespace-prefix}-arrow::before {
border-top-color: $color;
}
&[data-placement^='bottom'] > .#{$namespace-prefix}-arrow::before {
border-bottom-color: $color;
}
&[data-placement^='left'] > .#{$namespace-prefix}-arrow::before {
border-left-color: $color;
}
&[data-placement^='right'] > .#{$namespace-prefix}-arrow::before {
border-right-color: $color;
}
> .#{$namespace-prefix}-backdrop {
background-color: $color;
}
> .#{$namespace-prefix}-svg-arrow {
fill: $color;
}
}
================================================
FILE: src/scss/themes/material.scss
================================================
@import '../_mixins.scss';
@import '../_vars.scss';
$color: #505355;
.#{$namespace-prefix}-box[data-theme~='material'] {
background-color: $color;
font-weight: 600;
&[data-placement^='top'] > .#{$namespace-prefix}-arrow::before {
border-top-color: $color;
}
&[data-placement^='bottom'] > .#{$namespace-prefix}-arrow::before {
border-bottom-color: $color;
}
&[data-placement^='left'] > .#{$namespace-prefix}-arrow::before {
border-left-color: $color;
}
&[data-placement^='right'] > .#{$namespace-prefix}-arrow::before {
border-right-color: $color;
}
> .#{$namespace-prefix}-backdrop {
background-color: $color;
}
> .#{$namespace-prefix}-svg-arrow {
fill: $color;
}
}
================================================
FILE: src/scss/themes/translucent.scss
================================================
@import '../_mixins.scss';
@import '../_vars.scss';
$color: rgba(0, 0, 0, 0.7);
.#{$namespace-prefix}-box[data-theme~='translucent'] {
background-color: $color;
> .#{$namespace-prefix}-arrow {
width: 14px;
height: 14px;
}
/**
* We use an 8px arrow with 1px overlap by default, since some browsers (at
* least they used to) caused a 1px gap between the arrow and the popper.
* However, with the translucent theme, this causes darkening since it
* overlaps.
*/
&[data-placement^='top'] > .#{$namespace-prefix}-arrow {
&::before {
border-width: 7px 7px 0;
border-top-color: $color;
}
}
&[data-placement^='bottom'] > .#{$namespace-prefix}-arrow {
&::before {
border-width: 0 7px 7px;
border-bottom-color: $color;
}
}
&[data-placement^='left'] > .#{$namespace-prefix}-arrow {
&::before {
border-width: 7px 0 7px 7px;
border-left-color: $color;
}
}
&[data-placement^='right'] > .#{$namespace-prefix}-arrow {
&::before {
border-width: 7px 7px 7px 0;
border-right-color: $color;
}
}
> .#{$namespace-prefix}-backdrop {
background-color: $color;
}
> .#{$namespace-prefix}-svg-arrow {
fill: $color;
}
}
================================================
FILE: src/template.ts
================================================
import {
ARROW_CLASS,
BACKDROP_CLASS,
BOX_CLASS,
CONTENT_CLASS,
SVG_ARROW_CLASS,
} from './constants';
import {div, isElement} from './dom-utils';
import {Instance, PopperElement, Props} from './types';
import {PopperChildren} from './types-internal';
import {arrayFrom} from './utils';
// Firefox extensions don't allow .innerHTML = "..." property. This tricks it.
const innerHTML = (): 'innerHTML' => 'innerHTML';
function dangerouslySetInnerHTML(element: Element, html: string): void {
element[innerHTML()] = html;
}
function createArrowElement(value: Props['arrow']): HTMLDivElement {
const arrow = div();
if (value === true) {
arrow.className = ARROW_CLASS;
} else {
arrow.className = SVG_ARROW_CLASS;
if (isElement(value)) {
arrow.appendChild(value);
} else {
dangerouslySetInnerHTML(arrow, value as string);
}
}
return arrow;
}
export function setContent(content: HTMLDivElement, props: Props): void {
if (isElement(props.content)) {
dangerouslySetInnerHTML(content, '');
content.appendChild(props.content);
} else if (typeof props.content !== 'function') {
if (props.allowHTML) {
dangerouslySetInnerHTML(content, props.content);
} else {
content.textContent = props.content;
}
}
}
export function getChildren(popper: PopperElement): PopperChildren {
const box = popper.firstElementChild as HTMLDivElement;
const boxChildren = arrayFrom(box.children);
return {
box,
content: boxChildren.find((node) => node.classList.contains(CONTENT_CLASS)),
arrow: boxChildren.find(
(node) =>
node.classList.contains(ARROW_CLASS) ||
node.classList.contains(SVG_ARROW_CLASS)
),
backdrop: boxChildren.find((node) =>
node.classList.contains(BACKDROP_CLASS)
),
};
}
export function render(
instance: Instance
): {
popper: PopperElement;
onUpdate?: (prevProps: Props, nextProps: Props) => void;
} {
const popper = div();
const box = div();
box.className = BOX_CLASS;
box.setAttribute('data-state', 'hidden');
box.setAttribute('tabindex', '-1');
const content = div();
content.className = CONTENT_CLASS;
content.setAttribute('data-state', 'hidden');
setContent(content, instance.props);
popper.appendChild(box);
box.appendChild(content);
onUpdate(instance.props, instance.props);
function onUpdate(prevProps: Props, nextProps: Props): void {
const {box, content, arrow} = getChildren(popper);
if (nextProps.theme) {
box.setAttribute('data-theme', nextProps.theme);
} else {
box.removeAttribute('data-theme');
}
if (typeof nextProps.animation === 'string') {
box.setAttribute('data-animation', nextProps.animation);
} else {
box.removeAttribute('data-animation');
}
if (nextProps.inertia) {
box.setAttribute('data-inertia', '');
} else {
box.removeAttribute('data-inertia');
}
box.style.maxWidth =
typeof nextProps.maxWidth === 'number'
? `${nextProps.maxWidth}px`
: nextProps.maxWidth;
if (nextProps.role) {
box.setAttribute('role', nextProps.role);
} else {
box.removeAttribute('role');
}
if (
prevProps.content !== nextProps.content ||
prevProps.allowHTML !== nextProps.allowHTML
) {
setContent(content, instance.props);
}
if (nextProps.arrow) {
if (!arrow) {
box.appendChild(createArrowElement(nextProps.arrow));
} else if (prevProps.arrow !== nextProps.arrow) {
box.removeChild(arrow);
box.appendChild(createArrowElement(nextProps.arrow));
}
} else if (arrow) {
box.removeChild(arrow!);
}
}
return {
popper,
onUpdate,
};
}
// Runtime check to identify if the render function is the default one; this
// way we can apply default CSS transitions logic and it can be tree-shaken away
render.$$tippy = true;
================================================
FILE: src/types-internal.ts
================================================
import {State} from '@popperjs/core';
import {Props} from './types';
export interface ListenerObject {
node: Element;
eventType: string;
handler: EventListenerOrEventListenerObject;
options: boolean | Record<string, unknown>;
}
export interface PopperTreeData {
popperRect: ClientRect;
popperState: State;
props: Props;
}
export interface PopperChildren {
box: HTMLDivElement;
content: HTMLDivElement;
arrow?: HTMLDivElement;
backdrop?: HTMLDivElement;
}
================================================
FILE: src/types.ts
================================================
import * as Popper from '@popperjs/core';
export type BasePlacement = Popper.BasePlacement;
export type Placement = Popper.Placement;
export type Content =
| string
| Element
| DocumentFragment
| ((ref: Element) => string | Element | DocumentFragment);
export type SingleTarget = Element;
export type MultipleTargets = string | Element[] | NodeList;
export type Targets = SingleTarget | MultipleTargets;
export interface ReferenceElement<TProps = Props> extends Element {
_tippy?: Instance<TProps>;
}
export interface PopperElement<TProps = Props> extends HTMLDivElement {
_tippy?: Instance<TProps>;
}
export interface LifecycleHooks<TProps = Props> {
onAfterUpdate(
instance: Instance<TProps>,
partialProps: Partial<TProps>
): void;
onBeforeUpdate(
instance: Instance<TProps>,
partialProps: Partial<TProps>
): void;
onCreate(instance: Instance<TProps>): void;
onDestroy(instance: Instance<TProps>): void;
onHidden(instance: Instance<TProps>): void;
onHide(instance: Instance<TProps>): void | false;
onMount(instance: Instance<TProps>): void;
onShow(instance: Instance<TProps>): void | false;
onShown(instance: Instance<TProps>): void;
onTrigger(instance: Instance<TProps>, event: Event): void;
onUntrigger(instance: Instance<TProps>, event: Event): void;
onClickOutside(instance: Instance<TProps>, event: Event): void;
}
export interface RenderProps {
allowHTML: boolean;
animation: string | boolean;
arrow: boolean | string | SVGElement | DocumentFragment;
content: Content;
inertia: boolean;
maxWidth: number | string;
role: string;
theme: string;
zIndex: number;
}
export interface GetReferenceClientRect {
(): ClientRect | DOMRect;
contextElement?: Element;
}
export interface Props extends LifecycleHooks, RenderProps {
animateFill: boolean;
appendTo: 'parent' | Element | ((ref: Element) => Element);
aria: {
content?: 'auto' | 'describedby' | 'labelledby' | null;
expanded?: 'auto' | boolean;
};
delay: number | [number | null, number | null];
duration: number | [number | null, number | null];
followCursor: boolean | 'horizontal' | 'vertical' | 'initial';
getReferenceClientRect: null | GetReferenceClientRect;
hideOnClick: boolean | 'toggle';
ignoreAttributes: boolean;
inlinePositioning: boolean;
interactive: boolean;
interactiveBorder: number;
interactiveDebounce: number;
moveTransition: string;
offset:
| [number, number]
| (({
placement,
popper,
reference,
}: {
placement: Placement;
popper: Popper.Rect;
reference: Popper.Rect;
}) => [number, number]);
placement: Placement;
plugins: Plugin<unknown>[];
popperOptions: Partial<Popper.Options>;
render:
| ((
instance: Instance
) => {
popper: PopperElement;
onUpdate?: (prevProps: Props, nextProps: Props) => void;
})
| null;
showOnCreate: boolean;
sticky: boolean | 'reference' | 'popper';
touch: boolean | 'hold' | ['hold', number];
trigger: string;
triggerTarget: Element | Element[] | null;
}
export interface DefaultProps extends Omit<Props, 'delay' | 'duration'> {
delay: number | [number, number];
duration: number | [number, number];
}
export interface Instance<TProps = Props> {
clearDelayTimeouts(): void;
destroy(): void;
disable(): void;
enable(): void;
hide(): void;
hideWithInteractivity(event: MouseEvent): void;
id: number;
plugins: Plugin<TProps>[];
popper: PopperElement<TProps>;
popperInstance: Popper.Instance | null;
props: TProps;
reference: ReferenceElement<TProps>;
setContent(content: Content): void;
setProps(partialProps: Partial<TProps>): void;
show(): void;
state: {
isEnabled: boolean;
isVisible: boolean;
isDestroyed: boolean;
isMounted: boolean;
isShown: boolean;
};
unmount(): void;
}
export interface TippyStatics {
readonly currentInput: {isTouch: boolean};
readonly defaultProps: DefaultProps;
setDefaultProps(partialProps: Partial<DefaultProps>): void;
}
export interface Tippy<TProps = Props> extends TippyStatics {
(targets: SingleTarget, optionalProps?: Partial<TProps>): Instance<TProps>;
}
export interface Tippy<TProps = Props> extends TippyStatics {
(targets: MultipleTargets, optionalProps?: Partial<TProps>): Instance<
TProps
>[];
}
declare const tippy: Tippy;
// =============================================================================
// Addon types
// =============================================================================
export interface DelegateInstance<TProps = Props> extends Instance<TProps> {
destroy(shouldDestroyTargetInstances?: boolean): void;
}
export interface Delegate<TProps = Props> {
(
targets: SingleTarget,
props: Partial<TProps> & {target: string}
): DelegateInstance<TProps>;
}
export interface Delegate<TProps = Props> {
(
targets: MultipleTargets,
props: Partial<TProps> & {target: string}
): DelegateInstance<TProps>[];
}
export type CreateSingletonProps<TProps = Props> = TProps & {
overrides: Array<keyof TProps>;
};
export type CreateSingletonInstance<TProps = CreateSingletonProps> = Instance<
TProps
> & {
setInstances(instances: Instance<any>[]): void;
show(target?: ReferenceElement | Instance | number): void;
showNext(): void;
showPrevious(): void;
};
export type CreateSingleton<TProps = Props> = (
tippyInstances: Instance<any>[],
optionalProps?: Partial<CreateSingletonProps<TProps>>
) => CreateSingletonInstance<CreateSingletonProps<TProps>>;
declare const delegate: Delegate;
declare const createSingleton: CreateSingleton;
// =============================================================================
// Plugin types
// =============================================================================
export interface Plugin<TProps = Props> {
name?: string;
defaultValue?: any;
fn(instance: Instance<TProps>): Partial<LifecycleHooks<TProps>>;
}
export interface AnimateFill extends Plugin {
name: 'animateFill';
defaultValue: false;
}
export interface FollowCursor extends Plugin {
name: 'followCursor';
defaultValue: false;
}
export interface InlinePositioning extends Plugin {
name: 'inlinePositioning';
defaultValue: false;
}
export interface Sticky extends Plugin {
name: 'sticky';
defaultValue: false;
}
declare const animateFill: AnimateFill;
declare const followCursor: FollowCursor;
declare const inlinePositioning: InlinePositioning;
declare const sticky: Sticky;
// =============================================================================
// Misc types
// =============================================================================
export interface HideAllOptions {
duration?: number;
exclude?: Instance | ReferenceElement;
}
export type HideAll = (options?: HideAllOptions) => void;
declare const hideAll: HideAll;
declare const roundArrow: string;
export default tippy;
export {
hideAll,
delegate,
createSingleton,
animateFill,
followCursor,
inlinePositioning,
sticky,
roundArrow,
};
================================================
FILE: src/utils.ts
================================================
import {BasePlacement, Placement} from './types';
export function hasOwnProperty(
obj: Record<string, unknown>,
key: string
): boolean {
return {}.hasOwnProperty.call(obj, key);
}
export function getValueAtIndexOrReturn<T>(
value: T | [T | null, T | null],
index: number,
defaultValue: T | [T, T]
): T {
if (Array.isArray(value)) {
const v = value[index];
return v == null
? Array.isArray(defaultValue)
? defaultValue[index]
: defaultValue
: v;
}
return value;
}
export function isType(value: any, type: string): boolean {
const str = {}.toString.call(value);
return str.indexOf('[object') === 0 && str.indexOf(`${type}]`) > -1;
}
export function invokeWithArgsOrReturn(value: any, args: any[]): any {
return typeof value === 'function' ? value(...args) : value;
}
export function debounce<T>(
fn: (arg: T) => void,
ms: number
): (arg: T) => void {
// Avoid wrapping in `setTimeout` if ms is 0 anyway
if (ms === 0) {
return fn;
}
let timeout: any;
return (arg): void => {
clearTimeout(timeout);
timeout = setTimeout(() => {
fn(arg);
}, ms);
};
}
export function removeProperties<T>(obj: T, keys: string[]): Partial<T> {
const clone = {...obj};
keys.forEach((key) => {
delete (clone as any)[key];
});
return clone;
}
export function splitBySpaces(value: string): string[] {
return value.split(/\s+/).filter(Boolean);
}
export function normalizeToArray<T>(value: T | T[]): T[] {
return ([] as T[]).concat(value);
}
export function pushIfUnique<T>(arr: T[], value: T): void {
if (arr.indexOf(value) === -1) {
arr.push(value);
}
}
export function appendPxIfNumber(value: string | number): string {
return typeof value === 'number' ? `${value}px` : value;
}
export function unique<T>(arr: T[]): T[] {
return arr.filter((item, index) => arr.indexOf(item) === index);
}
export function getNumber(value: string | number): number {
return typeof value === 'number' ? value : parseFloat(value);
}
export function getBasePlacement(placement: Placement): BasePlacement {
return placement.split('-')[0] as BasePlacement;
}
export function arrayFrom(value: ArrayLike<any>): any[] {
return [].slice.call(value);
}
export function removeUndefinedProps(
obj: Record<string, unknown>
): Partial<Record<string, unknown>> {
return Object.keys(obj).reduce((acc, key) => {
if (obj[key] !== undefined) {
(acc as any)[key] = obj[key];
}
return acc;
}, {});
}
================================================
FILE: src/validation.ts
================================================
import {Targets} from './types';
export function createMemoryLeakWarning(method: string): string {
const txt = method === 'destroy' ? 'n already-' : ' ';
return [
`${method}() was called on a${txt}destroyed instance. This is a no-op but`,
'indicates a potential memory leak.',
].join(' ');
}
export function clean(value: string): string {
const spacesAndTabs = /[ \t]{2,}/g;
const lineStartWithSpaces = /^[ \t]*/gm;
return value
.replace(spacesAndTabs, ' ')
.replace(lineStartWithSpaces, '')
.trim();
}
function getDevMessage(message: string): string {
return clean(`
%ctippy.js
%c${clean(message)}
%c👷 This is a development-only message. It will be removed in production.
`);
}
export function getFormattedMessage(message: string): string[] {
return [
getDevMessage(message),
// title
'color: #00C584; font-size: 1.3em; font-weight: bold;',
// message
'line-height: 1.5',
// footer
'color: #a6a095;',
];
}
// Assume warnings and errors never have the same message
let visitedMessages: Set<string>;
if (__DEV__) {
resetVisitedMessages();
}
export function resetVisitedMessages(): void {
visitedMessages = new Set();
}
export function warnWhen(condition: boolean, message: string): void {
if (condition && !visitedMessages.has(message)) {
visitedMessages.add(message);
console.warn(...getFormattedMessage(message));
}
}
export function errorWhen(condition: boolean, message: string): void {
if (condition && !visitedMessages.has(message)) {
visitedMessages.add(message);
console.error(...getFormattedMessage(message));
}
}
export function validateTargets(targets: Targets): void {
const didPassFalsyValue = !targets;
const didPassPlainObject =
Object.prototype.toString.call(targets) === '[object Object]' &&
!(targets as any).addEventListener;
errorWhen(
didPassFalsyValue,
[
'tippy() was passed',
'`' + String(targets) + '`',
'as its targets (first) argument. Valid types are: String, Element,',
'Element[], or NodeList.',
].join(' ')
);
errorWhen(
didPassPlainObject,
[
'tippy() was passed a plain object which is not supported as an argument',
'for virtual positioning. Use props.getReferenceClientRect instead.',
].join(' ')
);
}
================================================
FILE: test/functional/border.test.js
================================================
/**
* @jest-environment puppeteer
*/
import {navigateToTest, screenshotTest} from '../utils';
describe('border', () => {
it('borders are correctly inherited and SVG styles are correct', async () => {
const page = await browser.newPage();
await page.setViewport({width: 1200, height: 800});
await page.goto('http://host.docker.internal:5000');
await navigateToTest(page, 'border');
expect(await screenshotTest(page, 'border')).toMatchImageSnapshot();
});
});
================================================
FILE: test/functional/followCursor.test.js
================================================
/**
* @jest-environment puppeteer
*/
import {navigateToTest, screenshotTest} from '../utils';
function generateSelector(test) {
return `#followCursor [data-test="${test}"]`;
}
describe('followCursor', () => {
describe('true', () => {
it('follows the cursor on both axes', async () => {
const selector = generateSelector(true);
const page = await browser.newPage();
await page.setViewport({width: 1200, height: 800});
await page.goto('http://host.docker.internal:5000');
await navigateToTest(page, 'followCursor');
const reference = await page.$(selector);
const rect = await page.evaluate((ref) => {
const {top, left} = ref.getBoundingClientRect();
return {top, left};
}, reference);
await page.hover(selector);
await page.waitFor(60);
await page.mouse.move(rect.left + 15, rect.top + 20);
expect(await screenshotTest(page, 'followCursor')).toMatchImageSnapshot();
});
});
it('stays at cursor when content changes', async () => {
const selector = generateSelector('contentChange');
const page = await browser.newPage();
await page.setViewport({width: 1200, height: 800});
await page.goto('http://host.docker.internal:5000');
await navigateToTest(page, 'followCursor');
await page.hover(selector);
await page.waitFor(150);
expect(await screenshotTest(page, 'followCursor')).toMatchImageSnapshot();
});
describe('false', () => {
it('does not follow the cursor at all', async () => {
const selector = generateSelector(false);
const page = await browser.newPage();
await page.setViewport({width: 1200, height: 800});
await page.goto('http://host.docker.internal:5000');
await navigateToTest(page, 'followCursor');
const reference = await page.$(selector);
const rect = await page.evaluate((ref) => {
const {top, left} = ref.getBoundingClientRect();
return {top, left};
}, reference);
await page.hover(selector);
await page.waitFor(60);
await page.mouse.move(rect.left + 15, rect.top + 20);
expect(await screenshotTest(page, 'followCursor')).toMatchImageSnapshot();
});
});
describe('horizontal', () => {
it('follows the cursor only on the horizontal axis', async () => {
const selector = generateSelector('horizontal');
const page = await browser.newPage();
await page.setViewport({width: 1200, height: 800});
await page.goto('http://host.docker.internal:5000');
await navigateToTest(page, 'followCursor');
const reference = await page.$(selector);
const rect = await page.evaluate((ref) => {
const {top, left} = ref.getBoundingClientRect();
return {top, left};
}, reference);
await page.hover(selector);
await page.waitFor(60);
await page.mouse.move(rect.left + 15, rect.top + 20);
expect(await screenshotTest(page, 'followCursor')).toMatchImageSnapshot();
});
});
describe('vertical', () => {
it('follows the cursor only on the vertical axis', async () => {
const selector = generateSelector('vertical');
const page = await browser.newPage();
await page.setViewport({width: 1200, height: 800});
await page.goto('http://host.docker.internal:5000');
await navigateToTest(page, 'followCursor');
const reference = await page.$(selector);
const rect = await page.evaluate((ref) => {
const {top, left} = ref.getBoundingClientRect();
return {top, left};
}, reference);
await page.hover(selector);
await page.waitFor(60);
await page.mouse.move(rect.left + 15, rect.top + 20);
expect(await screenshotTest(page, 'followCursor')).toMatchImageSnapshot();
});
});
describe('initial', () => {
it('follows the cursor only initially', async () => {
const selector = generateSelector('initial');
const page = await browser.newPage();
await page.setViewport({width: 1200, height: 800});
await page.goto('http://host.docker.internal:5000');
await navigateToTest(page, 'followCursor');
const reference = await page.$(selector);
const rect = await page.evaluate((ref) => {
const {top, left} = ref.getBoundingClientRect();
return {top, left};
}, reference);
await page.hover(selector);
await page.waitFor(60);
await page.mouse.move(rect.left, rect.top);
expect(await screenshotTest(page, 'followCursor')).toMatchImageSnapshot();
});
});
});
================================================
FILE: test/functional/inlinePositioning.test.js
================================================
/**
* @jest-environment puppeteer
*/
import {navigateToTest, screenshotTest} from '../utils';
describe('inlinePositioning', () => {
it('aligns correctly', async () => {
const page = await browser.newPage();
await page.setViewport({width: 1200, height: 800});
await page.goto('http://host.docker.internal:5000');
await navigateToTest(page, 'inlinePositioning');
expect(
await screenshotTest(page, 'inlinePositioning')
).toMatchImageSnapshot();
});
});
================================================
FILE: test/functional/sticky.test.js
================================================
/**
* @jest-environment puppeteer
*/
import {navigateToTest, screenshotTest} from '../utils';
describe('sticky', () => {
it('stays stuck to the reference element when it moves', async () => {
const page = await browser.newPage();
await page.setViewport({width: 1200, height: 800});
await page.goto('http://host.docker.internal:5000');
await navigateToTest(page, 'sticky');
expect(await screenshotTest(page, 'sticky')).toMatchImageSnapshot();
});
});
================================================
FILE: test/functional/themes.test.js
================================================
/**
* @jest-environment puppeteer
*/
import {navigateToTest, screenshotTest} from '../utils';
describe('themes', () => {
it('all themes are correct', async () => {
const page = await browser.newPage();
await page.setViewport({width: 1200, height: 800});
await page.goto('http://host.docker.internal:5000');
await navigateToTest(page, 'themes');
expect(await screenshotTest(page, 'themes')).toMatchImageSnapshot();
});
});
================================================
FILE: test/image-reporter.js
================================================
const {red, bold} = require('colorette');
const fs = require('fs');
const poster = require('poster');
// https://api.anonymousfiles.io/
class ImageReporter {
constructor(globalConfig, options) {
this._globalConfig = globalConfig;
this._options = options;
}
onTestResult(test, testResult, aggregateResults) {
if (process.env.CI !== 'true') {
return;
}
if (
testResult.numFailingTests &&
testResult.failureMessage.match(/different from snapshot/)
) {
const files = fs.readdirSync(
'./test/functional/__image_snapshots__/__diff_output__/'
);
files.forEach(async (value) => {
const file = `./test/functional/__image_snapshots__/__diff_output__/${value}`;
poster.post(
file,
{
uploadUrl: 'https://api.anonymousfiles.io/',
fileId: 'file',
fileContentType: 'image/png',
},
(err, data) => {
if (err) {
throw err;
}
console.log(
red(bold(`Uploaded image diff file to ${JSON.parse(data).url}`))
);
}
);
});
}
}
}
module.exports = ImageReporter;
================================================
FILE: test/integration/__snapshots__/createTippy.test.js.snap
================================================
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`createTippy returns the instance with expected properties 1`] = `
Object {
"clearDelayTimeouts": [Function],
"destroy": [Function],
"disable": [Function],
"enable": [Function],
"hide": [Function],
"hideWithInteractivity": [Function],
"id": 1,
"plugins": Array [],
"popper": <div
data-__namespace_prefix__-root=""
id="__NAMESPACE_PREFIX__-1"
style="pointer-events: none; z-index: 9999;"
>
<div
class="__NAMESPACE_PREFIX__-box"
data-animation="fade"
data-state="hidden"
role="tooltip"
style="max-width: 350px;"
tabindex="-1"
>
<div
class="__NAMESPACE_PREFIX__-content"
data-state="hidden"
>
__DEFAULT_TEST_CONTENT__
</div>
<div
class="__NAMESPACE_PREFIX__-arrow"
/>
</div>
</div>,
"popperInstance": null,
"props": Object {
"allowHTML": false,
"animateFill": false,
"animation": "fade",
"appendTo": [Function],
"aria": Object {
"content": "describedby",
"expanded": false,
},
"arrow": true,
"content": "__DEFAULT_TEST_CONTENT__",
"delay": 0,
"duration": 0,
"followCursor": false,
"getReferenceClientRect": null,
"hideOnClick": true,
"ignoreAttributes": false,
"inertia": false,
"inlinePositioning": false,
"interactive": false,
"interactiveBorder": 2,
"interactiveDebounce": 0,
"maxWidth": 350,
"moveTransition": "",
"offset": Array [
0,
10,
],
"onAfterUpdate": [Function],
"onBeforeUpdate": [Function],
"onClickOutside": [Function],
"onCreate": [Function],
"onDestroy": [Function],
"onHidden": [Function],
"onHide": [Function],
"onMount": [Function],
"onShow": [Function],
"onShown": [Function],
"onTrigger": [Function],
"onUntrigger": [Function],
"placement": "top",
"plugins": Array [],
"popperOptions": Object {},
"render": [Function],
"role": "tooltip",
"showOnCreate": false,
"sticky": false,
"theme": "",
"touch": true,
"trigger": "mouseenter focus",
"triggerTarget": null,
"zIndex": 9999,
},
"reference": <button
class="__tippy"
/>,
"setContent": [Function],
"setProps": [Function],
"show": [Function],
"state": Object {
"isDestroyed": false,
"isEnabled": true,
"isMounted": false,
"isShown": false,
"isVisible": false,
},
"unmount": [Function],
}
`;
================================================
FILE: test/integration/__snapshots__/props.test.js.snap
================================================
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`content DocumentFragment is injected into the tippy node 1`] = `
<div
class="__NAMESPACE_PREFIX__-content"
data-state="hidden"
>
<button
class="__tippy"
/>
</div>
`;
exports[`content Element is injected into the tippy node 1`] = `
<div
class="__NAMESPACE_PREFIX__-content"
data-state="visible"
style="transition-duration: 0ms;"
>
<button
class="__tippy"
>
string
</button>
</div>
`;
exports[`content Function is injected into the tippy node 1`] = `
<div
class="__NAMESPACE_PREFIX__-content"
data-state="visible"
style="transition-duration: 0ms;"
>
string
</div>
`;
exports[`content string is injected into the tippy node 1`] = `
<div
class="__NAMESPACE_PREFIX__-content"
data-state="visible"
style="transition-duration: 0ms;"
>
string
</div>
`;
exports[`popperOptions merges modifiers correctly 1`] = `
Array [
Object {
"data": Object {},
"enabled": true,
"fn": [Function],
"name": "popperOffsets",
"phase": "read",
},
Object {
"data": Object {
"_skip": false,
},
"enabled": true,
"fn": [Function],
"name": "flip",
"options": Object {
"fallbackPlacements": Array [
"right",
],
"padding": 5,
},
"phase": "main",
"requiresIfExists": Array [
"offset",
],
},
Object {
"data": Object {},
"enabled": true,
"fn": [Function],
"name": "preventOverflow",
"options": Object {
"padding": Object {
"bottom": 2,
"left": 5,
"right": 5,
"top": 2,
},
"rootBoundary": "document",
},
"phase": "main",
"requiresIfExists": Array [
"offset",
],
},
Object {
"data": Object {},
"effect": [Function],
"enabled": true,
"fn": [Function],
"name": "arrow",
"options": Object {
"element": <div
class="__NAMESPACE_PREFIX__-arrow"
style="position: absolute; left: 0px; transform: translate(999px, 0px);"
/>,
"padding": 999,
},
"phase": "main",
"requires": Array [
"popperOffsets",
],
"requiresIfExists": Array [
"preventOverflow",
],
},
Object {
"enabled": true,
"fn": [Function],
"name": "hide",
"phase": "main",
"requiresIfExists": Array [
"preventOverflow",
],
},
Object {
"data": Object {},
"enabled": true,
"fn": [Function],
"name": "computeStyles",
"options": Object {
"adaptive": true,
},
"phase": "beforeWrite",
},
Object {
"enabled": true,
"fn": [Function],
"name": "$$tippy",
"phase": "beforeWrite",
"requires": Array [
"computeStyles",
],
},
Object {
"data": Object {},
"effect": [Function],
"enabled": true,
"fn": [Function],
"name": "eventListeners",
"phase": "write",
},
Object {
"effect": [Function],
"enabled": true,
"fn": [Function],
"name": "applyStyles",
"phase": "write",
"requires": Array [
"computeStyles",
],
},
]
`;
================================================
FILE: test/integration/addons/createSingleton.test.js
================================================
import {fireEvent} from '@testing-library/dom';
import {h} from '../../utils';
import createSingleton from '../../../src/addons/createSingleton';
import tippy from '../../../src';
import {getFormattedMessage} from '../../../src/validation';
import {currentInput} from '../../../src/bindGlobalEventListeners';
describe('createSingleton', () => {
it('shows when a tippy instance reference is triggered', () => {
const refs = [h(), h()];
const singletonInstance = createSingleton(tippy(refs));
fireEvent.mouseEnter(refs[0]);
jest.runAllTimers();
expect(singletonInstance.state.isVisible).toBe(true);
});
it('does not show the original tippy element', () => {
const refs = [h(), h()];
const firstRef = refs[0];
createSingleton(tippy(refs));
fireEvent.mouseEnter(firstRef);
jest.runAllTimers();
expect(firstRef._tippy.state.isVisible).toBe(false);
});
it('uses the relevant tippy instance content', () => {
const configs = [{content: 'hi'}, {content: 'bye'}];
const instances = configs.map((props) => tippy(h(), props));
const singletonInstance = createSingleton(instances);
fireEvent.mouseEnter(instances[0].reference);
expect(singletonInstance.props.content).toBe('hi');
fireEvent.mouseLeave(instances[0].reference);
fireEvent.mouseEnter(instances[1].reference);
expect(singletonInstance.props.content).toBe('bye');
});
it('uses `delay: number` correctly', () => {
const refs = [h(), h()];
const singletonInstance = createSingleton(tippy(refs), {delay: 1000});
const firstRef = refs[0];
fireEvent.mouseEnter(firstRef);
jest.advanceTimersByTime(999);
expect(singletonInstance.state.isVisible).toBe(false);
jest.advanceTimersByTime(1);
expect(singletonInstance.state.isVisible).toBe(true);
fireEvent.mouseLeave(firstRef);
jest.advanceTimersByTime(999);
expect(singletonInstance.state.isVisible).toBe(true);
jest.advanceTimersByTime(1);
expect(singletonInstance.state.isVisible).toBe(false);
});
it('uses `delay: [number, number]` correctly', () => {
const refs = [h(), h()];
const singletonInstance = createSingleton(tippy(refs), {
delay: [500, 1000],
});
const firstRef = refs[0];
fireEvent.mouseEnter(firstRef);
jest.advanceTimersByTime(499);
expect(singletonInstance.state.isVisible).toBe(false);
jest.advanceTimersByTime(1);
expect(singletonInstance.state.isVisible).toBe(true);
fireEvent.mouseLeave(firstRef);
jest.advanceTimersByTime(999);
expect(singletonInstance.state.isVisible).toBe(true);
jest.advanceTimersByTime(1);
expect(singletonInstance.state.isVisible).toBe(false);
});
it('throws if not passed an array', () => {
expect(() => {
createSingleton(null);
}).toThrow();
expect(console.error).toHaveBeenCalledWith(
...getFormattedMessage(
[
'The first argument passed to createSingleton() must be an array of tippy',
'instances. The passed value was',
String(null),
].join(' ')
)
);
});
it('does not throw if any passed instance is not part of an existing singleton', () => {
expect(() => {
const instances = tippy([h(), h()]);
const singleton = createSingleton(instances);
singleton.destroy();
createSingleton(instances);
}).not.toThrow();
});
it('allows updates to `onTrigger`, `onDestroy`, and `onAfterUpdate`', () => {
const instances = tippy([h()]);
const onTriggerSpy = jest.fn();
const onDestroySpy = jest.fn();
const onAfterUpdateSpy = jest.fn();
const singleton = createSingleton(instances);
singleton.setProps({
onTrigger: onTriggerSpy,
onDestroy: onDestroySpy,
onAfterUpdate: onAfterUpdateSpy,
});
fireEvent.mouseEnter(instances[0].reference);
expect(onTriggerSpy).toHaveBeenCalled();
singleton.setProps({});
expect(onAfterUpdateSpy).toHaveBeenCalled();
singleton.destroy();
expect(onDestroySpy).toHaveBeenCalled();
});
it('can update the `delay` option', () => {
const refs = [h(), h()];
const singletonInstance = createSingleton(tippy(refs), {delay: 1000});
const firstRef = refs[0];
singletonInstance.setProps({delay: 500});
fireEvent.mouseEnter(firstRef);
jest.advanceTimersByTime(499);
expect(singletonInstance.state.isVisible).toBe(false);
jest.advanceTimersByTime(1);
expect(singletonInstance.state.isVisible).toBe(true);
fireEvent.mouseLeave(firstRef);
jest.advanceTimersByTime(499);
expect(singletonInstance.state.isVisible).toBe(true);
jest.advanceTimersByTime(1);
expect(singletonInstance.state.isVisible).toBe(false);
});
it('does not destroy the passed instances if passed `false`', () => {
const tippyInstances = tippy([h(), h()]);
const singletonInstance = createSingleton(tippyInstances);
singletonInstance.destroy(false);
tippyInstances.forEach((instance) => {
expect(instance.state.isDestroyed).toBe(false);
});
});
it('restores original state when destroyed', () => {
const tippyInstances = tippy([h(), h()]);
const prevInstanceProps = tippyInstances.map((instance) => instance.props);
const singletonInstance = createSingleton(tippyInstances);
singletonInstance.destroy(false);
tippyInstances.forEach((instance, i) => {
const {props} = instance;
expect({...props, ...prevInstanceProps[i]}).toEqual(props);
});
});
it('handles aria attribute correctly', () => {
const tippyInstances = tippy([h(), h()]);
const singletonInstance = createSingleton(tippyInstances, {delay: 100});
const id = `__NAMESPACE_PREFIX__-${singletonInstance.id}`;
const {reference: firstRef} = tippyInstances[0];
const {reference: secondRef} = tippyInstances[1];
fireEvent.mouseEnter(firstRef);
jest.runAllTimers();
expect(firstRef.getAttribute('aria-describedby')).toBe(id);
expect(secondRef.getAttribute('aria-describedby')).toBe(id);
fireEvent.mouseLeave(firstRef);
fireEvent.mouseEnter(secondRef);
expect(firstRef.getAttribute('aria-describedby')).toBe(id);
expect(secondRef.getAttribute('aria-describedby')).toBe(id);
singletonInstance.setProps({aria: {content: 'labelledby'}});
singletonInstance.hide();
fireEvent.mouseEnter(firstRef);
jest.runAllTimers();
expect(firstRef.getAttribute('aria-labelledby')).toBe(id);
expect(secondRef.getAttribute('aria-labelledby')).toBe(id);
fireEvent.mouseLeave(firstRef);
jest.advanceTimersByTime(100);
expect(firstRef.getAttribute('aria-labelledby')).toBe(null);
expect(secondRef.getAttribute('aria-labelledby')).toBe(null);
});
it('does not use the placeholder element content with a function', () => {
const refs = [h(), h()];
const instances = tippy(refs, {content: () => 'hello'});
const singleton = createSingleton(instances);
const firstRef = refs[0];
expect(singleton.props.content).toBe('__DEFAULT_TEST_CONTENT__');
fireEvent.mouseEnter(firstRef);
expect(singleton.props.content).toBe('hello');
});
});
describe('overrides prop', () => {
it('individual tippy instance props override singleton instance props', () => {
const tippyInstances = tippy([h(), h()], {delay: 100});
const singletonInstance = createSingleton(tippyInstances, {
delay: 0,
overrides: ['delay'],
});
fireEvent.mouseEnter(tippyInstances[0].reference);
expect(singletonInstance.props.delay).toBe(100);
});
it('can be updated via .setProps()', () => {
const tippyInstances = tippy([h(), h()], {delay: 100});
const singletonInstance = createSingleton(tippyInstances, {
delay: 0,
overrides: ['delay'],
});
singletonInstance.setProps({overrides: []});
fireEvent.mouseEnter(tippyInstances[0].reference);
expect(singletonInstance.props.delay).toBe(0);
});
});
describe('.setInstances() method', () => {
it('updates the singleton instances', () => {
const initialRefs = [h(), h()];
const nextRefs = [initialRefs[0], h()];
const initialInstances = tippy(initialRefs);
const nextInstances = tippy(nextRefs);
const singleton = createSingleton(initialInstances);
singleton.setInstances(nextInstances);
expect(initialInstances[1].state.isEnabled).toBe(true);
expect(nextInstances[1].state.isEnabled).toBe(false);
fireEvent.mouseEnter(nextRefs[1]);
expect(singleton.state.isVisible).toBe(true);
});
});
describe('.show() method', () => {
const getInstances = () =>
[{content: 'first'}, {content: 'second'}, {content: 'last'}].map((props) =>
tippy(h(), props)
);
it('shows the first tippy instance when no parameters are passed', () => {
const singletonInstance = createSingleton(getInstances());
singletonInstance.show();
expect(singletonInstance.state.isVisible).toBe(true);
expect(singletonInstance.props.content).toBe('first');
});
it('shows the tippy instance passed as an argument', () => {
const tippyInstances = getInstances();
const singletonInstance = createSingleton(tippyInstances);
singletonInstance.show(tippyInstances[1]);
expect(singletonInstance.state.isVisible).toBe(true);
expect(singletonInstance.props.content).toBe('second');
});
it('shows the tippy instance related to the reference element passed as an argument', () => {
const tippyInstances = getInstances();
const singletonInstance = createSingleton(tippyInstances);
singletonInstance.show(tippyInstances[1].reference);
expect(singletonInstance.state.isVisible).toBe(true);
expect(singletonInstance.props.content).toBe('second');
});
it('shows the tippy instance at the given index number', () => {
const singletonInstance = createSingleton(getInstances());
singletonInstance.show(1);
expect(singletonInstance.state.isVisible).toBe(true);
expect(singletonInstance.props.content).toBe('second');
});
});
describe('.showNext() method', () => {
const getInstances = () =>
[{content: 'first'}, {content: 'second'}, {content: 'last'}].map((props) =>
tippy(h(), props)
);
it('shows the first tippy instance if none is visible', () => {
const singletonInstance = createSingleton(getInstances());
singletonInstance.showNext();
expect(singletonInstance.state.isVisible).toBe(true);
expect(singletonInstance.props.content).toBe('first');
});
it('shows the tippy instance after the currently visible one', () => {
const singletonInstance = createSingleton(getInstances());
singletonInstance.show();
expect(singletonInstance.props.content).toBe('first');
singletonInstance.showNext();
expect(singletonInstance.props.content).toBe('second');
singletonInstance.showNext();
expect(singletonInstance.props.content).toBe('last');
});
it('loops to the beginning if the last instance is visible', () => {
const singletonInstance = createSingleton(getInstances());
singletonInstance.show(2);
expect(singletonInstance.props.content).toBe('last');
singletonInstance.showNext();
expect(singletonInstance.props.content).toBe('first');
});
});
describe('.showPrevious() method', () => {
const getInstances = () =>
[{content: 'first'}, {content: 'second'}, {content: 'last'}].map((props) =>
tippy(h(), props)
);
it('shows the last tippy instance if none is visible', () => {
const singletonInstance = createSingleton(getInstances());
singletonInstance.showPrevious();
expect(singletonInstance.state.isVisible).toBe(true);
expect(singletonInstance.props.content).toBe('last');
});
it('shows the tippy instance before the currently visible one', () => {
const singletonInstance = createSingleton(getInstances(), {
showOnCreate: true,
});
singletonInstance.hide();
singletonInstance.show(2);
expect(singletonInstance.props.content).toBe('last');
singletonInstance.showPrevious();
expect(singletonInstance.props.content).toBe('second');
singletonInstance.showPrevious();
expect(singletonInstance.props.content).toBe('first');
});
it('loops to the end if the first instance is visible', () => {
const singletonInstance = createSingleton(getInstances());
singletonInstance.show();
expect(singletonInstance.props.content).toBe('first');
singletonInstance.showPrevious();
expect(singletonInstance.props.content).toBe('last');
});
});
describe('showOnCreate prop', () => {
it('shows the first tippy instance on creation', () => {
const tippyInstances = [{content: 'first'}, {content: 'second'}].map(
(props) => tippy(h(), props)
);
const singletonInstance = createSingleton(tippyInstances, {
showOnCreate: true,
});
expect(singletonInstance.state.isVisible).toBe(true);
expect(singletonInstance.props.content).toBe('first');
});
it('resets correctly if showOnCreate is cancelled by a click outside', () => {
const tippyInstances = [{content: 'first'}, {content: 'second'}].map(
(props) => tippy(h(), props)
);
const singletonInstance = createSingleton(tippyInstances, {
showOnCreate: true,
delay: 500,
});
fireEvent.mouseDown(document.body);
jest.runAllTimers();
fireEvent.mouseEnter(tippyInstances[1].reference);
jest.runAllTimers();
expect(singletonInstance.state.isVisible).toBe(true);
expect(singletonInstance.props.content).toBe('second');
});
it('does not hide tippy upon clicking next target', () => {
currentInput.isTouch = true;
const tippyInstances = [{content: 'first'}, {content: 'second'}].map(
(props) => tippy(h(), props)
);
const singletonInstance = createSingleton(tippyInstances);
fireEvent.mouseEnter(tippyInstances[0].reference);
fireEvent.mouseDown(document.body);
fireEvent.mouseEnter(tippyInstances[1].reference);
jest.runAllTimers();
expect(singletonInstance.state.isVisible).toBe(true);
expect(singletonInstance.props.content).toBe('second');
currentInput.isTouch = false;
});
it('accepts custom `triggerTarget` in individual instances', () => {
const ref1 = h();
const ref2 = h();
const triggerTarget1 = h();
const triggerTarget2 = h();
const tippyInstances = [
{ref: ref1, content: 'first', triggerTarget: triggerTarget1},
{ref: ref2, content: 'second', triggerTarget: triggerTarget2},
].map(({ref, ...props}) => tippy(ref, props));
const singletonInstance = createSingleton(tippyInstances);
fireEvent.mouseEnter(ref1);
jest.runAllTimers();
expect(singletonInstance.state.isVisible).toBe(false);
fireEvent.mouseEnter(triggerTarget1);
jest.runAllTimers();
expect(singletonInstance.state.isVisible).toBe(true);
expect(singletonInstance.props.content).toBe('first');
fireEvent.mouseEnter(triggerTarget2);
jest.runAllTimers();
expect(singletonInstance.state.isVisible).toBe(true);
expect(singletonInstance.props.content).toBe('second');
fireEvent.mouseEnter(ref1);
jest.runAllTimers();
expect(singletonInstance.props.content).toBe('second');
});
});
================================================
FILE: test/integration/addons/delegate.test.js
================================================
import {fireEvent} from '@testing-library/dom';
import {h} from '../../utils';
import delegate from '../../../src/addons/delegate';
import {getFormattedMessage} from '../../../src/validation';
import {normalizeToArray} from '../../../src/utils';
let instance;
let delegateElement = h();
afterEach(() => {
if (instance) {
normalizeToArray(instance).forEach((i) => i.destroy());
}
delegateElement = h();
});
describe('delegate', () => {
it('creates an instance for the child target', () => {
const button = h('button', {}, delegateElement);
instance = delegate(delegateElement, {target: 'button'});
expect(button._tippy).toBeUndefined();
fireEvent.mouseOver(button);
expect(button._tippy).toBeDefined();
instance.destroy();
});
it('works with `trigger: click`', () => {
const button = h('button', {}, delegateElement);
instance = delegate(delegateElement, {
target: 'button',
trigger: 'click',
});
expect(button._tippy).toBeUndefined();
fireEvent.click(button);
expect(button._tippy).toBeDefined();
instance.destroy();
});
it('handles an array of delegate targets', () => {
const refs = [h(), h()];
refs.forEach((ref) => ref.append(document.createElement('button')));
instance = delegate(refs, {target: 'button'});
const button = refs[0].querySelector('button');
expect(button._tippy).toBeUndefined();
fireEvent.mouseOver(button);
expect(button._tippy).toBeDefined();
instance.forEach((instance) => instance.destroy());
});
it('does not show its own tippy', () => {
instance = delegate(delegateElement, {target: 'button'});
fireEvent.mouseOver(delegateElement);
fireEvent.mouseEnter(delegateElement);
jest.runAllTimers();
expect(instance.state.isVisible).toBe(false);
instance.destroy();
});
it('throws if delegate target is falsy', () => {
delegate(null, {target: 'button'});
expect(console.error).toHaveBeenCalledWith(
...getFormattedMessage(
[
'tippy() was passed',
'`' + String(null) + '`',
'as its targets (first) argument. Valid types are: String, Element, Element[],',
'or NodeList.',
].join(' ')
)
);
});
it('errors if passed missing props object', () => {
expect(() => delegate(delegateElement)).toThrow();
expect(console.error).toHaveBeenCalledWith(
...getFormattedMessage(
[
'You must specity a `target` prop indicating a CSS selector string matching',
'the target elements that should receive a tippy.',
].join(' ')
)
);
});
it('errors if passed falsy or missing `target` prop', () => {
delegate(delegateElement, {target: ''});
expect(console.error).toHaveBeenCalledWith(
...getFormattedMessage(
[
'You must specity a `target` prop indicating a CSS selector string matching',
'the target elements that should receive a tippy.',
].join(' ')
)
);
});
it('can be destroyed', () => {
const button = h('button', {}, delegateElement);
instance = delegate(delegateElement, {target: 'button'});
instance.destroy();
fireEvent.mouseOver(button);
expect(button._tippy).toBeUndefined();
});
it('destroys child instance by default too', () => {
const button = h('button', {}, delegateElement);
instance = delegate(delegateElement, {target: 'button'});
fireEvent.mouseOver(button);
instance.destroy();
expect(button._tippy).toBeUndefined();
});
it('does not destroy child instance if passed `false`', () => {
const button = h('button', {}, delegateElement);
instance = delegate(delegateElement, {target: 'button'});
fireEvent.mouseOver(button);
instance.destroy(false);
expect(button._tippy).toBeDefined();
});
it('handles `data-tippy-trigger` attribute', () => {
const clickButton = h(
'button',
{'data-tippy-trigger': 'click'},
delegateElement
);
const focusButton = h(
'button',
{'data-tippy-trigger': 'focus'},
delegateElement
);
instance = delegate(delegateElement, {
target: 'button',
trigger: 'mouseenter',
});
fireEvent.mouseOver(clickButton);
expect(clickButton._tippy).toBeUndefined();
fireEvent.click(clickButton);
expect(clickButton._tippy).toBeDefined();
fireEvent.mouseOver(focusButton);
expect(focusButton._tippy).toBeUndefined();
fireEvent.focusIn(focusButton);
expect(focusButton._tippy).toBeDefined();
});
it('respects `delay` on first show', () => {
const button = h('button', {}, delegateElement);
instance = delegate(delegateElement, {target: 'button', delay: 100});
fireEvent.mouseOver(button);
jest.advanceTimersByTime(99);
expect(button._tippy.state.isVisible).toBe(false);
jest.advanceTimersByTime(1);
expect(button._tippy.state.isVisible).toBe(true);
});
it('does not hide tippy with touch input using click trigger', () => {
const button = h('button', {}, delegateElement);
instance = delegate(delegateElement, {target: 'button', trigger: 'click'});
fireEvent.touchStart(button, {bubbles: true});
fireEvent.click(button, {bubbles: true});
jest.runAllTimers();
expect(button._tippy.state.isVisible).toBe(true);
});
});
================================================
FILE: test/integration/bindGlobalEventListeners.test.js
================================================
import {h} from '../utils';
import tippy from '../../src';
import * as Listeners from '../../src/bindGlobalEventListeners';
describe('onDocumentTouchStart', () => {
it('sets currentInput.isTouch to `true`', () => {
Listeners.onDocumentTouchStart();
Listeners.onDocumentTouchStart();
expect(Listeners
gitextract_flaxrdjs/
├── .config/
│ ├── .prettierignore
│ ├── babel.config.js
│ ├── eslint.config.js
│ ├── jest-puppeteer.config.js
│ ├── jest.config.js
│ └── rollup.config.js
├── .editorconfig
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ ├── config.yml
│ │ └── feature_request.md
│ └── workflows/
│ ├── cd.yml
│ └── ci.yml
├── .gitignore
├── LICENSE
├── MIGRATION_GUIDE.md
├── README.md
├── build/
│ ├── animations/
│ │ ├── perspective-extreme.js
│ │ ├── perspective-subtle.js
│ │ ├── perspective.js
│ │ ├── scale-extreme.js
│ │ ├── scale-subtle.js
│ │ ├── scale.js
│ │ ├── shift-away-extreme.js
│ │ ├── shift-away-subtle.js
│ │ ├── shift-away.js
│ │ ├── shift-toward-extreme.js
│ │ ├── shift-toward-subtle.js
│ │ └── shift-toward.js
│ ├── base-umd.js
│ ├── base.js
│ ├── bundle-umd.js
│ ├── css/
│ │ ├── backdrop.js
│ │ ├── border.js
│ │ ├── svg-arrow.js
│ │ └── tippy.js
│ ├── headless-umd.js
│ ├── headless.js
│ ├── index.js
│ └── themes/
│ ├── light-border.js
│ ├── light.js
│ ├── material.js
│ └── translucent.js
├── headless/
│ └── package.json
├── index.test-d.ts
├── package.json
├── src/
│ ├── _babel.d.ts
│ ├── addons/
│ │ ├── createSingleton.ts
│ │ └── delegate.ts
│ ├── bindGlobalEventListeners.ts
│ ├── browser.ts
│ ├── constants.ts
│ ├── createTippy.ts
│ ├── css.ts
│ ├── dom-utils.ts
│ ├── index.ts
│ ├── plugins/
│ │ ├── animateFill.ts
│ │ ├── followCursor.ts
│ │ ├── inlinePositioning.ts
│ │ └── sticky.ts
│ ├── props.ts
│ ├── scss/
│ │ ├── _mixins.scss
│ │ ├── _vars.scss
│ │ ├── animations/
│ │ │ ├── fade.scss
│ │ │ ├── perspective-extreme.scss
│ │ │ ├── perspective-subtle.scss
│ │ │ ├── perspective.scss
│ │ │ ├── scale-extreme.scss
│ │ │ ├── scale-subtle.scss
│ │ │ ├── scale.scss
│ │ │ ├── shift-away-extreme.scss
│ │ │ ├── shift-away-subtle.scss
│ │ │ ├── shift-away.scss
│ │ │ ├── shift-toward-extreme.scss
│ │ │ ├── shift-toward-subtle.scss
│ │ │ └── shift-toward.scss
│ │ ├── backdrop.scss
│ │ ├── border.scss
│ │ ├── index.scss
│ │ ├── svg-arrow.scss
│ │ └── themes/
│ │ ├── light-border.scss
│ │ ├── light.scss
│ │ ├── material.scss
│ │ └── translucent.scss
│ ├── template.ts
│ ├── types-internal.ts
│ ├── types.ts
│ ├── utils.ts
│ └── validation.ts
├── test/
│ ├── functional/
│ │ ├── border.test.js
│ │ ├── followCursor.test.js
│ │ ├── inlinePositioning.test.js
│ │ ├── sticky.test.js
│ │ └── themes.test.js
│ ├── image-reporter.js
│ ├── integration/
│ │ ├── __snapshots__/
│ │ │ ├── createTippy.test.js.snap
│ │ │ └── props.test.js.snap
│ │ ├── addons/
│ │ │ ├── createSingleton.test.js
│ │ │ └── delegate.test.js
│ │ ├── bindGlobalEventListeners.test.js
│ │ ├── createTippy.test.js
│ │ ├── plugins/
│ │ │ ├── __snapshots__/
│ │ │ │ └── inlinePositioning.test.js.snap
│ │ │ ├── animateFill.test.js
│ │ │ ├── followCursor.test.js
│ │ │ └── inlinePositioning.test.js
│ │ └── props.test.js
│ ├── setup.js
│ ├── unit/
│ │ ├── __snapshots__/
│ │ │ ├── props.test.js.snap
│ │ │ └── tippy.test.js.snap
│ │ ├── css.test.js
│ │ ├── dom-utils.test.js
│ │ ├── props.test.js
│ │ ├── tippy.test.js
│ │ ├── utils.test.js
│ │ └── validation.test.js
│ ├── utils.js
│ └── visual/
│ ├── index.css
│ ├── index.html
│ ├── index.js
│ └── tests.js
├── tsconfig.json
└── website/
├── .eslintignore
├── .gitignore
├── LICENSE
├── README.md
├── gatsby-config.js
├── gatsby-node.js
├── package.json
├── scripts/
│ ├── should-deploy-docs.js
│ └── should-deploy-docs.sh
└── src/
├── components/
│ ├── ElasticScroll.js
│ ├── Footer.js
│ ├── Framework.js
│ ├── Header.js
│ ├── Icon.js
│ ├── Image.js
│ ├── Layout.js
│ ├── Main.js
│ ├── MiniHeader.js
│ ├── Nav.js
│ ├── NavButtons.js
│ ├── PluginIcon.js
│ ├── RenderIcon.js
│ ├── SEO.js
│ ├── TextGradient.js
│ ├── Tippy.js
│ ├── TippyTransition.js
│ └── examples/
│ ├── Ajax.js
│ ├── ContextMenu.js
│ ├── Dropdown.js
│ ├── EventDelegation.js
│ ├── ImageTransition.js
│ ├── Nesting.js
│ ├── Singleton.js
│ ├── TextTransition.js
│ ├── TriggerTarget.js
│ └── mouseRestPlugin.js
├── css/
│ ├── index.js
│ └── theme.js
├── hooks/
│ └── index.js
├── pages/
│ ├── .prettierrc.json
│ ├── 404.js
│ ├── index.mdx
│ ├── v5/
│ │ ├── accessibility.mdx
│ │ ├── addons.mdx
│ │ ├── ajax.mdx
│ │ ├── all-props.mdx
│ │ ├── animations.mdx
│ │ ├── creating-tooltips.mdx
│ │ ├── customizing-tooltips.mdx
│ │ ├── faq.mdx
│ │ ├── getting-started.mdx
│ │ ├── html-content.mdx
│ │ ├── lifecycle-hooks.mdx
│ │ ├── methods.mdx
│ │ ├── misc.mdx
│ │ ├── motivation.mdx
│ │ ├── plugins.mdx
│ │ ├── themes.mdx
│ │ └── tippy-instance.mdx
│ └── v6/
│ ├── accessibility.mdx
│ ├── addons.mdx
│ ├── ajax.mdx
│ ├── all-props.mdx
│ ├── animations.mdx
│ ├── browser-support.mdx
│ ├── constructor.mdx
│ ├── customization.mdx
│ ├── faq.mdx
│ ├── getting-started.mdx
│ ├── headless-tippy.mdx
│ ├── html-content.mdx
│ ├── methods.mdx
│ ├── misc.mdx
│ ├── motivation.mdx
│ ├── plugins.mdx
│ ├── themes.mdx
│ └── tippy-instance.mdx
└── utils.js
SYMBOL INDEX (214 symbols across 57 files)
FILE: .config/rollup.config.js
constant NAMESPACE_PREFIX (line 12) | const NAMESPACE_PREFIX = process.env.NAMESPACE || 'tippy';
FILE: build/index.js
constant NAMESPACE_PREFIX (line 17) | const NAMESPACE_PREFIX = process.env.NAMESPACE || 'tippy';
constant THEME (line 18) | const THEME = process.env.THEME;
constant BASE_OUTPUT_CONFIG (line 20) | const BASE_OUTPUT_CONFIG = {
constant PLUGINS (line 26) | const PLUGINS = {
constant PLUGIN_CONFIG (line 39) | const PLUGIN_CONFIG = [
function createPluginSCSS (line 47) | function createPluginSCSS(output, shouldInjectNodeEnvTheme = false) {
function createRollupConfig (line 65) | function createRollupConfig(inputFile, plugins) {
function build (line 73) | async function build() {
FILE: index.test-d.ts
type CustomProps (line 20) | interface CustomProps {
type FilteredProps (line 24) | type FilteredProps = CustomProps &
type ExtendedProps (line 27) | type ExtendedProps = FilteredProps & LifecycleHooks<FilteredProps>;
method fn (line 76) | fn(instance) {
FILE: src/addons/createSingleton.ts
method effect (line 22) | effect({state}) {
function setTriggerTargets (line 72) | function setTriggerTargets(): void {
function setReferences (line 80) | function setReferences(): void {
function enableInstances (line 84) | function enableInstances(isEnabled: boolean): void {
function interceptSetProps (line 94) | function interceptSetProps(singleton: Instance): Array<() => void> {
function prepareInstance (line 113) | function prepareInstance(
method fn (line 147) | fn() {
FILE: src/addons/delegate.ts
constant BUBBLING_EVENTS_MAP (line 9) | const BUBBLING_EVENTS_MAP = {
function delegate (line 19) | function delegate(
FILE: src/bindGlobalEventListeners.ts
function onDocumentTouchStart (line 13) | function onDocumentTouchStart(): void {
function onDocumentMouseMove (line 30) | function onDocumentMouseMove(): void {
function onWindowBlur (line 48) | function onWindowBlur(): void {
function bindGlobalEventListeners (line 60) | function bindGlobalEventListeners(): void {
FILE: src/constants.ts
constant ROUND_ARROW (line 1) | const ROUND_ARROW =
constant BOX_CLASS (line 4) | const BOX_CLASS = `__NAMESPACE_PREFIX__-box`;
constant CONTENT_CLASS (line 5) | const CONTENT_CLASS = `__NAMESPACE_PREFIX__-content`;
constant BACKDROP_CLASS (line 6) | const BACKDROP_CLASS = `__NAMESPACE_PREFIX__-backdrop`;
constant ARROW_CLASS (line 7) | const ARROW_CLASS = `__NAMESPACE_PREFIX__-arrow`;
constant SVG_ARROW_CLASS (line 8) | const SVG_ARROW_CLASS = `__NAMESPACE_PREFIX__-svg-arrow`;
constant TOUCH_OPTIONS (line 10) | const TOUCH_OPTIONS = {passive: true, capture: true};
FILE: src/createTippy.ts
function createTippy (line 45) | function createTippy(
FILE: src/css.ts
function injectCSS (line 1) | function injectCSS(css: string): void {
FILE: src/dom-utils.ts
function div (line 5) | function div(): HTMLDivElement {
function isElement (line 9) | function isElement(value: unknown): value is Element | DocumentFragment {
function isNodeList (line 13) | function isNodeList(value: unknown): value is NodeList {
function isMouseEvent (line 17) | function isMouseEvent(value: unknown): value is MouseEvent {
function isReferenceElement (line 21) | function isReferenceElement(value: any): value is ReferenceElement {
function getArrayOfElements (line 25) | function getArrayOfElements(value: Targets): Element[] {
function setTransitionDuration (line 41) | function setTransitionDuration(
function setVisibilityState (line 52) | function setVisibilityState(
function getOwnerDocument (line 63) | function getOwnerDocument(
function isCursorOutsideInteractiveBorder (line 72) | function isCursorOutsideInteractiveBorder(
function updateTransitionEndListener (line 105) | function updateTransitionEndListener(
function actualContains (line 127) | function actualContains(parent: Element, child: Element): boolean {
FILE: src/index.ts
function tippy (line 10) | function tippy(
FILE: src/plugins/animateFill.ts
method fn (line 10) | fn(instance) {
function createBackdropElement (line 69) | function createBackdropElement(): HTMLDivElement {
FILE: src/plugins/followCursor.ts
function storeMouseCoords (line 7) | function storeMouseCoords({clientX, clientY}: MouseEvent): void {
function addMouseCoordsListener (line 11) | function addMouseCoordsListener(doc: Document): void {
function removeMouseCoordsListener (line 15) | function removeMouseCoordsListener(doc: Document): void {
method fn (line 22) | fn(instance) {
FILE: src/plugins/inlinePositioning.ts
function getProps (line 6) | function getProps(props: Props, modifier: Modifier<any, any>): Partial<P...
method fn (line 23) | fn(instance) {
function getInlineBoundingClientRect (line 112) | function getInlineBoundingClientRect(
FILE: src/plugins/sticky.ts
method fn (line 7) | fn(instance) {
function areRectsDifferent (line 60) | function areRectsDifferent(
FILE: src/props.ts
method onAfterUpdate (line 45) | onAfterUpdate() {}
method onBeforeUpdate (line 46) | onBeforeUpdate() {}
method onCreate (line 47) | onCreate() {}
method onDestroy (line 48) | onDestroy() {}
method onHidden (line 49) | onHidden() {}
method onHide (line 50) | onHide() {}
method onMount (line 51) | onMount() {}
method onShow (line 52) | onShow() {}
method onShown (line 53) | onShown() {}
method onTrigger (line 54) | onTrigger() {}
method onUntrigger (line 55) | onUntrigger() {}
method onClickOutside (line 56) | onClickOutside() {}
function getExtendedPassedProps (line 84) | function getExtendedPassedProps(
function getDataAttributeProps (line 107) | function getDataAttributeProps(
function evaluateProps (line 143) | function evaluateProps(
function validateProps (line 174) | function validateProps(
FILE: src/template.ts
function dangerouslySetInnerHTML (line 16) | function dangerouslySetInnerHTML(element: Element, html: string): void {
function createArrowElement (line 20) | function createArrowElement(value: Props['arrow']): HTMLDivElement {
function setContent (line 38) | function setContent(content: HTMLDivElement, props: Props): void {
function getChildren (line 51) | function getChildren(popper: PopperElement): PopperChildren {
function render (line 69) | function render(
FILE: src/types-internal.ts
type ListenerObject (line 4) | interface ListenerObject {
type PopperTreeData (line 11) | interface PopperTreeData {
type PopperChildren (line 17) | interface PopperChildren {
FILE: src/types.ts
type BasePlacement (line 3) | type BasePlacement = Popper.BasePlacement;
type Placement (line 5) | type Placement = Popper.Placement;
type Content (line 7) | type Content =
type SingleTarget (line 13) | type SingleTarget = Element;
type MultipleTargets (line 15) | type MultipleTargets = string | Element[] | NodeList;
type Targets (line 17) | type Targets = SingleTarget | MultipleTargets;
type ReferenceElement (line 19) | interface ReferenceElement<TProps = Props> extends Element {
type PopperElement (line 23) | interface PopperElement<TProps = Props> extends HTMLDivElement {
type LifecycleHooks (line 27) | interface LifecycleHooks<TProps = Props> {
type RenderProps (line 48) | interface RenderProps {
type GetReferenceClientRect (line 60) | interface GetReferenceClientRect {
type Props (line 65) | interface Props extends LifecycleHooks, RenderProps {
type DefaultProps (line 112) | interface DefaultProps extends Omit<Props, 'delay' | 'duration'> {
type Instance (line 117) | interface Instance<TProps = Props> {
type TippyStatics (line 143) | interface TippyStatics {
type Tippy (line 149) | interface Tippy<TProps = Props> extends TippyStatics {
type Tippy (line 153) | interface Tippy<TProps = Props> extends TippyStatics {
type DelegateInstance (line 164) | interface DelegateInstance<TProps = Props> extends Instance<TProps> {
type Delegate (line 168) | interface Delegate<TProps = Props> {
type Delegate (line 175) | interface Delegate<TProps = Props> {
type CreateSingletonProps (line 182) | type CreateSingletonProps<TProps = Props> = TProps & {
type CreateSingletonInstance (line 186) | type CreateSingletonInstance<TProps = CreateSingletonProps> = Instance<
type CreateSingleton (line 195) | type CreateSingleton<TProps = Props> = (
type Plugin (line 206) | interface Plugin<TProps = Props> {
type AnimateFill (line 212) | interface AnimateFill extends Plugin {
type FollowCursor (line 217) | interface FollowCursor extends Plugin {
type InlinePositioning (line 222) | interface InlinePositioning extends Plugin {
type Sticky (line 227) | interface Sticky extends Plugin {
type HideAllOptions (line 240) | interface HideAllOptions {
type HideAll (line 245) | type HideAll = (options?: HideAllOptions) => void;
FILE: src/utils.ts
function hasOwnProperty (line 3) | function hasOwnProperty(
function getValueAtIndexOrReturn (line 10) | function getValueAtIndexOrReturn<T>(
function isType (line 27) | function isType(value: any, type: string): boolean {
function invokeWithArgsOrReturn (line 32) | function invokeWithArgsOrReturn(value: any, args: any[]): any {
function debounce (line 36) | function debounce<T>(
function removeProperties (line 55) | function removeProperties<T>(obj: T, keys: string[]): Partial<T> {
function splitBySpaces (line 63) | function splitBySpaces(value: string): string[] {
function normalizeToArray (line 67) | function normalizeToArray<T>(value: T | T[]): T[] {
function pushIfUnique (line 71) | function pushIfUnique<T>(arr: T[], value: T): void {
function appendPxIfNumber (line 77) | function appendPxIfNumber(value: string | number): string {
function unique (line 81) | function unique<T>(arr: T[]): T[] {
function getNumber (line 85) | function getNumber(value: string | number): number {
function getBasePlacement (line 89) | function getBasePlacement(placement: Placement): BasePlacement {
function arrayFrom (line 93) | function arrayFrom(value: ArrayLike<any>): any[] {
function removeUndefinedProps (line 97) | function removeUndefinedProps(
FILE: src/validation.ts
function createMemoryLeakWarning (line 3) | function createMemoryLeakWarning(method: string): string {
function clean (line 12) | function clean(value: string): string {
function getDevMessage (line 22) | function getDevMessage(message: string): string {
function getFormattedMessage (line 32) | function getFormattedMessage(message: string): string[] {
function resetVisitedMessages (line 50) | function resetVisitedMessages(): void {
function warnWhen (line 54) | function warnWhen(condition: boolean, message: string): void {
function errorWhen (line 61) | function errorWhen(condition: boolean, message: string): void {
function validateTargets (line 68) | function validateTargets(targets: Targets): void {
FILE: test/functional/followCursor.test.js
function generateSelector (line 6) | function generateSelector(test) {
FILE: test/image-reporter.js
class ImageReporter (line 7) | class ImageReporter {
method constructor (line 8) | constructor(globalConfig, options) {
method onTestResult (line 13) | onTestResult(test, testResult, aggregateResults) {
FILE: test/integration/createTippy.test.js
method fn (line 276) | fn() {}
method effect (line 277) | effect() {
method onHidden (line 295) | onHidden() {
FILE: test/integration/plugins/animateFill.test.js
method render (line 83) | render() {
method render (line 102) | render() {
method render (line 117) | render() {
FILE: test/integration/plugins/followCursor.test.js
function matches (line 29) | function matches(receivedRect) {
FILE: test/integration/props.test.js
method render (line 1032) | render() {
method render (line 1047) | render() {
FILE: test/utils.js
constant IDENTIFIER (line 7) | const IDENTIFIER = '__tippy';
function cleanDocumentBody (line 9) | function cleanDocumentBody() {
function h (line 13) | function h(nodeName = 'button', attributes = {}, to = document.body) {
function enableTouchEnvironment (line 26) | function enableTouchEnvironment() {
function disableTouchEnvironment (line 31) | function disableTouchEnvironment() {
function screenshotTest (line 37) | async function screenshotTest(page, name) {
function navigateToTest (line 68) | async function navigateToTest(page, name) {
FILE: test/visual/index.js
function hide (line 24) | function hide() {
function show (line 36) | function show() {
function run (line 48) | function run() {
FILE: test/visual/tests.js
method onMount (line 74) | onMount() {
method onCreate (line 151) | onCreate({setContent}) {
method onDestroy (line 163) | onDestroy() {
FILE: website/scripts/should-deploy-docs.js
constant DOCS_PREFIXES (line 45) | const DOCS_PREFIXES = ['docs', 'release'];
FILE: website/src/components/ElasticScroll.js
function ElasticScroll (line 4) | function ElasticScroll({children, ...props}) {
FILE: website/src/components/Framework.js
constant MEDIA_SIZES (line 6) | const MEDIA_SIZES = {
constant MEDIA (line 14) | const MEDIA = Object.keys(MEDIA_SIZES).reduce((acc, mediaSize) => {
FILE: website/src/components/Header.js
class Header (line 154) | class Header extends Component {
method render (line 155) | render() {
FILE: website/src/components/Layout.js
class Heading (line 77) | class Heading extends React.Component {
method constructor (line 78) | constructor(props) {
method render (line 106) | render() {
class Layout (line 156) | class Layout extends Component {
method constructor (line 157) | constructor() {
method render (line 174) | render() {
FILE: website/src/components/MiniHeader.js
class MiniHeader (line 37) | class MiniHeader extends Component {
method render (line 38) | render() {
FILE: website/src/components/Nav.js
class Nav (line 127) | class Nav extends Component {
method componentDidMount (line 159) | componentDidMount() {
method componentWillUnmount (line 171) | componentWillUnmount() {
method render (line 177) | render() {
FILE: website/src/components/NavButtons.js
function NavButtons (line 137) | function NavButtons({next}) {
FILE: website/src/components/PluginIcon.js
function RenderIcon (line 9) | function RenderIcon({large}) {
FILE: website/src/components/RenderIcon.js
function RenderIcon (line 9) | function RenderIcon({large}) {
FILE: website/src/components/SEO.js
function SEO (line 5) | function SEO({title, description, lang, meta, keywords, pageContext}) {
FILE: website/src/components/Tippy.js
method fn (line 36) | fn(instance) {
method fn (line 56) | fn({hide}) {
FILE: website/src/components/TippyTransition.js
function parseTranslate3d (line 5) | function parseTranslate3d(string) {
function preserveInvocation (line 10) | function preserveInvocation(fn, args) {
function useStableMemo (line 16) | function useStableMemo(fn, deps) {
function TippyTransition (line 38) | function TippyTransition({children, onChange}) {
FILE: website/src/components/examples/Ajax.js
function Img (line 5) | function Img({src}) {
function Ajax (line 19) | function Ajax({children}) {
FILE: website/src/components/examples/ContextMenu.js
function ContextMenu (line 5) | function ContextMenu() {
FILE: website/src/components/examples/Dropdown.js
function Dropdown (line 46) | function Dropdown({text = 'Dropdown'}) {
FILE: website/src/components/examples/EventDelegation.js
function EventDelegation (line 5) | function EventDelegation() {
FILE: website/src/components/examples/ImageTransition.js
function DimensionsTransition (line 11) | function DimensionsTransition() {
FILE: website/src/components/examples/Nesting.js
method onCreate (line 11) | onCreate({popper}) {
function Nesting (line 19) | function Nesting() {
FILE: website/src/components/examples/Singleton.js
function Singleton (line 8) | function Singleton({group, transition}) {
FILE: website/src/components/examples/TextTransition.js
function DimensionsTransition (line 21) | function DimensionsTransition() {
FILE: website/src/components/examples/TriggerTarget.js
function TriggerTarget (line 11) | function TriggerTarget() {
FILE: website/src/components/examples/mouseRestPlugin.js
method fn (line 4) | fn(instance) {
FILE: website/src/css/index.js
constant MONOSPACE_FONT_STACK (line 5) | const MONOSPACE_FONT_STACK = `Menlo, "Dank Mono", Inconsolata, "Operator...
function CSS (line 527) | function CSS() {
FILE: website/src/hooks/index.js
function useInstance (line 3) | function useInstance(initialValue = {}) {
FILE: website/src/pages/404.js
function NotFoundPage (line 5) | function NotFoundPage({pageContext}) {
FILE: website/src/utils.js
constant CURRENT_MAJOR (line 4) | const CURRENT_MAJOR = `v${version.split('.')[0]}`;
constant HOME_PATHS (line 5) | const HOME_PATHS = ['/', '/tippyjs/'];
constant BOLD_HELLO (line 6) | const BOLD_HELLO = <strong>Hello</strong>;
function shouldShowLink (line 8) | function shouldShowLink(path, currentPath) {
function sortActivePages (line 19) | function sortActivePages(edges, location) {
function getVersionFromPath (line 30) | function getVersionFromPath(path) {
constant ALL_PLACEMENTS (line 34) | const ALL_PLACEMENTS = ['top', 'right', 'bottom', 'left'].reduce(
constant EXTRA_ANIMATIONS (line 41) | const EXTRA_ANIMATIONS = [
Condensed preview — 199 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (581K chars).
[
{
"path": ".config/.prettierignore",
"chars": 88,
"preview": "../build\n../dist\n../headless\n../website/public\n../website/.cache\n../animations\n../themes"
},
{
"path": ".config/babel.config.js",
"chars": 306,
"preview": "module.exports = {\n presets: [\n ['@babel/env', {loose: true, useBuiltIns: 'entry', corejs: 3}],\n '@babel/typescri"
},
{
"path": ".config/eslint.config.js",
"chars": 1058,
"preview": "module.exports = {\n env: {\n browser: true,\n node: true,\n jest: true,\n es6: true,\n },\n globals: {\n __DE"
},
{
"path": ".config/jest-puppeteer.config.js",
"chars": 434,
"preview": "require('dotenv').config();\n\nconst getConfig = require('jest-puppeteer-docker/lib/config');\nconst baseConfig = getConfig"
},
{
"path": ".config/jest.config.js",
"chars": 675,
"preview": "// https://github.com/smooth-code/jest-puppeteer/issues/160#issuecomment-491975158\nprocess.env.JEST_PUPPETEER_CONFIG = r"
},
{
"path": ".config/rollup.config.js",
"chars": 5144,
"preview": "import babel from 'rollup-plugin-babel';\nimport json from 'rollup-plugin-json';\nimport resolve from 'rollup-plugin-node-"
},
{
"path": ".editorconfig",
"chars": 175,
"preview": "# https://editorconfig.org\n\nroot = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_"
},
{
"path": ".github/FUNDING.yml",
"chars": 643,
"preview": "# These are supported funding model platforms\n\ngithub: [atomiks]\npatreon: # Replace with a single Patreon username\nopen_"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 446,
"preview": "---\nname: 🐞 Bug report\nabout: Something is broken\ntitle: ''\nlabels: \"\\U0001F41B bug, \\U0001F6A7 unconfirmed\"\nassignees: "
},
{
"path": ".github/ISSUE_TEMPLATE/config.yml",
"chars": 341,
"preview": "blank_issues_enabled: false\ncontact_links:\n - name: 👋 Stack Overflow\n url: https://stackoverflow.com/questions/tagge"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.md",
"chars": 257,
"preview": "---\nname: ✨ Feature request\nabout: Suggest a feature\ntitle: ''\nlabels: \"\\U0001F48E enhancement\"\nassignees: ''\n---\n\n## Pr"
},
{
"path": ".github/workflows/cd.yml",
"chars": 695,
"preview": "name: CD\non: [push]\nenv:\n CI: true\n\njobs:\n publish:\n if:\n ${{ startsWith(github.event.commits[0].message, 'doc"
},
{
"path": ".github/workflows/ci.yml",
"chars": 913,
"preview": "name: CI\non: [push, pull_request]\nenv:\n CI: true\n PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true\n\njobs:\n checks:\n name: Li"
},
{
"path": ".gitignore",
"chars": 83,
"preview": ".DS_Store\ncoverage/\n.devserver/\nnode_modules/\ndist/\n/themes\n/animations\nindex.d.ts\n"
},
{
"path": "LICENSE",
"chars": 1072,
"preview": "MIT License\n\nCopyright (c) 2017-present atomiks\n\nPermission is hereby granted, free of charge, to any person obtaining a"
},
{
"path": "MIGRATION_GUIDE.md",
"chars": 14429,
"preview": "# Migration Guide\n\n- [5.x to 6.x](#5x-to-6x)\n- [4.x to 5.x](#4x-to-5x)\n\n---\n\n# 5.x to 6.x\n\nPopper.js was updated to v2. "
},
{
"path": "README.md",
"chars": 1565,
"preview": "<div align=\"center\">\n <img alt=\"Tippy.js logo\" src=\"https://github.com/atomiks/tippyjs/raw/master/logo.png\" height=\"117"
},
{
"path": "build/animations/perspective-extreme.js",
"chars": 61,
"preview": "import '../../src/scss/animations/perspective-extreme.scss';\n"
},
{
"path": "build/animations/perspective-subtle.js",
"chars": 60,
"preview": "import '../../src/scss/animations/perspective-subtle.scss';\n"
},
{
"path": "build/animations/perspective.js",
"chars": 53,
"preview": "import '../../src/scss/animations/perspective.scss';\n"
},
{
"path": "build/animations/scale-extreme.js",
"chars": 55,
"preview": "import '../../src/scss/animations/scale-extreme.scss';\n"
},
{
"path": "build/animations/scale-subtle.js",
"chars": 54,
"preview": "import '../../src/scss/animations/scale-subtle.scss';\n"
},
{
"path": "build/animations/scale.js",
"chars": 47,
"preview": "import '../../src/scss/animations/scale.scss';\n"
},
{
"path": "build/animations/shift-away-extreme.js",
"chars": 60,
"preview": "import '../../src/scss/animations/shift-away-extreme.scss';\n"
},
{
"path": "build/animations/shift-away-subtle.js",
"chars": 59,
"preview": "import '../../src/scss/animations/shift-away-subtle.scss';\n"
},
{
"path": "build/animations/shift-away.js",
"chars": 52,
"preview": "import '../../src/scss/animations/shift-away.scss';\n"
},
{
"path": "build/animations/shift-toward-extreme.js",
"chars": 62,
"preview": "import '../../src/scss/animations/shift-toward-extreme.scss';\n"
},
{
"path": "build/animations/shift-toward-subtle.js",
"chars": 61,
"preview": "import '../../src/scss/animations/shift-toward-subtle.scss';\n"
},
{
"path": "build/animations/shift-toward.js",
"chars": 54,
"preview": "import '../../src/scss/animations/shift-toward.scss';\n"
},
{
"path": "build/base-umd.js",
"chars": 708,
"preview": "import tippy, {hideAll} from '../src';\nimport createSingleton from '../src/addons/createSingleton';\nimport delegate from"
},
{
"path": "build/base.js",
"chars": 610,
"preview": "import tippy from '../src';\nimport {render} from '../src/template';\n\ntippy.setDefaultProps({render});\n\nexport {default, "
},
{
"path": "build/bundle-umd.js",
"chars": 863,
"preview": "import css from '../dist/tippy.css';\nimport {injectCSS} from '../src/css';\nimport {isBrowser} from '../src/browser';\nimp"
},
{
"path": "build/css/backdrop.js",
"chars": 39,
"preview": "import '../../src/scss/backdrop.scss';\n"
},
{
"path": "build/css/border.js",
"chars": 37,
"preview": "import '../../src/scss/border.scss';\n"
},
{
"path": "build/css/svg-arrow.js",
"chars": 40,
"preview": "import '../../src/scss/svg-arrow.scss';\n"
},
{
"path": "build/css/tippy.js",
"chars": 36,
"preview": "import '../../src/scss/index.scss';\n"
},
{
"path": "build/headless-umd.js",
"chars": 678,
"preview": "import tippy, {hideAll} from '../src';\nimport createSingleton from '../src/addons/createSingleton';\nimport delegate from"
},
{
"path": "build/headless.js",
"chars": 594,
"preview": "import tippy from '../src';\n\nexport {hideAll} from '../src';\nexport {default as createSingleton} from '../src/addons/cre"
},
{
"path": "build/index.js",
"chars": 3069,
"preview": "// This file builds the CSS dist files. The main `rollup.config.js` builds the\n// JS dist files.\n\n/* eslint-disable @typ"
},
{
"path": "build/themes/light-border.js",
"chars": 50,
"preview": "import '../../src/scss/themes/light-border.scss';\n"
},
{
"path": "build/themes/light.js",
"chars": 43,
"preview": "import '../../src/scss/themes/light.scss';\n"
},
{
"path": "build/themes/material.js",
"chars": 46,
"preview": "import '../../src/scss/themes/material.scss';\n"
},
{
"path": "build/themes/translucent.js",
"chars": 49,
"preview": "import '../../src/scss/themes/translucent.scss';\n"
},
{
"path": "headless/package.json",
"chars": 376,
"preview": "{\n \"name\": \"tippy-headless\",\n \"private\": true,\n \"version\": \"0.1.0\",\n \"description\": \"Headless rendering for Tippy.js"
},
{
"path": "index.test-d.ts",
"chars": 2468,
"preview": "import {expectType} from 'tsd';\nimport tippy, {\n Instance,\n Props,\n Tippy,\n LifecycleHooks,\n delegate,\n DelegateIn"
},
{
"path": "package.json",
"chars": 4208,
"preview": "{\n \"name\": \"tippy.js\",\n \"version\": \"6.3.7\",\n \"description\": \"The complete tooltip, popover, dropdown, and menu soluti"
},
{
"path": "src/_babel.d.ts",
"chars": 32,
"preview": "declare const __DEV__: boolean;\n"
},
{
"path": "src/addons/createSingleton.ts",
"chars": 7464,
"preview": "import tippy from '..';\nimport {div} from '../dom-utils';\nimport {\n CreateSingleton,\n Plugin,\n CreateSingletonProps,\n"
},
{
"path": "src/addons/delegate.ts",
"chars": 4150,
"preview": "import tippy from '..';\nimport {TOUCH_OPTIONS} from '../constants';\nimport {defaultProps} from '../props';\nimport {Insta"
},
{
"path": "src/bindGlobalEventListeners.ts",
"chars": 1897,
"preview": "import {TOUCH_OPTIONS} from './constants';\nimport {isReferenceElement} from './dom-utils';\n\nexport const currentInput = "
},
{
"path": "src/browser.ts",
"chars": 177,
"preview": "export const isBrowser =\n typeof window !== 'undefined' && typeof document !== 'undefined';\n\nexport const isIE11 = isBr"
},
{
"path": "src/constants.ts",
"chars": 640,
"preview": "export const ROUND_ARROW =\n '<svg width=\"16\" height=\"6\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M0 6s1.796-.013 4.6"
},
{
"path": "src/createTippy.ts",
"chars": 31215,
"preview": "import {createPopper, StrictModifiers, Modifier} from '@popperjs/core';\nimport {currentInput} from './bindGlobalEventLis"
},
{
"path": "src/css.ts",
"chars": 424,
"preview": "export function injectCSS(css: string): void {\n const style = document.createElement('style');\n style.textContent = cs"
},
{
"path": "src/dom-utils.ts",
"chars": 3861,
"preview": "import {ReferenceElement, Targets} from './types';\nimport {PopperTreeData} from './types-internal';\nimport {arrayFrom, i"
},
{
"path": "src/index.ts",
"chars": 2825,
"preview": "import bindGlobalEventListeners, {\n currentInput,\n} from './bindGlobalEventListeners';\nimport createTippy, {mountedInst"
},
{
"path": "src/plugins/animateFill.ts",
"chars": 2103,
"preview": "import {BACKDROP_CLASS} from '../constants';\nimport {div, setVisibilityState} from '../dom-utils';\nimport {getChildren} "
},
{
"path": "src/plugins/followCursor.ts",
"chars": 4862,
"preview": "import {getOwnerDocument, isMouseEvent} from '../dom-utils';\nimport {FollowCursor, Instance} from '../types';\n\nlet mouse"
},
{
"path": "src/plugins/inlinePositioning.ts",
"chars": 5000,
"preview": "import {Modifier, Placement} from '@popperjs/core';\nimport {isMouseEvent} from '../dom-utils';\nimport {BasePlacement, In"
},
{
"path": "src/plugins/sticky.ts",
"chars": 1842,
"preview": "import {VirtualElement} from '@popperjs/core';\nimport {ReferenceElement, Sticky} from '../types';\n\nconst sticky: Sticky "
},
{
"path": "src/props.ts",
"chars": 4692,
"preview": "import {DefaultProps, Plugin, Props, ReferenceElement, Tippy} from './types';\nimport {\n hasOwnProperty,\n removePropert"
},
{
"path": "src/scss/_mixins.scss",
"chars": 840,
"preview": "@mixin backdrop-transform-enter($placement) {\n $scale: 1;\n @if ($placement == 'top') {\n transform: scale($scale) tr"
},
{
"path": "src/scss/_vars.scss",
"chars": 283,
"preview": "$namespace-prefix: '__NAMESPACE_PREFIX__' !default;\n$placements: 'top', 'bottom', 'left', 'right';\n$origins: bottom, top"
},
{
"path": "src/scss/animations/fade.scss",
"chars": 153,
"preview": "@import '../_mixins.scss';\n@import '../_vars.scss';\n\n.#{$namespace-prefix}-box {\n &[data-animation='fade'][data-state='"
},
{
"path": "src/scss/animations/perspective-extreme.scss",
"chars": 1340,
"preview": "@import '../_mixins.scss';\n@import '../_vars.scss';\n\n@mixin visible-transform($placement) {\n @if ($placement == 'top') "
},
{
"path": "src/scss/animations/perspective-subtle.scss",
"chars": 1333,
"preview": "@import '../_mixins.scss';\n@import '../_vars.scss';\n\n@mixin visible-transform($placement) {\n @if ($placement == 'top') "
},
{
"path": "src/scss/animations/perspective.scss",
"chars": 1326,
"preview": "@import '../_mixins.scss';\n@import '../_vars.scss';\n\n@mixin visible-transform($placement) {\n @if ($placement == 'top') "
},
{
"path": "src/scss/animations/scale-extreme.scss",
"chars": 377,
"preview": "@import '../_mixins.scss';\n@import '../_vars.scss';\n\n.#{$namespace-prefix}-box {\n &[data-animation='scale-extreme'] {\n "
},
{
"path": "src/scss/animations/scale-subtle.scss",
"chars": 375,
"preview": "@import '../_mixins.scss';\n@import '../_vars.scss';\n\n.#{$namespace-prefix}-box {\n &[data-animation='scale-subtle'] {\n "
},
{
"path": "src/scss/animations/scale.scss",
"chars": 368,
"preview": "@import '../_mixins.scss';\n@import '../_vars.scss';\n\n.#{$namespace-prefix}-box {\n &[data-animation='scale'] {\n @each"
},
{
"path": "src/scss/animations/shift-away-extreme.scss",
"chars": 662,
"preview": "@import '../_mixins.scss';\n@import '../_vars.scss';\n\n@mixin hidden-transform($placement) {\n @if ($placement == 'top') {"
},
{
"path": "src/scss/animations/shift-away-subtle.scss",
"chars": 657,
"preview": "@import '../_mixins.scss';\n@import '../_vars.scss';\n\n@mixin hidden-transform($placement) {\n @if ($placement == 'top') {"
},
{
"path": "src/scss/animations/shift-away.scss",
"chars": 628,
"preview": "@import '../_mixins.scss';\n@import '../_vars.scss';\n\n@mixin hidden-transform($placement) {\n @if ($placement == 'top') {"
},
{
"path": "src/scss/animations/shift-toward-extreme.scss",
"chars": 638,
"preview": "@import '../_mixins.scss';\n@import '../_vars.scss';\n\n@mixin hidden-transform($placement) {\n @if ($placement == 'top') {"
},
{
"path": "src/scss/animations/shift-toward-subtle.scss",
"chars": 678,
"preview": "@import '../_mixins.scss';\n@import '../_vars.scss';\n\n@mixin hidden-transform($placement) {\n @if ($placement == 'top') {"
},
{
"path": "src/scss/animations/shift-toward.scss",
"chars": 630,
"preview": "@import '../_mixins.scss';\n@import '../_vars.scss';\n\n@mixin hidden-transform($placement) {\n @if ($placement == 'top') {"
},
{
"path": "src/scss/backdrop.scss",
"chars": 1404,
"preview": "@import './_mixins.scss';\n@import './_vars.scss';\n\n.#{$namespace-prefix}-box {\n @each $placement in $placements {\n &"
},
{
"path": "src/scss/border.scss",
"chars": 1548,
"preview": "@import './_vars.scss';\n\n.#{$namespace-prefix}-box {\n border: 1px transparent;\n\n &[data-placement^='top'] > .#{$namesp"
},
{
"path": "src/scss/index.scss",
"chars": 1761,
"preview": "@import './_vars.scss';\n@import './animations/fade.scss';\n\n$color: #333;\n\n[data-#{$namespace-prefix}-root] {\n max-width"
},
{
"path": "src/scss/svg-arrow.scss",
"chars": 912,
"preview": "@import './_vars.scss';\n\n.#{$namespace-prefix}-box {\n &[data-placement^='top'] > .#{$namespace-prefix}-svg-arrow {\n "
},
{
"path": "src/scss/themes/light-border.scss",
"chars": 3090,
"preview": "@import '../_mixins.scss';\n@import '../_vars.scss';\n\n$color: white;\n$transparent-light: rgba(0, 8, 16, 0.08);\n$transpare"
},
{
"path": "src/scss/themes/light.scss",
"chars": 861,
"preview": "@import '../_mixins.scss';\n@import '../_vars.scss';\n\n$color: white;\n\n.#{$namespace-prefix}-box[data-theme~='light'] {\n "
},
{
"path": "src/scss/themes/material.scss",
"chars": 726,
"preview": "@import '../_mixins.scss';\n@import '../_vars.scss';\n\n$color: #505355;\n\n.#{$namespace-prefix}-box[data-theme~='material']"
},
{
"path": "src/scss/themes/translucent.scss",
"chars": 1240,
"preview": "@import '../_mixins.scss';\n@import '../_vars.scss';\n\n$color: rgba(0, 0, 0, 0.7);\n\n.#{$namespace-prefix}-box[data-theme~="
},
{
"path": "src/template.ts",
"chars": 3933,
"preview": "import {\n ARROW_CLASS,\n BACKDROP_CLASS,\n BOX_CLASS,\n CONTENT_CLASS,\n SVG_ARROW_CLASS,\n} from './constants';\nimport "
},
{
"path": "src/types-internal.ts",
"chars": 480,
"preview": "import {State} from '@popperjs/core';\nimport {Props} from './types';\n\nexport interface ListenerObject {\n node: Element;"
},
{
"path": "src/types.ts",
"chars": 7111,
"preview": "import * as Popper from '@popperjs/core';\n\nexport type BasePlacement = Popper.BasePlacement;\n\nexport type Placement = Po"
},
{
"path": "src/utils.ts",
"chars": 2510,
"preview": "import {BasePlacement, Placement} from './types';\n\nexport function hasOwnProperty(\n obj: Record<string, unknown>,\n key"
},
{
"path": "src/validation.ts",
"chars": 2332,
"preview": "import {Targets} from './types';\n\nexport function createMemoryLeakWarning(method: string): string {\n const txt = method"
},
{
"path": "test/functional/border.test.js",
"chars": 487,
"preview": "/**\n * @jest-environment puppeteer\n */\nimport {navigateToTest, screenshotTest} from '../utils';\n\ndescribe('border', () ="
},
{
"path": "test/functional/followCursor.test.js",
"chars": 4587,
"preview": "/**\n * @jest-environment puppeteer\n */\nimport {navigateToTest, screenshotTest} from '../utils';\n\nfunction generateSelect"
},
{
"path": "test/functional/inlinePositioning.test.js",
"chars": 490,
"preview": "/**\n * @jest-environment puppeteer\n */\nimport {navigateToTest, screenshotTest} from '../utils';\n\ndescribe('inlinePositio"
},
{
"path": "test/functional/sticky.test.js",
"chars": 479,
"preview": "/**\n * @jest-environment puppeteer\n */\nimport {navigateToTest, screenshotTest} from '../utils';\n\ndescribe('sticky', () ="
},
{
"path": "test/functional/themes.test.js",
"chars": 451,
"preview": "/**\n * @jest-environment puppeteer\n */\nimport {navigateToTest, screenshotTest} from '../utils';\n\ndescribe('themes', () ="
},
{
"path": "test/image-reporter.js",
"chars": 1214,
"preview": "const {red, bold} = require('colorette');\nconst fs = require('fs');\nconst poster = require('poster');\n\n// https://api.an"
},
{
"path": "test/integration/__snapshots__/createTippy.test.js.snap",
"chars": 2511,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`createTippy returns the instance with expected properties 1`] = `\nO"
},
{
"path": "test/integration/__snapshots__/props.test.js.snap",
"chars": 3085,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`content DocumentFragment is injected into the tippy node 1`] = `\n<d"
},
{
"path": "test/integration/addons/createSingleton.test.js",
"chars": 15311,
"preview": "import {fireEvent} from '@testing-library/dom';\nimport {h} from '../../utils';\n\nimport createSingleton from '../../../sr"
},
{
"path": "test/integration/addons/delegate.test.js",
"chars": 5396,
"preview": "import {fireEvent} from '@testing-library/dom';\nimport {h} from '../../utils';\n\nimport delegate from '../../../src/addon"
},
{
"path": "test/integration/bindGlobalEventListeners.test.js",
"chars": 1315,
"preview": "import {h} from '../utils';\n\nimport tippy from '../../src';\nimport * as Listeners from '../../src/bindGlobalEventListene"
},
{
"path": "test/integration/createTippy.test.js",
"chars": 18262,
"preview": "import {fireEvent} from '@testing-library/dom';\nimport {h, enableTouchEnvironment, disableTouchEnvironment} from '../uti"
},
{
"path": "test/integration/plugins/__snapshots__/inlinePositioning.test.js.snap",
"chars": 327,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`inlinePositioning does not have its popper modifier removed when up"
},
{
"path": "test/integration/plugins/animateFill.test.js",
"chars": 3283,
"preview": "import {h} from '../../utils';\n\nimport tippy from '../../../src';\nimport animateFill from '../../../src/plugins/animateF"
},
{
"path": "test/integration/plugins/followCursor.test.js",
"chars": 8077,
"preview": "import {fireEvent} from '@testing-library/dom';\nimport {h, enableTouchEnvironment, disableTouchEnvironment} from '../../"
},
{
"path": "test/integration/plugins/inlinePositioning.test.js",
"chars": 481,
"preview": "import {h} from '../../utils';\n\nimport tippy from '../../../src';\nimport inlinePositioning from '../../../src/plugins/in"
},
{
"path": "test/integration/props.test.js",
"chars": 42191,
"preview": "import {fireEvent, createEvent} from '@testing-library/dom';\nimport {h, enableTouchEnvironment, disableTouchEnvironment}"
},
{
"path": "test/setup.js",
"chars": 945,
"preview": "import 'expect-puppeteer';\n\nimport tippy from '../src';\nimport {render} from '../src/template';\nimport {cleanDocumentBod"
},
{
"path": "test/unit/__snapshots__/props.test.js.snap",
"chars": 841,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`evaluateProps considers plugin props 1`] = `\nObject {\n \"aria\": Obj"
},
{
"path": "test/unit/__snapshots__/tippy.test.js.snap",
"chars": 1320,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`tippy merges the default props with the supplied props 1`] = `\nObje"
},
{
"path": "test/unit/css.test.js",
"chars": 1449,
"preview": "import {injectCSS} from '../../src/css';\n\nafterEach(() => {\n document.head.innerHTML = '';\n});\n\ndescribe('injectCSS', ("
},
{
"path": "test/unit/dom-utils.test.js",
"chars": 3630,
"preview": "import {h, IDENTIFIER} from '../utils';\n\nimport * as DomUtils from '../../src/dom-utils';\nimport tippy from '../../src';"
},
{
"path": "test/unit/props.test.js",
"chars": 6230,
"preview": "import {h} from '../utils';\n\nimport {\n getDataAttributeProps,\n evaluateProps,\n validateProps,\n setDefaultProps,\n} fr"
},
{
"path": "test/unit/tippy.test.js",
"chars": 3858,
"preview": "import {h} from '../utils';\n\nimport {defaultProps, extraProps} from '../../src/props';\nimport {POPPER_SELECTOR} from '.."
},
{
"path": "test/unit/utils.test.js",
"chars": 6170,
"preview": "import * as Utils from '../../src/utils';\n\ndescribe('hasOwnProperty', () => {\n it('works for plain objects', () => {\n "
},
{
"path": "test/unit/validation.test.js",
"chars": 1106,
"preview": "import {\n validateTargets,\n getFormattedMessage,\n warnWhen,\n errorWhen,\n} from '../../src/validation';\n\ndescribe('va"
},
{
"path": "test/utils.js",
"chars": 1538,
"preview": "import {\n onDocumentMouseMove,\n onDocumentTouchStart,\n} from '../src/bindGlobalEventListeners';\nimport tippy from '../"
},
{
"path": "test/visual/index.css",
"chars": 1963,
"preview": "*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\nbody {\n font-family: Arial !important;\n}\n\n#controls {\n position:"
},
{
"path": "test/visual/index.html",
"chars": 3318,
"preview": "<!DOCTYPE html> <title>test</title>\n\n<meta charset=\"utf8\" />\n<link rel=\"stylesheet\" href=\"dist/bundle.css\" />\n<link rel="
},
{
"path": "test/visual/index.js",
"chars": 1335,
"preview": "const containers = document.querySelectorAll('.container');\n\ncontainers.forEach((container) => {\n const button = docume"
},
{
"path": "test/visual/tests.js",
"chars": 9486,
"preview": "import tippy from '../../src';\nimport {render} from '../../src/template';\nimport {ROUND_ARROW as roundArrow} from '../.."
},
{
"path": "tsconfig.json",
"chars": 295,
"preview": "{\n \"compilerOptions\": {\n \"lib\": [\"dom\", \"es2015\"],\n \"moduleResolution\": \"node\",\n \"noEmit\": true,\n \"resolveJ"
},
{
"path": "website/.eslintignore",
"chars": 7,
"preview": "../dist"
},
{
"path": "website/.gitignore",
"chars": 973,
"preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directo"
},
{
"path": "website/LICENSE",
"chars": 1076,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2015 gatsbyjs\n\nPermission is hereby granted, free of charge, to any person obtainin"
},
{
"path": "website/README.md",
"chars": 5356,
"preview": "<!-- AUTO-GENERATED-CONTENT:START (STARTER) -->\n<p align=\"center\">\n <a href=\"https://www.gatsbyjs.org\">\n <img alt=\"G"
},
{
"path": "website/gatsby-config.js",
"chars": 1773,
"preview": "module.exports = {\n pathPrefix: '/tippyjs',\n siteMetadata: {\n title: `Tippy.js`,\n description: `The complete too"
},
{
"path": "website/gatsby-node.js",
"chars": 544,
"preview": "exports.createPages = ({graphql, actions}) => {\n const {createRedirect} = actions;\n\n const paths = [\n 'creating-too"
},
{
"path": "website/package.json",
"chars": 2622,
"preview": "{\n \"name\": \"gatsby-starter-default\",\n \"private\": true,\n \"description\": \"A simple starter to get up and developing qui"
},
{
"path": "website/scripts/should-deploy-docs.js",
"chars": 1541,
"preview": "#!/usr/bin/env node\n\nconst {execSync} = require('child_process');\n\nconst {TRAVIS_COMMIT_RANGE = '', TRAVIS_COMMIT_MESSAG"
},
{
"path": "website/scripts/should-deploy-docs.sh",
"chars": 504,
"preview": "#!/bin/bash\n\nset -e\n\nlocal NODE_OUPUT=$(node ./scripts/should-deploy-docs.js)\n\necho \"$NODE_OUPUT\"\n\n# match anything exce"
},
{
"path": "website/src/components/ElasticScroll.js",
"chars": 889,
"preview": "import React, {useEffect, useRef, cloneElement} from 'react';\nimport elasticScroll from 'elastic-scroll-polyfill';\n\nfunc"
},
{
"path": "website/src/components/Footer.js",
"chars": 230,
"preview": "import styled from '@emotion/styled';\nimport Theme from '../css/theme';\n\nconst Footer = styled.footer`\n text-align: cen"
},
{
"path": "website/src/components/Framework.js",
"chars": 3223,
"preview": "import React from 'react';\nimport styled from '@emotion/styled';\nimport {css} from '@emotion/core';\nimport {Link as Gats"
},
{
"path": "website/src/components/Header.js",
"chars": 4939,
"preview": "import React, {Component} from 'react';\nimport styled from '@emotion/styled';\nimport {css, keyframes} from '@emotion/cor"
},
{
"path": "website/src/components/Icon.js",
"chars": 526,
"preview": "import React, {forwardRef} from 'react';\nimport {css} from '@emotion/core';\n\nconst Icon = forwardRef(({src, alt, float, "
},
{
"path": "website/src/components/Image.js",
"chars": 1055,
"preview": "import React from 'react';\nimport {StaticQuery, graphql} from 'gatsby';\nimport Img from 'gatsby-image';\nimport styled fr"
},
{
"path": "website/src/components/Layout.js",
"chars": 4613,
"preview": "import React, {Component} from 'react';\nimport {SkipNavLink, SkipNavContent} from '@reach/skip-nav';\nimport {MDXProvider"
},
{
"path": "website/src/components/Main.js",
"chars": 169,
"preview": "import styled from '@emotion/styled';\nimport {MEDIA} from './Framework';\n\nconst Main = styled.main`\n ${MEDIA.lg} {\n "
},
{
"path": "website/src/components/MiniHeader.js",
"chars": 1431,
"preview": "import React, {Component} from 'react';\nimport styled from '@emotion/styled';\nimport TippyLogo from '../images/logo.svg'"
},
{
"path": "website/src/components/Nav.js",
"chars": 6921,
"preview": "import React, {Component, createRef} from 'react';\nimport styled from '@emotion/styled';\nimport {StaticQuery, graphql} f"
},
{
"path": "website/src/components/NavButtons.js",
"chars": 3966,
"preview": "import React from 'react';\nimport {Link, graphql, StaticQuery} from 'gatsby';\nimport styled from '@emotion/styled';\nimpo"
},
{
"path": "website/src/components/PluginIcon.js",
"chars": 716,
"preview": "import React from 'react';\nimport Icon from './Icon';\nimport Tippy from './Tippy';\n\nimport plugin from '../images/plugin"
},
{
"path": "website/src/components/RenderIcon.js",
"chars": 703,
"preview": "import React from 'react';\nimport Icon from './Icon';\nimport Tippy from './Tippy';\n\nimport render from '../images/render"
},
{
"path": "website/src/components/SEO.js",
"chars": 2360,
"preview": "import React from 'react';\nimport Helmet from 'react-helmet';\nimport {StaticQuery, graphql} from 'gatsby';\n\nfunction SEO"
},
{
"path": "website/src/components/TextGradient.js",
"chars": 269,
"preview": "import styled from '@emotion/styled';\n\nconst TextGradient = styled.span`\n background: linear-gradient(45deg, #fff2df, #"
},
{
"path": "website/src/components/Tippy.js",
"chars": 2332,
"preview": "import React, {forwardRef} from 'react';\nimport Tippy, {useSingleton, tippy} from '@tippyjs/react';\nimport {\n roundArro"
},
{
"path": "website/src/components/TippyTransition.js",
"chars": 7280,
"preview": "import {cloneElement, Children} from 'react';\nimport Flipper from 'react-flip-toolkit/es/core';\nimport {useInstance} fro"
},
{
"path": "website/src/components/examples/Ajax.js",
"chars": 1169,
"preview": "import React, {useState} from 'react';\nimport Tippy from '../Tippy';\nimport {Button} from '../Framework';\n\nfunction Img("
},
{
"path": "website/src/components/examples/ContextMenu.js",
"chars": 1583,
"preview": "import React, {useEffect, useRef} from 'react';\nimport {tippy} from '../Tippy';\nimport {css} from '@emotion/core';\n\nfunc"
},
{
"path": "website/src/components/examples/Dropdown.js",
"chars": 2172,
"preview": "import React, {forwardRef} from 'react';\nimport styled from '@emotion/styled';\nimport {css} from '@emotion/core';\nimport"
},
{
"path": "website/src/components/examples/EventDelegation.js",
"chars": 701,
"preview": "import React from 'react';\nimport Tippy from '../Tippy';\nimport {Button} from '../Framework';\n\nfunction EventDelegation("
},
{
"path": "website/src/components/examples/ImageTransition.js",
"chars": 1816,
"preview": "import React, {useState, useRef} from 'react';\nimport styled from '@emotion/styled';\nimport Tippy from '../Tippy';\nimpor"
},
{
"path": "website/src/components/examples/Nesting.js",
"chars": 1057,
"preview": "import React from 'react';\nimport {css} from '@emotion/core';\nimport Tippy from '../Tippy';\nimport {Button} from '../Fra"
},
{
"path": "website/src/components/examples/Singleton.js",
"chars": 963,
"preview": "import React, {cloneElement} from 'react';\nimport Tippy, {useSingleton} from '../Tippy';\nimport {Button} from '../Framew"
},
{
"path": "website/src/components/examples/TextTransition.js",
"chars": 1960,
"preview": "import React, {useState, useEffect} from 'react';\nimport styled from '@emotion/styled';\nimport Tippy from '../Tippy';\nim"
},
{
"path": "website/src/components/examples/TriggerTarget.js",
"chars": 688,
"preview": "import React, {useState, useEffect, useRef} from 'react';\nimport Tippy from '../Tippy';\nimport styled from '@emotion/sty"
},
{
"path": "website/src/components/examples/mouseRestPlugin.js",
"chars": 1055,
"preview": "export default {\n name: 'mouseRest',\n defaultValue: false,\n fn(instance) {\n const {reference} = instance;\n cons"
},
{
"path": "website/src/css/index.js",
"chars": 9687,
"preview": "import React from 'react';\nimport {MEDIA} from '../components/Framework';\nimport {Global, css} from '@emotion/core';\n\nco"
},
{
"path": "website/src/css/theme.js",
"chars": 123,
"preview": "export default {\n border: 'rgba(0, 32, 128, 0.12)',\n gradient: 'linear-gradient(135deg, #00acff, #6f99fc) no-repeat',\n"
},
{
"path": "website/src/hooks/index.js",
"chars": 120,
"preview": "import {useState} from 'react';\n\nexport function useInstance(initialValue = {}) {\n return useState(initialValue)[0];\n}\n"
},
{
"path": "website/src/pages/.prettierrc.json",
"chars": 78,
"preview": "{\n \"singleQuote\": true,\n \"bracketSpacing\": false,\n \"proseWrap\": \"always\"\n}\n"
},
{
"path": "website/src/pages/404.js",
"chars": 453,
"preview": "import React from 'react';\nimport Layout from '../components/Layout';\nimport SEO from '../components/SEO';\n\nfunction Not"
},
{
"path": "website/src/pages/index.mdx",
"chars": 10988,
"preview": "---\ntitle: Demo\npath: /\nindex: 0\n---\n\nimport {ALL_PLACEMENTS, EXTRA_ANIMATIONS} from '../utils';\nimport Dropdown from '."
},
{
"path": "website/src/pages/v5/accessibility.mdx",
"chars": 3045,
"preview": "---\ntitle: Accessibility\npath: /v5/accessibility/\nindex: 12\n---\n\nTooltip and popovers are usually not mouse-only UI elem"
},
{
"path": "website/src/pages/v5/addons.mdx",
"chars": 4077,
"preview": "---\ntitle: Addons\npath: /v5/addons/\nindex: 13\n---\n\nAddons are external functions that control or create many different T"
},
{
"path": "website/src/pages/v5/ajax.mdx",
"chars": 3665,
"preview": "---\ntitle: AJAX\npath: /v5/ajax/\nindex: 11\n---\n\nimport Ajax from '../../components/examples/Ajax';\n\nInitiating AJAX reque"
},
{
"path": "website/src/pages/v5/all-props.mdx",
"chars": 25760,
"preview": "---\ntitle: All Props\npath: /v5/all-props/\nindex: 4\n---\n\n| Prop "
},
{
"path": "website/src/pages/v5/animations.mdx",
"chars": 3008,
"preview": "---\ntitle: Animations\npath: /v5/animations/\nindex: 7\n---\n\nimport ImageTransition from '../../components/examples/ImageTr"
},
{
"path": "website/src/pages/v5/creating-tooltips.mdx",
"chars": 2536,
"preview": "---\ntitle: Creating Tooltips\npath: /v5/creating-tooltips/\nindex: 2\n---\n\nGive elements you would like to give tooltips to"
},
{
"path": "website/src/pages/v5/customizing-tooltips.mdx",
"chars": 1205,
"preview": "---\ntitle: Customizing Tooltips\npath: /v5/customizing-tooltips/\nindex: 3\n---\n\nAs seen in the demo, the `tippy()` functio"
},
{
"path": "website/src/pages/v5/faq.mdx",
"chars": 6378,
"preview": "---\ntitle: FAQ\npath: /v5/faq/\nindex: 16\n---\n\n### What syntax theme is used on this website?\n\nIt's a theme I made called\n"
},
{
"path": "website/src/pages/v5/getting-started.mdx",
"chars": 3034,
"preview": "---\ntitle: Getting Started\npath: /v5/getting-started/\nindex: 1\n---\n\nThere are two ways to install the package.\n\n### 1. P"
},
{
"path": "website/src/pages/v5/html-content.mdx",
"chars": 2027,
"preview": "---\ntitle: HTML Content\npath: /v5/html-content/\nindex: 5\n---\n\nThe `content` prop can accept a string, element, or functi"
},
{
"path": "website/src/pages/v5/lifecycle-hooks.mdx",
"chars": 2747,
"preview": "---\ntitle: Lifecycle Hooks\npath: /v5/lifecycle-hooks/\nindex: 10\n---\n\nLifecycle hooks provide a way to run code in respon"
},
{
"path": "website/src/pages/v5/methods.mdx",
"chars": 1819,
"preview": "---\ntitle: Methods\npath: /v5/methods/\nindex: 9\n---\n\n### Instance methods\n\nMethods on instances allow you to control the "
},
{
"path": "website/src/pages/v5/misc.mdx",
"chars": 4096,
"preview": "---\ntitle: Misc\npath: /v5/misc/\nindex: 15\n---\n\nimport EventDelegation from '../../components/examples/EventDelegation';\n"
},
{
"path": "website/src/pages/v5/motivation.mdx",
"chars": 3718,
"preview": "---\ntitle: Motivation\npath: /v5/motivation/\nindex: 17\n---\n\n### Why tooltips and popovers?\n\nBoth are elements positioned "
},
{
"path": "website/src/pages/v5/plugins.mdx",
"chars": 2896,
"preview": "---\ntitle: Plugins\npath: /v5/plugins/\nindex: 14\n---\n\nPlugins are an extensible way to add functionality to tippy instanc"
},
{
"path": "website/src/pages/v5/themes.mdx",
"chars": 4668,
"preview": "---\ntitle: Themes\npath: /v5/themes/\nindex: 6\n---\n\nTippies can have any custom styling via CSS.\n\n### Included themes\n\nThe"
},
{
"path": "website/src/pages/v5/tippy-instance.mdx",
"chars": 1362,
"preview": "---\ntitle: Tippy Instance\npath: /v5/tippy-instance/\nindex: 8\n---\n\nA tippy instance is an individual tippy object. It has"
},
{
"path": "website/src/pages/v6/accessibility.mdx",
"chars": 3005,
"preview": "---\ntitle: Accessibility\npath: /v6/accessibility/\nindex: 11\n---\n\nTooltip and popovers are usually not mouse-only UI elem"
},
{
"path": "website/src/pages/v6/addons.mdx",
"chars": 5347,
"preview": "---\ntitle: Addons\npath: /v6/addons/\nindex: 12\n---\n\nAddons are external functions that control or create many different T"
},
{
"path": "website/src/pages/v6/ajax.mdx",
"chars": 3311,
"preview": "---\ntitle: AJAX\npath: /v6/ajax/\nindex: 10\n---\n\nimport Ajax from '../../components/examples/Ajax';\n\nInitiating AJAX reque"
},
{
"path": "website/src/pages/v6/all-props.mdx",
"chars": 21041,
"preview": "---\ntitle: All Props\npath: /v6/all-props/\nindex: 4\n---\n\nimport {BOLD_HELLO} from '../../utils';\nimport RenderIcon from '"
},
{
"path": "website/src/pages/v6/animations.mdx",
"chars": 3350,
"preview": "---\ntitle: Animations\npath: /v6/animations/\nindex: 7\n---\n\nimport ImageTransition from '../../components/examples/ImageTr"
},
{
"path": "website/src/pages/v6/browser-support.mdx",
"chars": 996,
"preview": "---\ntitle: Browser Support\npath: /v6/browser-support/\nindex: 18\n---\n\nAll modern browsers are supported. Proxy mobile bro"
},
{
"path": "website/src/pages/v6/constructor.mdx",
"chars": 1886,
"preview": "---\ntitle: Constructor\npath: /v6/constructor/\nindex: 2\n---\n\nThe `tippy()` constructor (a plain function) creates individ"
},
{
"path": "website/src/pages/v6/customization.mdx",
"chars": 1201,
"preview": "---\ntitle: Customization\npath: /v6/customization/\nindex: 3\n---\n\n`tippy()` takes an object of optional props (configurati"
},
{
"path": "website/src/pages/v6/faq.mdx",
"chars": 5536,
"preview": "---\ntitle: FAQ\npath: /v6/faq/\nindex: 16\n---\n\n### What syntax theme is used on this website?\n\nIt's a theme I made called\n"
},
{
"path": "website/src/pages/v6/getting-started.mdx",
"chars": 2753,
"preview": "---\ntitle: Getting Started\npath: /v6/getting-started/\nindex: 1\n---\n\nThere are two ways to install the package.\n\n### 1. P"
},
{
"path": "website/src/pages/v6/headless-tippy.mdx",
"chars": 3219,
"preview": "---\ntitle: Headless Tippy\npath: /v6/headless-tippy/\nindex: 14\n---\n\n\"Headless Tippy\" refers to Tippy without any of the d"
},
{
"path": "website/src/pages/v6/html-content.mdx",
"chars": 2084,
"preview": "---\ntitle: HTML Content\npath: /v6/html-content/\nindex: 5\n---\n\nThe `content` prop can accept a string, element, or functi"
},
{
"path": "website/src/pages/v6/methods.mdx",
"chars": 2507,
"preview": "---\ntitle: Methods\npath: /v6/methods/\nindex: 9\n---\n\n### Instance methods\n\nMethods on instances allow you to control the "
},
{
"path": "website/src/pages/v6/misc.mdx",
"chars": 3707,
"preview": "---\ntitle: Misc\npath: /v6/misc/\nindex: 15\n---\n\nimport EventDelegation from '../../components/examples/EventDelegation';\n"
},
{
"path": "website/src/pages/v6/motivation.mdx",
"chars": 3138,
"preview": "---\ntitle: Motivation\npath: /v6/motivation/\nindex: 17\n---\n\n### Why tooltips and popovers?\n\nBoth are elements positioned "
},
{
"path": "website/src/pages/v6/plugins.mdx",
"chars": 3638,
"preview": "---\ntitle: Plugins\npath: /v6/plugins/\nindex: 13\n---\n\nPlugins are an extensible way to add functionality to tippy instanc"
},
{
"path": "website/src/pages/v6/themes.mdx",
"chars": 5717,
"preview": "---\ntitle: Themes\npath: /v6/themes/\nindex: 6\n---\n\nTippies can have any custom styling via CSS.\n\n### Included themes\n\nThe"
},
{
"path": "website/src/pages/v6/tippy-instance.mdx",
"chars": 1362,
"preview": "---\ntitle: Tippy Instance\npath: /v6/tippy-instance/\nindex: 8\n---\n\nA tippy instance is an individual tippy object. It has"
},
{
"path": "website/src/utils.js",
"chars": 1364,
"preview": "import React from 'react';\nimport {version} from '../../package.json';\n\nexport const CURRENT_MAJOR = `v${version.split('"
}
]
About this extraction
This page contains the full source code of the atomiks/tippyjs GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 199 files (532.8 KB), approximately 138.1k tokens, and a symbol index with 214 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.