├── .gitignore ├── docs ├── static │ ├── icon.png │ ├── preview.png │ ├── preview-dark.png │ ├── logo-dark.svg │ └── logo-light.svg ├── .env.example ├── nuxt.config.js ├── .gitignore ├── content │ ├── en │ │ ├── examples │ │ │ ├── context-menu.md │ │ │ ├── trigger-target.md │ │ │ └── advanced-follow-mouse.md │ │ ├── configuration.md │ │ ├── flavor │ │ │ ├── directive.md │ │ │ ├── composition-api.md │ │ │ └── component.md │ │ ├── installation.md │ │ ├── index.md │ │ ├── basic-usage.md │ │ ├── animations.md │ │ ├── themes.md │ │ └── props.md │ └── settings.json ├── package.json ├── README.md └── components │ └── global │ ├── FeatureList.vue │ └── Logo.vue ├── .prettierrc ├── playground ├── Vetur.vue ├── shim.d.ts ├── App.vue ├── components │ ├── Button.vue │ ├── Counter.vue │ └── Icon.vue ├── pages │ ├── Wga.vue │ ├── SingletonComponents.vue │ ├── AppContext.vue │ ├── ReactiveProps.vue │ ├── NestedComponents.vue │ ├── Theme.vue │ ├── Testing.vue │ ├── ReactiveState.vue │ └── Index.vue ├── index.html ├── tsconfig.json └── main.ts ├── src ├── global.d.ts ├── composables │ ├── index.ts │ ├── useTippyComponent.ts │ ├── useSingleton.ts │ └── useTippy.ts ├── plugin │ └── index.ts ├── index.ts ├── types │ └── index.ts ├── components │ ├── TippySingleton.ts │ └── Tippy.ts └── directive │ └── index.ts ├── index.cjs ├── index.js ├── tsconfig.json ├── vetur ├── attributes.json └── tags.json ├── api-extractor.json ├── index.html ├── LICENSE ├── CONTRIBUTING.md ├── README.md ├── webpack.config.js ├── package.json └── rollup.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | yarn-error.log -------------------------------------------------------------------------------- /docs/static/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KABBOUCHI/vue-tippy/HEAD/docs/static/icon.png -------------------------------------------------------------------------------- /docs/static/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KABBOUCHI/vue-tippy/HEAD/docs/static/preview.png -------------------------------------------------------------------------------- /docs/static/preview-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KABBOUCHI/vue-tippy/HEAD/docs/static/preview-dark.png -------------------------------------------------------------------------------- /docs/.env.example: -------------------------------------------------------------------------------- 1 | 2 | # Create one with no scope selected on https://github.com/settings/tokens/new 3 | GITHUB_TOKEN= 4 | -------------------------------------------------------------------------------- /docs/nuxt.config.js: -------------------------------------------------------------------------------- 1 | import { withDocus } from 'docus' 2 | 3 | export default withDocus({ 4 | components: true, 5 | }) 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "trailingComma": "es5", 4 | "singleQuote": true, 5 | "arrowParens": "avoid" 6 | } 7 | -------------------------------------------------------------------------------- /playground/Vetur.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.iml 3 | .idea 4 | *.log* 5 | .nuxt 6 | .vscode 7 | .DS_Store 8 | coverage 9 | dist 10 | sw.* 11 | .env 12 | -------------------------------------------------------------------------------- /playground/shim.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import { Component } from 'vue' 3 | var component: Component 4 | export default component 5 | } 6 | -------------------------------------------------------------------------------- /src/global.d.ts: -------------------------------------------------------------------------------- 1 | // Global compile-time constants 2 | declare var __DEV__: boolean 3 | declare var __BROWSER__: boolean 4 | declare var __CI__: boolean 5 | -------------------------------------------------------------------------------- /src/composables/index.ts: -------------------------------------------------------------------------------- 1 | export { useTippy } from './useTippy' 2 | export { useTippyComponent } from './useTippyComponent' 3 | export { useSingleton } from './useSingleton' 4 | -------------------------------------------------------------------------------- /index.cjs: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | if (process.env.NODE_ENV === 'production') { 4 | module.exports = require('./dist/vue-tippy.prod.cjs') 5 | } else { 6 | module.exports = require('./dist/vue-tippy.cjs') 7 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | if (process.env.NODE_ENV === 'production') { 4 | module.exports = require('./dist/vue-tippy.prod.cjs') 5 | } else { 6 | module.exports = require('./dist/vue-tippy.cjs') 7 | } -------------------------------------------------------------------------------- /playground/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 10 | -------------------------------------------------------------------------------- /docs/content/en/examples/context-menu.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Context Menu 3 | description: '' 4 | category: Examples 5 | csb_link: https://codesandbox.io/embed/vue-tippy-context-menu-j6ofb?module=/src/App.js&hidenavigation=1&theme=dark 6 | fullscreen: true 7 | --- 8 | 9 | 10 | -------------------------------------------------------------------------------- /playground/components/Button.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | -------------------------------------------------------------------------------- /docs/content/en/examples/trigger-target.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Trigger Target 3 | description: '' 4 | category: Examples 5 | csb_link: https://codesandbox.io/embed/vue-tippy-different-trigger-target-jssjn?module=/src/App.js&hidenavigation=1&theme=dark 6 | fullscreen: true 7 | --- 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-tippy", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "nuxt", 7 | "build": "nuxt build", 8 | "start": "nuxt start", 9 | "generate": "nuxt generate" 10 | }, 11 | "dependencies": { 12 | "docus": "^0.0.9", 13 | "nuxt": "^2.14.7" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /docs/content/en/examples/advanced-follow-mouse.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Advanced - Custom Follow Mouse 3 | menuTitle: Custom Follow Mouse 4 | description: '' 5 | category: Examples 6 | csb_link: https://codesandbox.io/embed/vue-tippy-follow-mouse-jsewo?module=/src/App.js&hidenavigation=1&theme=dark 7 | fullscreen: true 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /docs/content/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "VueTippy", 3 | "url": "https://vue-tippy.netlify.app", 4 | "github": { 5 | "repo": "KABBOUCHI/vue-tippy", 6 | "branch": "next", 7 | "dir": "docs", 8 | "releases": false 9 | }, 10 | "twitter": "@KABBOUCHI", 11 | "algolia": { 12 | "apiKey": "37a8c0266932ce63f6edc40c19aa4e55", 13 | "indexName": "vue-tippy" 14 | } 15 | } -------------------------------------------------------------------------------- /playground/components/Counter.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 23 | -------------------------------------------------------------------------------- /playground/pages/Wga.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | -------------------------------------------------------------------------------- /playground/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 |

Vue 3

14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # vue-tippy 2 | 3 | ## Setup 4 | 5 | Install dependencies: 6 | 7 | ```bash 8 | yarn install 9 | ``` 10 | 11 | ## Development 12 | 13 | ```bash 14 | yarn dev 15 | ``` 16 | 17 | ## Static Generation 18 | 19 | This will create the `dist/` directory for publishing to static hosting: 20 | 21 | ```bash 22 | yarn generate 23 | ``` 24 | 25 | To preview the static generated app, run `yarn start` 26 | 27 | For detailed explanation on how things work, checkout [nuxt/content](https://content.nuxtjs.org) and [@nuxt/content theme docs](https://content.nuxtjs.org/themes-docs). 28 | -------------------------------------------------------------------------------- /docs/content/en/configuration.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Configuration 3 | description: '' 4 | category: Getting started 5 | position: 3 6 | --- 7 | 8 | ### Using global installation 9 | 10 | ```js 11 | import { createApp } from 'vue' 12 | import VueTippy from 'vue-tippy' 13 | 14 | const app = createApp({}) 15 | 16 | app.use(VueTippy, { 17 | defaultProps: { placement: 'right' }, 18 | }) 19 | 20 | app.mount('#app') 21 | ``` 22 | 23 | ### Using global function 24 | 25 | ```js 26 | import { setDefaultProps } from 'vue-tippy' 27 | 28 | setDefaultProps({ 29 | placement: 'right', 30 | }) 31 | ``` 32 | -------------------------------------------------------------------------------- /docs/content/en/flavor/directive.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Directive 3 | description: '' 4 | position: 11 5 | category: Flavor 6 | --- 7 | 8 | Work in progess 9 | 10 | Simple and elegant, great for adding simple tooltips for any element. 11 | 12 | Click [here](/props) to see full documentation on props. 13 | 14 | ## Basic Usage 15 | 16 | ```html 17 | 20 | ``` 21 | 22 | ## Advanced Usage 23 | 24 | ```html 25 | 28 | ``` 29 | -------------------------------------------------------------------------------- /playground/pages/SingletonComponents.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | -------------------------------------------------------------------------------- /src/composables/useTippyComponent.ts: -------------------------------------------------------------------------------- 1 | import { h, ref } from 'vue' 2 | import { TippyComponent, TippyOptions } from '../types' 3 | import Tippy from './../components/Tippy' 4 | 5 | export function useTippyComponent( 6 | opts: TippyOptions = {}, 7 | children?: any 8 | ) { 9 | const instance = ref() 10 | 11 | return { 12 | instance, 13 | TippyComponent: h( 14 | Tippy, 15 | { 16 | ...opts as any, 17 | onVnodeMounted: (vnode:any) => { 18 | //@ts-ignore 19 | instance.value = vnode.component.ctx 20 | }, 21 | }, 22 | children 23 | ), 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /playground/pages/AppContext.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /src/plugin/index.ts: -------------------------------------------------------------------------------- 1 | import TippyComponent from '../components/Tippy' 2 | import TippySingletonComponent from '../components/TippySingleton' 3 | import directive from '../directive' 4 | import { Plugin } from 'vue' 5 | import tippy from 'tippy.js' 6 | import { TippyPluginOptions } from '../types' 7 | 8 | const plugin: Plugin = { 9 | install(app, options: TippyPluginOptions = {}) { 10 | tippy.setDefaultProps(options.defaultProps || {}) 11 | 12 | app.directive(options.directive || 'tippy', directive) 13 | app.component(options.component || 'tippy', TippyComponent) 14 | app.component(options.componentSingleton || 'tippy-singleton', TippySingletonComponent) 15 | }, 16 | } 17 | 18 | export default plugin 19 | -------------------------------------------------------------------------------- /playground/components/Icon.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src/**/*"], 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "rootDir": ".", 6 | "outDir": "dist", 7 | "sourceMap": false, 8 | "noEmit": true, 9 | 10 | "target": "es2019", 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "allowJs": false, 14 | 15 | "noUnusedLocals": true, 16 | "strictNullChecks": true, 17 | "noImplicitAny": true, 18 | "noImplicitThis": true, 19 | "noImplicitReturns": true, 20 | "strict": true, 21 | "isolatedModules": false, 22 | 23 | "experimentalDecorators": true, 24 | "resolveJsonModule": true, 25 | "esModuleInterop": true, 26 | "removeComments": false, 27 | "jsx": "preserve", 28 | "lib": ["esnext", "dom"], 29 | "types": ["node"] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /vetur/attributes.json: -------------------------------------------------------------------------------- 1 | { 2 | "tippy/content": { 3 | "type": "any", 4 | "description": "The content of the tippy" 5 | }, 6 | "tippy/allowHTML": { 7 | "type": "boolean", 8 | "description": "Determines if content strings are parsed as HTML instead of text" 9 | }, 10 | "tippy/arrow": { 11 | "type": "boolean|string|Element", 12 | "description": "Determines if the tippy has an arrow" 13 | }, 14 | "tippy/theme": { 15 | "type": "string", 16 | "description": "Determines the theme of the tippy element." 17 | }, 18 | "tippy/sticky": { 19 | "type": "boolean|string", 20 | "description": "Determines if the tippy sticks to the reference element while it is mounted." 21 | }, 22 | "tippy/followCursor": { 23 | "type": "boolean|string", 24 | "description": "Determines if the tippy follows the user's mouse cursor." 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import tippy, { 2 | sticky, 3 | inlinePositioning, 4 | followCursor, 5 | animateFill, 6 | roundArrow, 7 | hideAll, 8 | } from 'tippy.js' 9 | 10 | import Tippy from './components/Tippy' 11 | import TippySingleton from './components/TippySingleton' 12 | import directive from './directive' 13 | import plugin from './plugin' 14 | 15 | import { useTippy } from './composables/useTippy' 16 | import { useTippyComponent } from './composables/useTippyComponent' 17 | import { useSingleton } from './composables/useSingleton' 18 | 19 | const setDefaultProps = tippy.setDefaultProps 20 | 21 | setDefaultProps({ 22 | ignoreAttributes: true, 23 | plugins: [sticky, inlinePositioning, followCursor, animateFill], 24 | }) 25 | 26 | export { 27 | useTippy, 28 | useTippyComponent, 29 | roundArrow, 30 | tippy, 31 | hideAll, 32 | useSingleton, 33 | setDefaultProps, 34 | Tippy, 35 | TippySingleton, 36 | directive, 37 | plugin, 38 | } 39 | 40 | export * from './types' 41 | export default plugin 42 | -------------------------------------------------------------------------------- /playground/pages/ReactiveProps.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 36 | -------------------------------------------------------------------------------- /vetur/tags.json: -------------------------------------------------------------------------------- 1 | { 2 | "tippy": { 3 | "attributes": [ 4 | "content", 5 | "arrow", 6 | "theme", 7 | "sticky", 8 | "followCursor", 9 | "allowHTML", 10 | "a11y", 11 | "flip", 12 | "flipOnUpdate", 13 | "hideOnClick", 14 | "ignoreAttributes", 15 | "inertia", 16 | "interactive", 17 | "lazy", 18 | "multiple", 19 | "showOnInit", 20 | "touch", 21 | "touchHold", 22 | "animateFill", 23 | "inlinePositioning", 24 | "animation", 25 | "maxWidth", 26 | "role", 27 | "aria", 28 | "delay", 29 | "duration", 30 | "getReferenceClientRect", 31 | "interactiveBorder", 32 | "interactiveDebounce", 33 | "moveTransition", 34 | "offset", 35 | "placement", 36 | "plugins", 37 | "popperOptions", 38 | "render", 39 | "showOnCreate", 40 | "trigger", 41 | "triggerTarget" 42 | ], 43 | "description": "Vue component for Tippy.js" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /api-extractor.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", 3 | "mainEntryPointFilePath": "./dist/src/index.d.ts", 4 | "apiReport": { 5 | "enabled": false, 6 | "reportFolder": "/temp/" 7 | }, 8 | "docModel": { 9 | "enabled": false 10 | }, 11 | "dtsRollup": { 12 | "enabled": true, 13 | "untrimmedFilePath": "./dist/.d.ts" 14 | }, 15 | "tsdocMetadata": { 16 | "enabled": false 17 | }, 18 | "messages": { 19 | "compilerMessageReporting": { 20 | "default": { 21 | "logLevel": "warning" 22 | } 23 | }, 24 | "extractorMessageReporting": { 25 | "default": { 26 | "logLevel": "warning", 27 | "addToApiReportFile": true 28 | }, 29 | "ae-missing-release-tag": { 30 | "logLevel": "none" 31 | } 32 | }, 33 | "tsdocMessageReporting": { 34 | "default": { 35 | "logLevel": "warning" 36 | } 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /playground/pages/NestedComponents.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 41 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | VueTippy v6 7 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /playground/pages/Theme.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Georges KABBOUCHI 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /docs/content/en/installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Installation 3 | description: '' 4 | category: Getting started 5 | position: 2 6 | --- 7 | 8 | Add `vue-tippy` dependency to your project: 9 | 10 | 11 | 12 | 13 | ```bash 14 | yarn add vue-tippy@v6 15 | ``` 16 | 17 | 18 | 19 | 20 | ```bash 21 | npm install vue-tippy@v6 22 | ``` 23 | 24 | 25 | 26 | ### Install VueTippy globally (optional) 27 | 28 | ```js 29 | import { createApp } from 'vue' 30 | 31 | import VueTippy from 'vue-tippy' 32 | // or 33 | import { plugin as VueTippy } from 'vue-tippy' 34 | import 'tippy.js/dist/tippy.css' // optional for styling 35 | 36 | const app = createApp({}) 37 | 38 | app.use( 39 | VueTippy, 40 | // optional 41 | { 42 | directive: 'tippy', // => v-tippy 43 | component: 'tippy', // => 44 | componentSingleton: 'tippy-singleton', // => , 45 | defaultProps: { 46 | placement: 'auto-end', 47 | allowHTML: true, 48 | }, // => Global default options * see all props 49 | } 50 | ) 51 | 52 | app.mount('#app') 53 | ``` 54 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are **welcome** and will be fully **credited**. 4 | 5 | We accept contributions via Pull Requests on [Github](https://github.com/KABBOUCHI/vue-tippy). 6 | 7 | 8 | ## Pull Requests 9 | 10 | - **Keep the same style** - eslint will automatically be ran before committing 11 | 12 | - **Tip** to pass lint tests easier use the `npm run lint:fix` command 13 | 14 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests. 15 | 16 | - **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. 17 | 18 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option. 19 | 20 | - **Create feature branches** - Don't ask us to pull from your master branch. 21 | 22 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. 23 | 24 | - **Send coherent history** - Make sure your commits message means something 25 | 26 | 27 | ## Running Tests 28 | 29 | Launch visual tests and watch the components at the same time 30 | 31 | ``` bash 32 | $ npm run dev 33 | ``` 34 | 35 | 36 | **Happy coding**! 37 | -------------------------------------------------------------------------------- /playground/pages/Testing.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | -------------------------------------------------------------------------------- /src/composables/useSingleton.ts: -------------------------------------------------------------------------------- 1 | import { TippyInstance, TippyInstances } from '../types' 2 | import { 3 | createSingleton, 4 | CreateSingletonProps, 5 | Instance, 6 | Props, 7 | } from 'tippy.js' 8 | import { onMounted, ref } from 'vue' 9 | 10 | export function useSingleton( 11 | instances: TippyInstances, 12 | optionalProps?: Partial> 13 | ) { 14 | const singleton = ref>() 15 | 16 | onMounted(() => { 17 | const pendingTippyInstances: TippyInstance[] = Array.isArray(instances) 18 | ? instances.map(i => i.value) 19 | : typeof instances === 'function' 20 | ? instances() 21 | : instances.value 22 | 23 | const tippyInstances: Instance[] = pendingTippyInstances 24 | .map((instance: TippyInstance) => { 25 | if (instance instanceof Element) { 26 | //@ts-ignore 27 | return instance._tippy 28 | } 29 | return instance 30 | }) 31 | .filter(Boolean) 32 | 33 | singleton.value = createSingleton( 34 | tippyInstances, 35 | optionalProps 36 | ? { allowHTML: true, ...optionalProps } 37 | : { allowHTML: true } 38 | ) 39 | }) 40 | 41 | return { 42 | singleton, 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /docs/content/en/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Introduction 3 | description: 'What is VueTippy?' 4 | position: 1 5 | category: 'Getting started' 6 | features: 7 | - 'Optimized positioning engine for flipping and overflow prevention' 8 | - 'Accessible by default, WAI-ARIA compliant' 9 | - 'Themeable, style via custom CSS, includes extra themes and animations' 10 | - 'TypeScript support out of the box' 11 | - 'Works as Vue Directive v-tippy' 12 | - 'Works as Vue Component <tippy/>' 13 | - 'Has compostion api useTippy()' 14 | --- 15 | 16 |
17 | 18 | 19 | VueTippy 20 | 21 |
22 | 23 | Tippy.js is the complete tooltip, popover, dropdown, and menu solution for the web, powered by Popper. 24 |
25 |
26 | `VueTippy` is a Vue 3 wrapper for the `Tippy.js` library, which allows you to use the tooltips as a Vue component and as a directive. 27 | 28 | It is designed to work-friendly with `Vue.js` 29 | 30 | ## Features 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | import Tippy from '../components/Tippy' 2 | import { useTippy } from '../composables' 3 | import { Props, Content, DefaultProps, Instance } from 'tippy.js' 4 | import { VNode, Ref, Component, UnwrapNestedRefs } from 'vue' 5 | 6 | export declare type TippyContent = Content | VNode | Component | Ref 7 | export declare type TippyTarget = 8 | | Element 9 | | Element[] 10 | | Ref 11 | | Ref 12 | | null 13 | 14 | export declare type TippyOptions = Partial< 15 | Omit & { 16 | content: TippyContent 17 | triggerTarget: TippyTarget 18 | getReferenceClientRect: null | (() => DOMRect & any) 19 | } 20 | > 21 | 22 | export declare type TippyComponent = InstanceType & UnwrapNestedRefs< 23 | Pick< 24 | ReturnType, 25 | 'tippy' | 'refresh' | 'refreshContent' | 'setContent' | 'setProps' | 'destroy' | 'hide' | 'show' | 'disable' | 'enable' | 'unmount' | 'mount' | 'state' 26 | > 27 | > 28 | 29 | export interface TippyPluginOptions { 30 | directive?: string 31 | component?: string 32 | componentSingleton?: string 33 | defaultProps?: Partial 34 | } 35 | 36 | export type TippyInstance = Instance | Element | undefined 37 | export type TippyInstances = Ref[] | Ref | (() => TippyInstance[]) 38 | -------------------------------------------------------------------------------- /playground/pages/ReactiveState.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 62 | -------------------------------------------------------------------------------- /playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["./**/*.ts"], 3 | "compilerOptions": { 4 | "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */, 5 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, 6 | // "lib": ["es2017.object"] /* Specify library files to be included in the compilation. */, 7 | // "declaration": true /* Generates corresponding '.d.ts' file. */, 8 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 9 | "sourceMap": true /* Generates corresponding '.map' file. */, 10 | // "outFile": "./", /* Concatenate and emit output to single file. */ 11 | "outDir": "./dist" /* Redirect output structure to the directory. */, 12 | 13 | "strict": true /* Enable all strict type-checking options. */, 14 | "noUnusedLocals": true /* Report errors on unused locals. */, 15 | "noImplicitReturns": true /* Report error when not all code paths in function return a value. */, 16 | 17 | "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */, 18 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /docs/content/en/basic-usage.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Basic Usage 3 | description: '' 4 | category: Getting started 5 | position: 4 6 | --- 7 | 8 | ### Vue Directive 9 | 10 | ```html 11 | 15 | 16 | 20 | 29 | ``` 30 | 31 | ### Vue Component 32 | 33 | ```html 34 | 47 | 48 | 52 | 59 | ``` 60 | 61 | ### Composition API 62 | 63 | ```js 64 | import { defineComponent, ref, h } from 'vue' 65 | import { useTippy } from 'vue-tippy' 66 | 67 | export default defineComponent({ 68 | setup() { 69 | const button = ref() 70 | 71 | useTippy(button, { 72 | content: 'Hi!', 73 | }) 74 | 75 | return () => h('button', { ref: button }, 'Tippy!') 76 | }, 77 | }) 78 | ``` 79 | -------------------------------------------------------------------------------- /docs/components/global/FeatureList.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 54 | 55 | 77 | -------------------------------------------------------------------------------- /playground/main.ts: -------------------------------------------------------------------------------- 1 | // necessary for webpack 2 | /// 3 | import { createApp } from 'vue' 4 | import { createRouter, createWebHistory } from 'vue-router' 5 | import VueTippy from '../src' 6 | import "tippy.js/dist/tippy.css"; 7 | import 'tippy.js/themes/light.css' 8 | import 'tippy.js/themes/material.css' 9 | import App from './App.vue' 10 | import PageIndex from './pages/Index.vue' 11 | import PageNestedComponents from './pages/NestedComponents.vue' 12 | import PageSingletonComponents from './pages/SingletonComponents.vue' 13 | import PageWga from './pages/Wga.vue' 14 | import PageAppContext from './pages/AppContext.vue' 15 | import ReactiveProps from './pages/ReactiveProps.vue' 16 | import Testing from './pages/Testing.vue' 17 | import ReactiveState from './pages/ReactiveState.vue' 18 | import Theme from './pages/Theme.vue' 19 | import Counter from './components/Counter.vue' 20 | import UiIcon from "./components/Icon.vue"; 21 | 22 | const router = createRouter({ 23 | history: createWebHistory(), 24 | routes: [ 25 | { path: '/', component: PageIndex }, 26 | { path: '/nested-components', component: PageNestedComponents }, 27 | { path: '/singleton-components', component: PageSingletonComponents }, 28 | { path: '/testing', component: Testing }, 29 | { path: '/reactive-props', component: ReactiveProps }, 30 | { path: '/reactive-state', component: ReactiveState }, 31 | { path: '/theme', component: Theme }, 32 | { path: '/wga', component: PageWga }, 33 | { path: '/app-context', component: PageAppContext }, 34 | ] 35 | }) 36 | 37 | 38 | const app = createApp(App) 39 | app.component('counter', Counter) 40 | app.component("ui-icon", UiIcon); 41 | 42 | app.provide('settings', { 43 | appName: 'Vue Tippy', 44 | }) 45 | 46 | app.use(router) 47 | app.use(VueTippy, { 48 | defaultProps: { placement: 'right' }, 49 | }) 50 | app.mount('#app') -------------------------------------------------------------------------------- /src/components/TippySingleton.ts: -------------------------------------------------------------------------------- 1 | import { Instance } from 'tippy.js' 2 | import { ComponentObjectPropsOptions, defineComponent, h, ref } from 'vue' 3 | import { useSingleton } from '../composables' 4 | import { TippyOptions } from '../types' 5 | import tippy, { DefaultProps } from 'tippy.js' 6 | 7 | declare module 'vue' { 8 | interface ComponentCustomProps extends TippyOptions {} 9 | } 10 | 11 | const booleanProps = [ 12 | 'a11y', 13 | 'allowHTML', 14 | 'arrow', 15 | 'flip', 16 | 'flipOnUpdate', 17 | 'hideOnClick', 18 | 'ignoreAttributes', 19 | 'inertia', 20 | 'interactive', 21 | 'lazy', 22 | 'multiple', 23 | 'showOnInit', 24 | 'touch', 25 | 'touchHold', 26 | ] 27 | 28 | let props: ComponentObjectPropsOptions = {} 29 | 30 | Object.keys(tippy.defaultProps).forEach((prop: string) => { 31 | if (booleanProps.includes(prop)) { 32 | props[prop] = { 33 | type: Boolean, 34 | default: function () { 35 | return tippy.defaultProps[prop as keyof DefaultProps] as Boolean 36 | }, 37 | } 38 | } else { 39 | props[prop] = { 40 | default: function () { 41 | return tippy.defaultProps[prop as keyof DefaultProps] 42 | }, 43 | } 44 | } 45 | }) 46 | 47 | 48 | const TippySingleton = defineComponent({ 49 | props, 50 | setup(props) { 51 | const instances = ref[]>([]) 52 | 53 | const { singleton } = useSingleton(instances, props) 54 | 55 | return { instances, singleton } 56 | }, 57 | mounted() { 58 | const parent = this.$el.parentElement 59 | const elements = parent.querySelectorAll('[data-v-tippy]') 60 | 61 | this.instances = Array.from(elements) 62 | .map((el: any) => el._tippy) 63 | .filter(Boolean) 64 | 65 | this.singleton?.setInstances(this.instances) 66 | }, 67 | render() { 68 | let slot = this.$slots.default ? this.$slots.default() : [] 69 | 70 | return h(() => slot) 71 | }, 72 | }) 73 | 74 | export default TippySingleton 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VueTippy - V6 2 | 3 | [![npm](https://img.shields.io/npm/v/vue-tippy/latest.svg)](https://www.npmjs.com/package/vue-tippy) [![vue2](https://img.shields.io/badge/vue-3.x-brightgreen.svg)](https://vuejs.org/) [![download](https://img.shields.io/npm/dt/vue-tippy.svg)](https://www.npmjs.com/package/vue-tippy) 4 | 5 | > Vue.js 3 wrapper for Tippy.js 6 | 7 | 10 | 11 | 12 | ## Documentation 13 | 14 | For full v6 documentation, visit [https://vue-tippy.netlify.app/](https://vue-tippy.netlify.app/). 15 | 16 | ## Installation 17 | 18 | ```js 19 | npm install vue-tippy@v6 20 | 21 | //or 22 | 23 | yarn add vue-tippy@v6 24 | ``` 25 | 26 | ## Configuration (optional) 27 | 28 | ```js 29 | import { createApp } from 'vue' 30 | 31 | import VueTippy from 'vue-tippy' 32 | // or 33 | import { plugin as VueTippy } from 'vue-tippy' 34 | 35 | const app = createApp({}) 36 | 37 | app.use( 38 | VueTippy, 39 | // optional 40 | { 41 | directive: 'tippy', // => v-tippy 42 | component: 'tippy', // => 43 | } 44 | ) 45 | 46 | app.mount('#app') 47 | ``` 48 | 49 | ## Usage 50 | 51 | ### Vue Directive 52 | 53 | ```html 54 | 58 | 59 | 62 | ``` 63 | 64 | ### Vue Component 65 | 66 | ```html 67 | 72 | 73 | 76 | ``` 77 | 78 | ### Using composition api 79 | 80 | ```js 81 | import { defineComponent, ref, h } from 'vue' 82 | import { useTippy } from 'vue-tippy' 83 | 84 | export default defineComponent({ 85 | setup() { 86 | const button = ref() 87 | 88 | useTippy(button, { 89 | content: 'Hi!', 90 | }) 91 | 92 | return () => h('button', { ref: button }, 'Tippy!') 93 | }, 94 | }) 95 | ``` 96 | -------------------------------------------------------------------------------- /docs/content/en/flavor/composition-api.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Composition API 3 | description: '' 4 | position: 13 5 | category: Flavor 6 | --- 7 | 8 | Work in progess 9 | 10 | Low level, flexible composition, great for build tooltips with complex interactions. 11 | 12 | Click [here](/props) to see full documentation on props. 13 | 14 | ## Basic Usage 15 | 16 | ```vue 17 | 20 | 21 | 29 | ``` 30 | 31 | ## Advanced Usage 32 | 33 | ### Example 1 34 | 35 | ```vue 36 | 39 | 40 | 65 | ``` 66 | 67 | ### Example 2 68 | 69 | ```vue 70 | 73 | 74 | 85 | ``` 86 | 87 | ## Singleton 88 | 89 | ### Example 1 90 | 91 | ```vue 92 | 98 | 99 | 109 | ``` 110 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path') 2 | const HtmlWebpackPlugin = require('html-webpack-plugin') 3 | const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer') 4 | const { VueLoaderPlugin } = require('vue-loader') 5 | const webpack = require('webpack') 6 | 7 | const outputPath = resolve(__dirname, 'playground_dist') 8 | 9 | /** @type {import('webpack').ConfigurationFactory} */ 10 | const config = (env = {}) => { 11 | const extraPlugins = env.prod ? [new BundleAnalyzerPlugin()] : [] 12 | 13 | return { 14 | mode: env.prod ? 'production' : 'development', 15 | devtool: env.prod ? 'source-map' : 'inline-source-map', 16 | 17 | devServer: { 18 | contentBase: outputPath, 19 | historyApiFallback: true, 20 | hot: true, 21 | stats: 'minimal', 22 | }, 23 | 24 | output: { 25 | path: outputPath, 26 | publicPath: '/', 27 | filename: 'bundle.js', 28 | }, 29 | 30 | entry: [resolve(__dirname, 'playground/main.ts')], 31 | module: { 32 | rules: [ 33 | { 34 | test: /\.css$/i, 35 | use: ['style-loader', 'css-loader'], 36 | }, 37 | { 38 | test: /\.ts$/, 39 | use: { 40 | loader: 'ts-loader', 41 | options: { appendTsSuffixTo: [/\.vue$/] }, 42 | }, 43 | }, 44 | { 45 | test: /\.vue$/, 46 | use: 'vue-loader', 47 | }, 48 | ], 49 | }, 50 | resolve: { 51 | alias: { 52 | // this isn't technically needed, since the default `vue` entry for bundlers 53 | // is a simple `export * from '@vue/runtime-dom`. However having this 54 | // extra re-export somehow causes webpack to always invalidate the module 55 | // on the first HMR update and causes the page to reload. 56 | vue: '@vue/runtime-dom', 57 | }, 58 | // Add `.ts` and `.tsx` as a resolvable extension. 59 | extensions: ['.ts', 'd.ts', '.tsx', '.js', '.vue'], 60 | }, 61 | plugins: [ 62 | new VueLoaderPlugin(), 63 | new HtmlWebpackPlugin({ 64 | template: resolve(__dirname, 'playground/index.html'), 65 | }), 66 | new webpack.DefinePlugin({ 67 | __DEV__: JSON.stringify(!env.prod), 68 | __BROWSER__: 'true', 69 | 'process.env': { 70 | NODE_ENV: JSON.stringify(process.env.NODE_ENV), 71 | }, 72 | }), 73 | ...extraPlugins, 74 | ], 75 | } 76 | } 77 | 78 | module.exports = config 79 | -------------------------------------------------------------------------------- /docs/content/en/flavor/component.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Component 3 | description: '' 4 | position: 12 5 | category: Flavor 6 | --- 7 | 8 | Work in progess 9 | 10 | Simple, high-level, great for building complex tooltips with interactions 11 | 12 | Click [here](/props) to see full documentation on props. 13 | 14 | `` additional props: 15 | 16 | | Prop | Type | Default | Description | 17 | | :-------------- | :-------------------- | :------ | :-------------------- | 18 | | `tag` | `String` or `null` | `span` | Trigger wrapper tag | 19 | | `content-tag` | `String` | `span` | Content wrapper tag | 20 | | `content-class` | `String` | `null` | Content wrapper class | 21 | | `to` | `Element` or `String` | `null` | Target selector | 22 | 23 | ## Basic Usage 24 | 25 | ```html 26 | 27 | 28 | 29 | ``` 30 | 31 | ## Advanced Usage 32 | 33 | ## Example 0 34 | 35 | Mount tippy on the child node instead of default tag 36 | 37 | ```html 38 | 39 | 40 | 41 | ``` 42 | 43 | ### Example 1 44 | 45 | ```html 46 | 52 | 58 | 59 | 62 | 63 | ``` 64 | 65 | ### Example 2 66 | 67 | ```html 68 | 69 | 70 | 71 | 72 | ``` 73 | 74 | ### Example 3 75 | Tippy as a child using `to="parent"`. \ 76 | Works like `:tag="null"`, but applies the tooltip to the parent element instead of the child. 77 | 78 | ```html 79 | 85 | ``` 86 | 87 | ## Singleton 88 | 89 | ### Example 1 90 | 91 | ```html 92 | 93 | 94 | Button 1 95 | 96 | 97 | 98 | Button 2 99 | 100 | 101 | ``` 102 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-tippy", 3 | "version": "6.7.1", 4 | "main": "index.js", 5 | "module": "dist/vue-tippy.mjs", 6 | "unpkg": "dist/vue-tippy.iife.js", 7 | "jsdelivr": "dist/vue-tippy.iife.js", 8 | "types": "dist/vue-tippy.d.ts", 9 | "exports": { 10 | ".": { 11 | "types": "./dist/vue-tippy.d.ts", 12 | "browser": "./dist/vue-tippy.esm-browser.js", 13 | "node": { 14 | "import": "./dist/vue-tippy.mjs", 15 | "require": { 16 | "production": "./dist/vue-tippy.prod.cjs", 17 | "development": "./dist/vue-tippy.cjs", 18 | "default": "./index.js" 19 | } 20 | }, 21 | "import": "./dist/vue-tippy.mjs" 22 | }, 23 | "./package.json": "./package.json", 24 | "./dist/*": "./dist/*" 25 | }, 26 | "sideEffects": false, 27 | "license": "MIT", 28 | "files": [ 29 | "src/**/*", 30 | "dist/*.js", 31 | "dist/*.mjs", 32 | "dist/*.cjs", 33 | "dist/vue-tippy.d.ts", 34 | "index.js", 35 | "index.cjs", 36 | "vetur/*.json", 37 | "tsconfig.json", 38 | "LICENSE", 39 | "README.md" 40 | ], 41 | "scripts": { 42 | "build": "NODE_ENV=production rollup -c rollup.config.js", 43 | "build:dts": "api-extractor run --local --verbose", 44 | "dev": "webpack-dev-server --mode=development", 45 | "dev:docs": "cd docs && yarn dev", 46 | "prepublishOnly": "yarn build && yarn build:dts" 47 | }, 48 | "peerDependencies": { 49 | "vue": "^3.2.0" 50 | }, 51 | "devDependencies": { 52 | "@microsoft/api-extractor": "7.8.1", 53 | "@rollup/plugin-alias": "^3.1.1", 54 | "@rollup/plugin-commonjs": "^14.0.0", 55 | "@rollup/plugin-node-resolve": "^8.4.0", 56 | "@rollup/plugin-replace": "^2.3.3", 57 | "@types/webpack": "^4.41.21", 58 | "@types/webpack-env": "^1.15.2", 59 | "@vue/compiler-sfc": "^3.2.0", 60 | "css-loader": "^4.2.0", 61 | "html-webpack-plugin": "^4.3.0", 62 | "rollup": "^2.23.0", 63 | "rollup-plugin-css-only": "^2.1.0", 64 | "rollup-plugin-postcss": "^3.1.3", 65 | "rollup-plugin-terser": "^6.1.0", 66 | "rollup-plugin-typescript2": "^0.27.1", 67 | "style-loader": "^1.2.1", 68 | "ts-loader": "^8.0.2", 69 | "ts-node": "^8.10.2", 70 | "typescript": "^4.1.3", 71 | "vue": "^3.2.0", 72 | "vue-loader": "^16.0.0-beta.8", 73 | "vue-router": "4", 74 | "webpack": "^4.44.1", 75 | "webpack-bundle-analyzer": "^3.8.0", 76 | "webpack-cli": "^3.3.12", 77 | "webpack-dev-server": "^3.11.0" 78 | }, 79 | "dependencies": { 80 | "tippy.js": "^6.3.7" 81 | }, 82 | "vetur": { 83 | "tags": "vetur/tags.json", 84 | "attributes": "vetur/attributes.json" 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /docs/static/logo-dark.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 23 | 24 | 25 | 26 | 27 | 28 | 31 | 33 | 34 | 35 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /docs/static/logo-light.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 23 | 24 | 25 | 26 | 27 | 28 | 31 | 33 | 34 | 35 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/directive/index.ts: -------------------------------------------------------------------------------- 1 | import { useTippy } from '../composables' 2 | import { Directive } from 'vue' 3 | 4 | const directive: Directive = { 5 | mounted(el, binding, vnode) { 6 | const opts = typeof binding.value === "string" ? { content: binding.value } : binding.value || {} 7 | 8 | const modifiers = Object.keys(binding.modifiers || {}) 9 | const placement = modifiers.find(modifier => modifier !== 'arrow') 10 | const withArrow = modifiers.findIndex(modifier => modifier === 'arrow') !== -1 11 | 12 | if (placement) { 13 | opts.placement = opts.placement || placement 14 | } 15 | 16 | if (withArrow) { 17 | opts.arrow = opts.arrow !== undefined ? opts.arrow : true 18 | } 19 | 20 | if (vnode.props && vnode.props.onTippyShow) { 21 | opts.onShow = function (...args: any[]) { 22 | return vnode.props?.onTippyShow(...args) 23 | } 24 | } 25 | 26 | if (vnode.props && vnode.props.onTippyShown) { 27 | opts.onShown = function (...args: any[]) { 28 | return vnode.props?.onTippyShown(...args) 29 | } 30 | } 31 | 32 | if (vnode.props && vnode.props.onTippyHidden) { 33 | opts.onHidden = function (...args: any[]) { 34 | return vnode.props?.onTippyHidden(...args) 35 | } 36 | } 37 | 38 | if (vnode.props && vnode.props.onTippyHide) { 39 | opts.onHide = function (...args: any[]) { 40 | return vnode.props?.onTippyHide(...args) 41 | } 42 | } 43 | 44 | if (vnode.props && vnode.props.onTippyMount) { 45 | opts.onMount = function (...args: any[]) { 46 | return vnode.props?.onTippyMount(...args) 47 | } 48 | } 49 | 50 | if (el.getAttribute('title') && !opts.content) { 51 | opts.content = el.getAttribute('title') 52 | el.removeAttribute('title') 53 | } 54 | 55 | if (el.getAttribute('content') && !opts.content) { 56 | opts.content = el.getAttribute('content') 57 | } 58 | 59 | useTippy(el, opts) 60 | }, 61 | unmounted(el) { 62 | if (el.$tippy) { 63 | el.$tippy.destroy() 64 | } else if (el._tippy) { 65 | el._tippy.destroy() 66 | } 67 | }, 68 | 69 | updated(el, binding) { 70 | const opts = typeof binding.value === "string" ? { content: binding.value } : binding.value || {} 71 | 72 | // any kind of falsy content value, even undefined, should be treated as null - 'no tooltip to show' 73 | if (!opts.content) { 74 | opts.content = null 75 | } 76 | 77 | if (el.getAttribute('title') && !opts.content) { 78 | opts.content = el.getAttribute('title') 79 | el.removeAttribute('title') 80 | } 81 | 82 | if (el.getAttribute('content') && !opts.content) { 83 | opts.content = el.getAttribute('content') 84 | } 85 | 86 | if (el.$tippy) { 87 | el.$tippy.setProps(opts || {}) 88 | } else if (el._tippy) { 89 | el._tippy.setProps(opts || {}) 90 | } 91 | }, 92 | } 93 | 94 | export default directive 95 | -------------------------------------------------------------------------------- /docs/content/en/animations.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Animations 3 | description: '' 4 | category: Getting started 5 | position: 6 6 | --- 7 | 8 | Tippies use an opacity `fade` transition by default. 9 | 10 | ## Included animations 11 | 12 | The package comes with extra animations for you to use: 13 | 14 | - `shift-away` 15 | - `shift-toward` 16 | - `scale` 17 | - `perspective` 18 | 19 | They need to be imported separately. 20 | 21 | ```js 22 | import 'tippy.js/animations/scale.css' 23 | ``` 24 | 25 | Pass the animation name as the `animation` prop: 26 | 27 | ```js 28 | useTippy(target, { 29 | animation: 'scale', 30 | }) 31 | ``` 32 | 33 | Each of these animations also has 3 variants (normal, subtle, and extreme) using the following format: 34 | 35 | ```js 36 | import 'tippy.js/animations/scale.css' 37 | import 'tippy.js/animations/scale-subtle.css' 38 | import 'tippy.js/animations/scale-extreme.css' 39 | ``` 40 | 41 | ## Custom animations 42 | 43 | To create your own animation: 44 | 45 | - Specify the animation name in the `[data-animation]` attribute selector 46 | - Target the visibility state of the tippy: `[data-state="hidden"]` or `[data-state="visible"]` 47 | - Depending on the animation, target the placement of the tippy too: e.g. `[data-placement^="top"]` 48 | 49 | ```css 50 | .tippy-box[data-animation='rotate'][data-state='hidden'] { 51 | opacity: 0; 52 | transform: rotate(90deg); 53 | } 54 | ``` 55 | 56 | ```js 57 | useTippy(target, { 58 | animation: 'rotate', 59 | }) 60 | ``` 61 | 62 | ## Inertia 63 | 64 | There's a prop named `inertia` that adds an elastic inertial effect to the tippy, which is a limited CSS-only way to mimic spring physics. 65 | 66 | ```js 67 | useTippy(target, { 68 | inertia: true, 69 | }) 70 | ``` 71 | 72 | You can customize this prop in your CSS: 73 | 74 | ```css 75 | .tippy-box[data-inertia][data-state='visible'] { 76 | transition-timing-function: cubic-bezier(...); 77 | } 78 | ``` 79 | 80 | ## Material filling effect 81 | 82 | Import `backdrop.css` & `animations/shift-away.css` stylesheets. 83 | 84 | ```js 85 | import 'tippy.js/dist/backdrop.css' 86 | import 'tippy.js/animations/shift-away.css' 87 | 88 | useTippy(target, { 89 | animateFill: true, 90 | }) 91 | ``` 92 | 93 | ## CSS animations 94 | 95 | Maybe plain transitions aren't enough for your use case. You can also use CSS animations (e.g. `animate.css`): 96 | 97 | ```js 98 | useTippy(target, { 99 | onMount(instance) { 100 | const box = instance.popper.firstElementChild 101 | requestAnimationFrame(() => { 102 | box.classList.add('animated') 103 | box.classList.add('wobble') 104 | }) 105 | }, 106 | onHidden(instance) { 107 | const box = instance.popper.firstElementChild 108 | box.classList.remove('animated') 109 | box.classList.remove('wobble') 110 | }, 111 | }) 112 | ``` 113 | 114 | You can also use `@keyframes` and add the animation property to your `animation` selector too. 115 | -------------------------------------------------------------------------------- /docs/components/global/Logo.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/content/en/themes.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Themes 3 | description: '' 4 | category: Getting started 5 | position: 7 6 | --- 7 | 8 | Tippies can have any custom styling via CSS. 9 | 10 | ## Base Style 11 | 12 | ```js 13 | import 'tippy.js/dist/tippy.css' 14 | ``` 15 | ## Included themes 16 | 17 | The package comes with themes for you to use: 18 | 19 | - `light` 20 | - `light-border` 21 | - `material` 22 | - `translucent` 23 | 24 | They need to be imported separately. 25 | 26 | ```js 27 | import 'tippy.js/themes/light.css' 28 | ``` 29 | 30 | Pass the theme name as the `theme` prop: 31 | 32 | ```js 33 | useTippy(target, { 34 | theme: 'light', 35 | }) 36 | ``` 37 | 38 | ## Tippy elements 39 | 40 | To learn how to create a theme, it's helpful to understand the basic structure of a tippy element: 41 | 42 | ```html 43 |
44 |
45 |
46 | My content 47 |
48 |
49 |
50 | ``` 51 | 52 | A tippy is essentially three nested `div`s. 53 | 54 | - `[data-tippy-root]` is the outermost node. It is what Popper uses to position the tippy. You don't need to apply any styles to this element. 55 | - `tippy-box` is the actual box node. 56 | - `tippy-content` is the content node. 57 | 58 | Depending on the props supplied, there will exist other elements inside it: 59 | 60 | ```html 61 |
62 |
63 |
64 | 65 |
66 | 67 |
68 | My content 69 |
70 |
71 |
72 | ``` 73 | 74 | ## Creating a theme 75 | 76 | Themes are created by including a class on the `tippy-box` element as part of a selector in the form `.tippy-box[data-theme~='theme']`. 77 | 78 | Let's demonstrate this by creating our own theme called`tomato`: 79 | 80 | ```css 81 | .tippy-box[data-theme~='tomato'] { 82 | background-color: tomato; 83 | color: yellow; 84 | } 85 | ``` 86 | 87 | To apply the theme: 88 | 89 | ```js 90 | useTippy(target, { 91 | theme: 'tomato', 92 | }) 93 | ``` 94 | 95 | Or using scoped style 96 | 97 | ```html 98 | 112 | 113 | 116 | 117 | 123 | ``` 124 | 125 | What is `~=`? 126 | 127 | Since `theme` can have multiple names, it allows you to target a single theme inside the space-separated list. Visit [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors) for more information. 128 | 129 | 130 | 131 | ## Styling the arrow 132 | 133 | There are two types of arrows: 134 | 135 | - CSS arrows (using `border-width`) 136 | - SVG arrows (using an `` element) 137 | 138 | ### CSS arrow 139 | 140 | To style the default CSS arrow, target each different base placement using the `placement` prop and apply it to the `.tippy-arrow` element's `::before` pseudo-element: 141 | 142 | ```css 143 | .tippy-box[data-theme~='tomato'][data-placement^='top'] > .tippy-arrow::before { 144 | border-top-color: tomato; 145 | } 146 | .tippy-box[data-theme~='tomato'][data-placement^='bottom'] 147 | > .tippy-arrow::before { 148 | border-bottom-color: tomato; 149 | } 150 | .tippy-box[data-theme~='tomato'][data-placement^='left'] 151 | > .tippy-arrow::before { 152 | border-left-color: tomato; 153 | } 154 | .tippy-box[data-theme~='tomato'][data-placement^='right'] 155 | > .tippy-arrow::before { 156 | border-right-color: tomato; 157 | } 158 | ``` 159 | 160 | ### SVG arrow 161 | 162 | First import the `svg-arrow.css` stylesheet for SVG arrow styling: 163 | 164 | ```js 165 | import 'tippy.js/dist/svg-arrow.css' 166 | ``` 167 | 168 | To color an SVG arrow, specify `fill` and target `.tippy-svg-arrow`: 169 | 170 | ```css 171 | .tippy-box[data-theme~='tomato'] > .tippy-svg-arrow { 172 | fill: tomato; 173 | } 174 | ``` 175 | 176 | The shape isn't dependent on the placement for styling, which is why it doesn't require the CSS arrow's more verbose styles. 177 | 178 | There is a default round arrow SVG shape exported from the package for you to use. 179 | 180 | ```js 181 | import { roundArrow } from 'vue-tippy' 182 | 183 | useTippy(target, { 184 | arrow: roundArrow, 185 | }) 186 | ``` 187 | 188 | ## Changing the arrow size 189 | 190 | ### Option 1: `transform: scale()` 191 | 192 | This is the easiest technique and works for most cases: 193 | 194 | ```css 195 | .tippy-box[data-theme~='tomato'] > .tippy-arrow::before { 196 | transform: scale(1.5); 197 | } 198 | ``` 199 | 200 | ### Option 2: Pixel increase 201 | 202 | If your tippy theme has a `border` (e.g. the included `light-border` theme), then the `transform: scale()` technique distorts the border width of the arrow. Instead, you will need to change the size of the arrow in pixels directly. 203 | 204 | ## Arrow border 205 | 206 | There is a stylesheet to aid in adding borders to your tippies: 207 | 208 | ```js 209 | import 'tippy.js/dist/border.css' 210 | ``` 211 | 212 | This adds color inheritance for borders when using the default CSS arrow, and aids in creating SVG arrow borders. 213 | 214 | ### CSS arrow 215 | 216 | ```css 217 | /* The border of the arrow will now match the border of the box */ 218 | .tippy-box[data-theme~='tomato'] { 219 | background: tomato; 220 | border: 1px solid yellow; 221 | } 222 | ``` 223 | 224 | ### SVG arrow 225 | 226 | Duplicate the SVG arrow so that there are two of them, like so: 227 | 228 | ```js 229 | import { roundArrow } from 'vue-tippy' 230 | 231 | useTippy(target, { 232 | arrow: roundArrow + roundArrow, 233 | }) 234 | ``` 235 | 236 | ```css 237 | /* The border */ 238 | .tippy-box[data-theme~='tomato'] > .tippy-svg-arrow > svg:first-child { 239 | fill: yellow; 240 | } 241 | 242 | /* The fill */ 243 | .tippy-box[data-theme~='tomato'] > .tippy-svg-arrow > svg:last-child { 244 | fill: tomato; 245 | } 246 | ``` 247 | 248 | ## Browser DevTools 249 | 250 | It's highly recommended you inspect a tippy element via your browser's DevTools. An easy way to do this is to give it `hideOnClick: false` and `trigger: 'click'` props so that it stays visible when focus is switched to the DevTools window. 251 | 252 | The tippy element gets appended to the very end of the ``, so you should scroll down the elements panel. If `interactive: true`, then the tippy is appended to the reference element's parentNode instead. 253 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import ts from 'rollup-plugin-typescript2' 3 | import replace from '@rollup/plugin-replace' 4 | import resolve from '@rollup/plugin-node-resolve' 5 | import commonjs from '@rollup/plugin-commonjs' 6 | import pascalcase from 'pascalcase' 7 | 8 | const pkg = require('./package.json') 9 | const name = pkg.name 10 | 11 | function getAuthors(pkg) { 12 | const { contributors, author } = pkg 13 | 14 | const authors = new Set() 15 | if (contributors && contributors) 16 | contributors.forEach((contributor) => { 17 | authors.add(contributor.name) 18 | }) 19 | if (author) authors.add(author.name) 20 | 21 | return Array.from(authors).join(', ') 22 | } 23 | 24 | const banner = `/*! 25 | * ${pkg.name} v${pkg.version} 26 | * (c) ${new Date().getFullYear()} ${getAuthors(pkg)} 27 | * @license MIT 28 | */` 29 | 30 | // ensure TS checks only once for each build 31 | let hasTSChecked = false 32 | 33 | const outputConfigs = { 34 | // each file name has the format: `dist/${name}.${format}.js` 35 | // format being a key of this object 36 | mjs: { 37 | file: pkg.module, 38 | format: `es`, 39 | }, 40 | cjs: { 41 | file: pkg.module.replace('mjs', 'cjs'), 42 | format: `cjs`, 43 | }, 44 | global: { 45 | file: pkg.unpkg, 46 | format: `iife`, 47 | }, 48 | browser: { 49 | file: 'dist/vue-tippy.esm-browser.js', 50 | format: `es`, 51 | }, 52 | } 53 | 54 | const packageBuilds = Object.keys(outputConfigs) 55 | const packageConfigs = packageBuilds.map((format) => 56 | createConfig(format, outputConfigs[format]) 57 | ) 58 | 59 | // only add the production ready if we are bundling the options 60 | packageBuilds.forEach((buildName) => { 61 | if (buildName === 'cjs') { 62 | packageConfigs.push(createProductionConfig(buildName)) 63 | } else if (buildName === 'global') { 64 | packageConfigs.push(createMinifiedConfig(buildName)) 65 | } 66 | }) 67 | 68 | export default packageConfigs 69 | 70 | function createConfig(buildName, output, plugins = []) { 71 | if (!output) { 72 | console.log(require('chalk').yellow(`invalid format: "${buildName}"`)) 73 | process.exit(1) 74 | } 75 | 76 | output.sourcemap = !!process.env.SOURCE_MAP 77 | output.banner = banner 78 | output.externalLiveBindings = false 79 | output.globals = { 80 | vue: 'Vue', 81 | } 82 | 83 | const isProductionBuild = /\.prod\.(js|mjs|cjs)$/.test(output.file) 84 | const isGlobalBuild = buildName === 'global' 85 | const isRawESMBuild = buildName === 'browser' 86 | const isNodeBuild = buildName === 'cjs' 87 | const isBundlerESMBuild = buildName === 'browser' || buildName === 'mjs' 88 | 89 | if (isGlobalBuild) output.name = pascalcase(pkg.name) 90 | 91 | const shouldEmitDeclarations = !hasTSChecked 92 | 93 | const tsPlugin = ts({ 94 | check: !hasTSChecked, 95 | tsconfig: path.resolve(__dirname, './tsconfig.json'), 96 | cacheRoot: path.resolve(__dirname, './node_modules/.rts2_cache'), 97 | tsconfigOverride: { 98 | compilerOptions: { 99 | sourceMap: output.sourcemap, 100 | declaration: shouldEmitDeclarations, 101 | declarationMap: shouldEmitDeclarations, 102 | }, 103 | exclude: ['packages/*/__tests__', 'packages/*/test-dts'], 104 | }, 105 | }) 106 | // we only need to check TS and generate declarations once for each build. 107 | // it also seems to run into weird issues when checking multiple times 108 | // during a single build. 109 | hasTSChecked = true 110 | 111 | const external = ['vue-demi', 'vue', '@vue/composition-api'] 112 | if (!isGlobalBuild) { 113 | external.push('@vue/devtools-api') 114 | } 115 | 116 | const nodePlugins = [resolve(), commonjs()] 117 | 118 | return { 119 | input: `src/index.ts`, 120 | // Global and Browser ESM builds inlines everything so that they can be 121 | // used alone. 122 | external, 123 | plugins: [ 124 | tsPlugin, 125 | createReplacePlugin( 126 | isProductionBuild, 127 | isBundlerESMBuild, 128 | // isBrowserBuild? 129 | isGlobalBuild || isRawESMBuild || isBundlerESMBuild, 130 | isGlobalBuild, 131 | isNodeBuild 132 | ), 133 | ...nodePlugins, 134 | ...plugins, 135 | ], 136 | output, 137 | // onwarn: (msg, warn) => { 138 | // if (!/Circular/.test(msg)) { 139 | // warn(msg) 140 | // } 141 | // }, 142 | } 143 | } 144 | 145 | function createReplacePlugin( 146 | isProduction, 147 | isBundlerESMBuild, 148 | isBrowserBuild, 149 | isGlobalBuild, 150 | isNodeBuild 151 | ) { 152 | const replacements = { 153 | __COMMIT__: `"${process.env.COMMIT}"`, 154 | __VERSION__: `"${pkg.version}"`, 155 | __DEV__: 156 | isBundlerESMBuild || (isNodeBuild && !isProduction) 157 | ? // preserve to be handled by bundlers 158 | `(process.env.NODE_ENV !== 'production')` 159 | : // hard coded dev/prod builds 160 | JSON.stringify(!isProduction), 161 | // this is only used during tests 162 | __TEST__: 163 | isBundlerESMBuild || isNodeBuild 164 | ? `(process.env.NODE_ENV === 'test')` 165 | : 'false', 166 | // If the build is expected to run directly in the browser (global / esm builds) 167 | __BROWSER__: JSON.stringify(isBrowserBuild), 168 | // is targeting bundlers? 169 | __BUNDLER__: JSON.stringify(isBundlerESMBuild), 170 | __GLOBAL__: JSON.stringify(isGlobalBuild), 171 | // is targeting Node (SSR)? 172 | __NODE_JS__: JSON.stringify(isNodeBuild), 173 | 174 | // tippy.js 175 | 'process.env.NODE_ENV': isBundlerESMBuild || isProduction ? '"production"' : '"development"', 176 | } 177 | // allow inline overrides like 178 | //__RUNTIME_COMPILE__=true yarn build 179 | Object.keys(replacements).forEach((key) => { 180 | if (key in process.env) { 181 | replacements[key] = process.env[key] 182 | } 183 | }) 184 | return replace({ 185 | preventAssignment: true, 186 | values: replacements, 187 | }) 188 | } 189 | 190 | function createProductionConfig(format) { 191 | const extension = format === 'cjs' ? 'cjs' : 'js' 192 | const descriptor = format === 'cjs' ? '' : `.${format}` 193 | return createConfig(format, { 194 | file: `dist/${name}${descriptor}.prod.${extension}`, 195 | format: outputConfigs[format].format, 196 | }) 197 | } 198 | 199 | function createMinifiedConfig(format) { 200 | const { terser } = require('rollup-plugin-terser') 201 | return createConfig( 202 | format, 203 | { 204 | file: `dist/${name}.${format === 'global' ? 'iife' : format}.prod.js`, 205 | format: outputConfigs[format].format, 206 | }, 207 | [ 208 | terser({ 209 | module: /^esm/.test(format), 210 | compress: { 211 | ecma: 2015, 212 | pure_getters: true, 213 | }, 214 | }), 215 | ] 216 | ) 217 | } -------------------------------------------------------------------------------- /src/composables/useTippy.ts: -------------------------------------------------------------------------------- 1 | import tippy, { Instance, Props, Content } from 'tippy.js' 2 | import { 3 | ref, 4 | onMounted, 5 | Ref, 6 | isRef, 7 | isReactive, 8 | isVNode, 9 | watch, 10 | VNode, 11 | h, 12 | onUnmounted, 13 | getCurrentInstance, 14 | createApp, 15 | shallowRef, 16 | App, 17 | } from 'vue' 18 | import { TippyOptions, TippyContent } from '../types' 19 | 20 | tippy.setDefaultProps({ 21 | //@ts-ignore 22 | onShow: instance => { 23 | if (!instance.props.content) return false 24 | }, 25 | }) 26 | 27 | const isComponentInstance = (value: any): value is { $el: any } => { 28 | return value instanceof Object && '$' in value && '$el' in value 29 | } 30 | 31 | type TippyElement = Element | any // TODO: use ComponentPublicInstance 32 | 33 | export function useTippy( 34 | el: TippyElement | (() => TippyElement) | Ref | Ref, 35 | opts: TippyOptions = {}, 36 | settings: { 37 | mount: boolean, 38 | appName: string, 39 | } = { mount: true, appName: 'Tippy' } 40 | ) { 41 | settings = Object.assign({ mount: true, appName: 'Tippy' }, settings); 42 | 43 | const vm = getCurrentInstance() 44 | const instance = ref() 45 | const state = ref({ 46 | isEnabled: false, 47 | isVisible: false, 48 | isDestroyed: false, 49 | isMounted: false, 50 | isShown: false, 51 | }) 52 | const headlessApp = shallowRef() 53 | 54 | let container: any = null 55 | 56 | const getContainer = () => { 57 | if (container) return container 58 | container = document.createDocumentFragment() 59 | return container 60 | } 61 | 62 | const getContent = (content: TippyContent): Content => { 63 | let newContent: Content 64 | 65 | let unwrappedContent: Content | VNode | { render: Function } = isRef( 66 | content 67 | ) 68 | ? content.value 69 | : content 70 | 71 | 72 | if (isVNode(unwrappedContent)) { 73 | if (!headlessApp.value) { 74 | headlessApp.value = createApp({ 75 | name: settings.appName, 76 | setup: () => { 77 | return () => isRef(content) ? content.value : content 78 | }, 79 | }) 80 | 81 | if (vm) { 82 | Object.assign(headlessApp.value._context, vm.appContext) 83 | } 84 | 85 | headlessApp.value.mount(getContainer()) 86 | } 87 | newContent = () => getContainer() 88 | } else if (typeof unwrappedContent === 'object') { 89 | if (!headlessApp.value) { 90 | headlessApp.value = createApp({ 91 | name: settings.appName, 92 | setup: () => { 93 | return () => h(isRef(content) ? content.value : content as any) 94 | }, 95 | }) 96 | 97 | if (vm) { 98 | Object.assign(headlessApp.value._context, vm.appContext) 99 | } 100 | 101 | headlessApp.value.mount(getContainer()) 102 | } 103 | 104 | newContent = () => getContainer() 105 | } else { 106 | newContent = unwrappedContent 107 | } 108 | 109 | return newContent! 110 | } 111 | 112 | const getProps = (opts: TippyOptions): Partial => { 113 | let options: any = {} 114 | 115 | if (isRef(opts)) { 116 | options = opts.value || {} 117 | } else if (isReactive(opts)) { 118 | options = { ...opts } 119 | } else { 120 | options = { ...opts } 121 | } 122 | 123 | if (options.content) { 124 | options.content = getContent(options.content) 125 | } 126 | 127 | if (options.triggerTarget) { 128 | options.triggerTarget = isRef(options.triggerTarget) 129 | ? options.triggerTarget.value 130 | : options.triggerTarget 131 | } 132 | 133 | if (!options.plugins || !Array.isArray(options.plugins)) { 134 | options.plugins = [] 135 | } 136 | 137 | options.plugins = options.plugins.filter((plugin: any) => plugin.name !== 'vueTippyReactiveState'); 138 | 139 | options.plugins.push({ 140 | name: 'vueTippyReactiveState', 141 | fn: () => { 142 | return { 143 | onCreate() { 144 | state.value.isEnabled = true 145 | }, 146 | onMount() { 147 | state.value.isMounted = true 148 | }, 149 | onShow() { 150 | state.value.isMounted = true 151 | state.value.isVisible = true 152 | }, 153 | onShown() { 154 | state.value.isShown = true 155 | }, 156 | onHide() { 157 | state.value.isMounted = false 158 | state.value.isVisible = false 159 | }, 160 | onHidden() { 161 | state.value.isShown = false 162 | }, 163 | onUnmounted() { 164 | state.value.isMounted = false 165 | }, 166 | onDestroy() { 167 | state.value.isDestroyed = true 168 | }, 169 | } 170 | } 171 | }) 172 | 173 | return options as Props 174 | } 175 | 176 | const refresh = () => { 177 | if (!instance.value) return 178 | 179 | instance.value.setProps(getProps(opts)) 180 | } 181 | 182 | const refreshContent = () => { 183 | if (!instance.value || !opts.content) return 184 | 185 | instance.value.setContent(getContent(opts.content)) 186 | } 187 | 188 | const setContent = (value: TippyContent) => { 189 | instance.value?.setContent(getContent(value)) 190 | } 191 | 192 | const setProps = (value: TippyOptions) => { 193 | instance.value?.setProps(getProps(value)) 194 | } 195 | 196 | const destroy = () => { 197 | if (instance.value) { 198 | try { 199 | //@ts-ignore 200 | // delete instance.value.reference.$tippy 201 | } catch (error) { } 202 | 203 | instance.value.destroy() 204 | instance.value = undefined 205 | } 206 | container = null 207 | headlessApp.value?.unmount() 208 | headlessApp.value = undefined 209 | } 210 | 211 | const show = () => { 212 | instance.value?.show() 213 | } 214 | 215 | const hide = () => { 216 | instance.value?.hide() 217 | } 218 | 219 | const disable = () => { 220 | instance.value?.disable() 221 | state.value.isEnabled = false; 222 | } 223 | 224 | const enable = () => { 225 | instance.value?.enable() 226 | state.value.isEnabled = true; 227 | } 228 | 229 | const unmount = () => { 230 | instance.value?.unmount() 231 | } 232 | 233 | const mount = () => { 234 | if (!el) return 235 | 236 | let target = isRef(el) ? el.value : el 237 | 238 | if (typeof target === 'function') target = target() 239 | 240 | if (isComponentInstance(target)) { 241 | target = target.$el as Element 242 | } 243 | 244 | if (target) { 245 | //@ts-ignore 246 | instance.value = tippy(target, getProps(opts)) 247 | //@ts-ignore 248 | target.$tippy = response 249 | } 250 | } 251 | 252 | const response = { 253 | tippy: instance, 254 | refresh, 255 | refreshContent, 256 | setContent, 257 | setProps, 258 | destroy, 259 | hide, 260 | show, 261 | disable, 262 | enable, 263 | unmount, 264 | mount, 265 | state, 266 | } 267 | 268 | if (settings.mount) { 269 | if (vm) { 270 | if (vm.isMounted) { 271 | mount() 272 | } else { 273 | onMounted(mount) 274 | } 275 | } else { 276 | mount() 277 | } 278 | } 279 | 280 | if (vm) { 281 | onUnmounted(() => { 282 | destroy() 283 | }) 284 | } 285 | 286 | if (isRef(opts) || isReactive(opts)) { 287 | watch(opts, refresh, { immediate: false }) 288 | } else if (isRef(opts.content)) { 289 | watch(opts.content, refreshContent, { immediate: false }) 290 | } 291 | 292 | return response 293 | } 294 | -------------------------------------------------------------------------------- /src/components/Tippy.ts: -------------------------------------------------------------------------------- 1 | import { defineComponent, ref, h, UnwrapNestedRefs, onMounted, nextTick, watch, unref, reactive, PropType } from 'vue' 2 | import { TippyOptions } from '../types' 3 | import { useTippy } from '../composables' 4 | import tippy from 'tippy.js' 5 | 6 | declare module 'vue' { 7 | interface ComponentCustomProps extends TippyOptions { 8 | to: string | Element 9 | tag: string 10 | contentTag: string 11 | contentClass: string 12 | } 13 | interface ComponentCustomProperties extends UnwrapNestedRefs> { } 14 | } 15 | 16 | function toValue(r: any): any { 17 | return typeof r === 'function' 18 | ? (r as any)() 19 | : unref(r) 20 | } 21 | function unrefElement(elRef: any): any { 22 | const plain = toValue(elRef) 23 | return (plain as any)?.$el ?? plain 24 | } 25 | 26 | const TippyComponent = defineComponent({ 27 | props: { 28 | to: { 29 | type: [String, Function] as PropType, 30 | }, 31 | tag: { 32 | type: [String, Object], 33 | default: 'span' 34 | }, 35 | contentTag: { 36 | type: [String, Object], 37 | default: 'span' 38 | }, 39 | contentClass: { 40 | type: String, 41 | default: null 42 | }, 43 | appendTo: { default: () => tippy.defaultProps['appendTo'] }, 44 | aria: { default: () => tippy.defaultProps['aria'] }, 45 | delay: { default: () => tippy.defaultProps['delay'] }, 46 | duration: { default: () => tippy.defaultProps['duration'] }, 47 | getReferenceClientRect: { default: () => tippy.defaultProps['getReferenceClientRect'] }, 48 | hideOnClick: { type: [Boolean, String], default: () => tippy.defaultProps['hideOnClick'] }, 49 | ignoreAttributes: { type: Boolean, default: () => tippy.defaultProps['ignoreAttributes'] }, 50 | interactive: { type: Boolean, default: () => tippy.defaultProps['interactive'] }, 51 | interactiveBorder: { default: () => tippy.defaultProps['interactiveBorder'] }, 52 | interactiveDebounce: { default: () => tippy.defaultProps['interactiveDebounce'] }, 53 | moveTransition: { default: () => tippy.defaultProps['moveTransition'] }, 54 | offset: { default: () => tippy.defaultProps['offset'] }, 55 | onAfterUpdate: { default: () => tippy.defaultProps['onAfterUpdate'] }, 56 | onBeforeUpdate: { default: () => tippy.defaultProps['onBeforeUpdate'] }, 57 | onCreate: { default: () => tippy.defaultProps['onCreate'] }, 58 | onDestroy: { default: () => tippy.defaultProps['onDestroy'] }, 59 | onHidden: { default: () => tippy.defaultProps['onHidden'] }, 60 | onHide: { default: () => tippy.defaultProps['onHide'] }, 61 | onMount: { default: () => tippy.defaultProps['onMount'] }, 62 | onShow: { default: () => tippy.defaultProps['onShow'] }, 63 | onShown: { default: () => tippy.defaultProps['onShown'] }, 64 | onTrigger: { default: () => tippy.defaultProps['onTrigger'] }, 65 | onUntrigger: { default: () => tippy.defaultProps['onUntrigger'] }, 66 | onClickOutside: { default: () => tippy.defaultProps['onClickOutside'] }, 67 | placement: { default: () => tippy.defaultProps['placement'] }, 68 | plugins: { default: () => tippy.defaultProps['plugins'] }, 69 | popperOptions: { default: () => tippy.defaultProps['popperOptions'] }, 70 | render: { default: () => tippy.defaultProps['render'] }, 71 | showOnCreate: { type: Boolean, default: () => tippy.defaultProps['showOnCreate'] }, 72 | touch: { type: [Boolean, String, Array], default: () => tippy.defaultProps['touch'] }, 73 | trigger: { default: () => tippy.defaultProps['trigger'] }, 74 | triggerTarget: { default: () => tippy.defaultProps['triggerTarget'] }, 75 | animateFill: { type: Boolean, default: () => tippy.defaultProps['animateFill'] }, 76 | followCursor: { type: [Boolean, String], default: () => tippy.defaultProps['followCursor'] }, 77 | inlinePositioning: { type: Boolean, default: () => tippy.defaultProps['inlinePositioning'] }, 78 | sticky: { type: [Boolean, String], default: () => tippy.defaultProps['sticky'] }, 79 | allowHTML: { type: Boolean, default: () => tippy.defaultProps['allowHTML'] }, 80 | animation: { default: () => tippy.defaultProps['animation'] }, 81 | arrow: { default: () => tippy.defaultProps['arrow'] }, 82 | content: { default: () => tippy.defaultProps['content'] }, 83 | inertia: { default: () => tippy.defaultProps['inertia'] }, 84 | maxWidth: { default: () => tippy.defaultProps['maxWidth'] }, 85 | role: { default: () => tippy.defaultProps['role'] }, 86 | theme: { default: () => tippy.defaultProps['theme'] }, 87 | zIndex: { default: () => tippy.defaultProps['zIndex'] } 88 | }, 89 | emits: ['state'], 90 | setup(props, { slots, emit, expose }) { 91 | const elem = ref() 92 | const findParentHelper = ref() 93 | const contentElem = ref() 94 | const mounted = ref(false) 95 | 96 | const getOptions = () => { 97 | let options = { ...props } as any as TippyOptions; 98 | for (const prop of ['to', 'tag', 'contentTag', 'contentClass']) { 99 | if (options.hasOwnProperty(prop)) { 100 | // @ts-ignore 101 | delete options[prop]; 102 | } 103 | } 104 | 105 | return options 106 | } 107 | 108 | let target: any = () => unrefElement(elem) 109 | 110 | if (props.to) { 111 | if (typeof Element !== 'undefined' && props.to instanceof Element) { 112 | target = () => props.to 113 | } else if (props.to === 'parent') { 114 | target = () => { 115 | let el = elem.value 116 | if (!el) { 117 | el = elem.value = findParentHelper.value!.parentElement as HTMLElement 118 | } 119 | return el 120 | } 121 | } else if (typeof props.to === 'string' || props.to instanceof String) { 122 | target = () => document.querySelector(props.to as any) 123 | } 124 | } 125 | 126 | const tippy = useTippy(target, getOptions()) 127 | let contentSlot = slots.content 128 | if (!contentSlot && props.to === 'parent') { 129 | contentSlot = slots.default 130 | } 131 | 132 | onMounted(() => { 133 | mounted.value = true 134 | 135 | nextTick(() => { 136 | if (contentSlot) 137 | tippy.setContent(() => contentElem.value) 138 | }) 139 | }) 140 | 141 | watch(tippy.state, () => { 142 | emit('state', unref(tippy.state)) 143 | }, { immediate: true, deep: true }) 144 | 145 | watch(() => props, () => { 146 | tippy.setProps(getOptions()) 147 | 148 | if (contentSlot) 149 | tippy.setContent(() => contentElem.value) 150 | }, { deep: true }) 151 | 152 | let exposed = reactive({ 153 | elem, 154 | contentElem, 155 | mounted, 156 | ...tippy 157 | }) 158 | 159 | expose(exposed) 160 | 161 | return () => { 162 | const contentTag = typeof props.contentTag === 'string' ? props.contentTag as string : props.contentTag 163 | const content = contentSlot 164 | ? h( 165 | contentTag, 166 | { 167 | ref: contentElem, 168 | style: { display: mounted.value ? 'inherit' : 'none' }, 169 | class: props.contentClass, 170 | }, 171 | contentSlot(exposed) 172 | ) 173 | : null 174 | 175 | if (props.to === 'parent') { 176 | const result = [] 177 | if (!elem.value) { 178 | const findParentHelperNode = h('span', { 179 | ref: findParentHelper, 180 | 'data-v-tippy': '', 181 | style: { display: 'none' }, 182 | }) 183 | result.push(findParentHelperNode) 184 | } 185 | if (content) { 186 | result.push(content) 187 | } 188 | 189 | return result 190 | } 191 | 192 | const slot = slots.default ? slots.default(exposed) : [] 193 | if (!props.tag) { 194 | const trigger = h(slot[0] as any, { 195 | ref: elem, 'data-v-tippy': '' 196 | }); 197 | 198 | return content ? [trigger, content] : trigger 199 | } 200 | 201 | const tag = typeof props.tag === 'string' ? props.tag as string : props.tag 202 | 203 | return h( 204 | tag, 205 | { ref: elem, 'data-v-tippy': '' }, 206 | content ? [slot, content] : slot 207 | ) 208 | } 209 | }, 210 | }) 211 | 212 | export default TippyComponent 213 | -------------------------------------------------------------------------------- /playground/pages/Index.vue: -------------------------------------------------------------------------------- 1 | 297 | -------------------------------------------------------------------------------- /docs/content/en/props.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: All Props 3 | description: '' 4 | category: Getting started 5 | position: 5 6 | --- 7 | 8 | `Props` are configurable properties you can pass optionally to: 9 | 10 | ```html 11 | 12 | 13 | ``` 14 | 15 | ```html 16 | 17 | 18 | 19 | 20 | ``` 21 | 22 | ```js 23 | // Composition api 24 | useTippy(target, props) 25 | ``` 26 | 27 | ## allowHTML 28 | 29 | Determines if content strings are parsed as HTML instead of text. 30 | 31 | ```js 32 | useTippy(target, { 33 | // default 34 | allowHTML: false, 35 | // parse `content` strings as HTML 36 | allowHTML: true, 37 | }) 38 | ``` 39 | 40 | ## animateFill 41 | 42 | Determines if the background fill color of the tippy should be animated. 43 | 44 | ```js 45 | // You must also import the backdrop.css 46 | // and animations/shift-away.css stylesheets 47 | // for styling to work. 48 | 49 | import 'tippy.js/dist/backdrop.css' 50 | import 'tippy.js/animations/shift-away.css' 51 | 52 | useTippy(target, { 53 | // default 54 | animateFill: false, 55 | // enable it 56 | animateFill: true, 57 | }) 58 | ``` 59 | 60 | ## animation 61 | 62 | The type of transition animation. See [Animations](/animations) for details. 63 | 64 | ```js 65 | useTippy(target, { 66 | // default 67 | animation: 'fade', 68 | }) 69 | ``` 70 | 71 | ## appendTo 72 | 73 | The element to append the tippy to. If `interactive: true`, the default behavior is `appendTo: "parent"`. 74 | 75 | Sometimes the tippy needs to be appended to a different DOM context due to accessibility, clipping, or z-index issues. 76 | 77 | ```js 78 | useTippy(target, { 79 | // default (takes reference as an argument) 80 | appendTo: () => document.body, 81 | // append to reference's parentNode 82 | appendTo: 'parent', 83 | // append to an Element 84 | appendTo: element, 85 | }) 86 | ``` 87 | 88 | ## aria 89 | 90 | The aria attribute configuration. Both properties are optional: 91 | 92 | - `content`: The `aria-*` attribute applied to the reference element to announce the tippy content. 93 | - `expanded`: Whether to add an `aria-expanded` attribute to the reference element. 94 | 95 | ```js 96 | useTippy(targets, { 97 | // default 98 | aria: { 99 | // `null` when interactive: true, otherwise "describedby" 100 | content: 'auto', 101 | // matches `interactive` boolean 102 | expanded: 'auto', 103 | }, 104 | 105 | // announce as a label rather than a description 106 | // the content will also be announced with `interactive: true` 107 | aria: { 108 | content: 'labelledby', 109 | }, 110 | 111 | // to abide by strict WCAG 2.1 rules with `interactive: true` to make it 112 | // hoverable if it's not actually interactive, but the content will still be 113 | // announced 114 | aria: { 115 | content: 'describedby', 116 | }, 117 | 118 | // disable completely, left up to you to control 119 | aria: { 120 | content: null, 121 | expanded: false, 122 | }, 123 | }) 124 | ``` 125 | 126 | ## arrow 127 | 128 | 129 | Theme or Styling the arrow is required 130 | 131 | 132 | Determines if the tippy has an arrow. 133 | 134 | ```js 135 | useTippy(target, { 136 | // default 137 | arrow: true, 138 | // disable arrow 139 | arrow: false, 140 | // custom arrow string 141 | arrow: '...', 142 | // custom arrow element 143 | arrow: svgElement, 144 | }) 145 | ``` 146 | 147 | ## content 148 | 149 | The content of the tippy. 150 | 151 | ```js 152 | useTippy(target, { 153 | // default 154 | content: '', 155 | // string 156 | content: 'Hello', 157 | // Element 158 | content: document.createElement('div'), 159 | // (reference) => string | Element 160 | content: reference => reference.getAttribute('title'), 161 | // using a ref value 162 | // import { ref } from 'vue' 163 | // const refContent = ref("Hi!") 164 | content: refContent, 165 | // using a computed property 166 | content: computed(() => `(${x.value},${y.value})`), 167 | // Render function 168 | content: h('h1', 'Hello'), 169 | // Vue Component without props 170 | content: VueComponent, 171 | // Vue Component with props 172 | content: h(VueComponent, { message: 'Hello' }), 173 | // Vue Component with reactive props 174 | content: computed(() => 175 | h(Counter, { onClick: () => counter.value++ }, 'Click Me!') 176 | ), 177 | // using define component 178 | content: defineComponent(() => { 179 | return () => h('p', 'Hellooooo') 180 | }), 181 | }) 182 | ``` 183 | 184 | ## delay 185 | 186 | Delay in ms once a trigger event is fired before tippy shows or hides. 187 | 188 | ```js 189 | useTippy(target, { 190 | // default 191 | delay: 0, 192 | // show and hide delay are 100ms 193 | delay: 100, 194 | // show delay is 100ms, hide delay is 200ms 195 | delay: [100, 200], 196 | // show delay is 100ms, hide delay is the default 197 | delay: [100, null], 198 | } 199 | ``` 200 | 201 | ## duration 202 | 203 | Duration in ms of the transition animation. 204 | 205 | ```js 206 | useTippy(target, { 207 | // default 208 | duration: [300, 250], 209 | // show and hide durations are 100ms 210 | duration: 100, 211 | // show duration is 100ms, hide duration is 200ms 212 | duration: [100, 200], 213 | // show duration is 100ms, hide duration is the default 214 | duration: [100, null], 215 | } 216 | ``` 217 | 218 | ## followCursor 219 | 220 | Determines if the tippy follows the user's mouse cursor. 221 | 222 | ```js 223 | useTippy(target, { 224 | // default 225 | followCursor: false, 226 | // follow on both x and y axes 227 | followCursor: true, 228 | // follow on x axis 229 | followCursor: 'horizontal', 230 | // follow on y axis 231 | followCursor: 'vertical', 232 | // follow until it shows (taking into account `delay`) 233 | followCursor: 'initial', 234 | }) 235 | ``` 236 | 237 | ## getReferenceClientRect 238 | 239 | Used as the positioning reference for the tippy. 240 | 241 | ```js 242 | useTippy(target, { 243 | // default (uses the reference passed as first argument) 244 | getReferenceClientRect: null, 245 | // function that returns a ClientRect object 246 | getReferenceClientRect: () => ({ 247 | width: 100, 248 | height: 100, 249 | left: 100, 250 | right: 200, 251 | top: 100, 252 | bottom: 200, 253 | }), 254 | }) 255 | ``` 256 | 257 | ## hideOnClick 258 | 259 | Determines if the tippy hides upon clicking the reference or outside of the tippy. The behavior can depend upon the `trigger` events used. 260 | 261 | ```js 262 | useTippy(target, { 263 | // default 264 | hideOnClick: true, 265 | // never hide upon clicking 266 | hideOnClick: false, 267 | // hide only upon clicking the reference, but not outside 268 | hideOnClick: 'toggle', 269 | }) 270 | ``` 271 | 272 | ## ignoreAttributes 273 | 274 | When using UI (component) libraries like Vue, this is generally not necessary and slows down initialization perf a bit. 275 | 276 | ```js 277 | useTippy(target, { 278 | // default 279 | ignoreAttributes: true, 280 | // consider `data-tippy-*` attributes on the reference element 281 | ignoreAttributes: false, 282 | }) 283 | ``` 284 | 285 | ## inertia 286 | 287 | Determines if a (customizable) CSS spring-like animation is applied to the transition animation. 288 | 289 | Changing the show duration to a higher value makes this look better. 290 | 291 | ```js 292 | useTippy(target, { 293 | // default 294 | inertia: false, 295 | // enable it 296 | inertia: true, 297 | }) 298 | ``` 299 | 300 | ```css 301 | .tippy-box[data-inertia][data-state='visible'] { 302 | transition-timing-function: cubic-bezier(...); 303 | } 304 | ``` 305 | 306 | ## inlinePositioning 307 | 308 | Provides enhanced support for `display: inline` elements. It will choose the most appropriate rect based on the placement. 309 | 310 | ```js 311 | useTippy(target, { 312 | // default 313 | inlinePositioning: false, 314 | // enable it 315 | inlinePositioning: true, 316 | }) 317 | ``` 318 | 319 | ## interactive 320 | 321 | Determines if the tippy has interactive content inside of it, so that it can be hovered over and clicked inside without hiding. 322 | 323 | ```js 324 | useTippy(target, { 325 | // default 326 | interactive: false, 327 | // enable it 328 | interactive: true, 329 | }) 330 | ``` 331 | 332 | ## interactiveBorder 333 | 334 | Determines the size of the invisible border around the tippy that will prevent it from hiding if the cursor left it. 335 | 336 | ```js 337 | useTippy(target, { 338 | // default 339 | interactiveBorder: 2, 340 | // 30px 341 | interactiveBorder: 30, 342 | }) 343 | ``` 344 | 345 | ## interactiveDebounce 346 | 347 | Determines the time in ms to debounce the interactive hide handler when the cursor leaves the tippy's interactive region. 348 | 349 | Offers a temporal (rather than spacial) alternative to `interactiveBorder`, although it can be used in conjunction with it. 350 | 351 | ```js 352 | useTippy(target, { 353 | // default 354 | interactiveDebounce: 0, 355 | // 75ms 356 | interactiveDebounce: 75, 357 | }) 358 | ``` 359 | 360 | ## maxWidth 361 | 362 | Specifies the maximum width of the tippy. Useful to prevent it from being too horizontally wide to read. 363 | 364 | 365 | This is applied to the `.tippy-box` (inner element), rather than the root positioned popper node. The core CSS applies `max-width: calc(100vw - 10px)` on the root popper node to prevent it from exceeding the viewport width on small screens. 366 | 367 | 368 | ```js 369 | useTippy(target, { 370 | // default 371 | maxWidth: 350, 372 | // no maxWidth 373 | maxWidth: 'none', 374 | }) 375 | ``` 376 | 377 | ## moveTransition 378 | 379 | Specifies the transition applied to the root positioned popper node. This describes the transition between "moves" (or position updates) of the popper element when it e.g. flips or changes target location. 380 | 381 | ```js 382 | useTippy(target, { 383 | // default 384 | moveTransition: '', 385 | // custom transition 386 | moveTransition: 'transform 0.2s ease-out', 387 | }) 388 | ``` 389 | 390 | ## offset 391 | 392 | Displaces the tippy from its reference element in pixels (skidding and distance). 393 | 394 | See [Popper's docs](https://popper.js.org/docs/v2/modifiers/offset/) for details. 395 | 396 | ```js 397 | useTippy(target, { 398 | // default [skidding, distance] 399 | offset: [0, 10], 400 | }) 401 | ``` 402 | 403 | ## onAfterUpdate 404 | 405 | Invoked after the tippy props has been updated. 406 | 407 | ```js 408 | useTippy(target, { 409 | onAfterUpdate(instance, partialProps) { 410 | // ... 411 | }, 412 | }) 413 | ``` 414 | 415 | ## onBeforeUpdate 416 | 417 | Invoked before the tippy props has been updated. 418 | 419 | ```js 420 | useTippy(target, { 421 | onAfterUpdate(instance, partialProps) { 422 | // ... 423 | }, 424 | }) 425 | ``` 426 | 427 | ## onClickOutside 428 | 429 | Invoked when the user clicks anywhere outside of the tippy or reference element. 430 | 431 | ```js 432 | useTippy(target, { 433 | onClickOutside(instance, event) { 434 | // ... 435 | }, 436 | }) 437 | ``` 438 | 439 | ## onCreate 440 | 441 | Invoked once the tippy has been created. 442 | 443 | ```js 444 | useTippy(target, { 445 | onCreate(instance) { 446 | // ... 447 | }, 448 | }) 449 | ``` 450 | 451 | ## onDestroy 452 | 453 | Invoked once the tippy has been destroyed. 454 | 455 | ```js 456 | useTippy(target, { 457 | onDestroy(instance) { 458 | // ... 459 | }, 460 | }) 461 | ``` 462 | ## onHidden 463 | 464 | Invoked once the tippy has been fully hidden and unmounted from the DOM. 465 | 466 | ```js 467 | useTippy(target, { 468 | onHidden(instance) { 469 | // ... 470 | }, 471 | }) 472 | ``` 473 | ## onHide 474 | 475 | Invoked once the tippy begins to hide. 476 | 477 | ```js 478 | useTippy(target, { 479 | onHide(instance) { 480 | // ... 481 | }, 482 | }) 483 | ``` 484 | 485 | You can optionally `return false` from this lifecycle to cancel a hide based on a condition. 486 | 487 | ## onMount 488 | 489 | Invoked once the tippy has been mounted to the DOM (and the popperInstance created). 490 | 491 | ```js 492 | useTippy(target, { 493 | onMount(instance) { 494 | // ... 495 | }, 496 | }) 497 | ``` 498 | 499 | ## onShow 500 | 501 | Invoked once the tippy begins to show. 502 | 503 | ```js 504 | useTippy(target, { 505 | onShow(instance) { 506 | // ... 507 | }, 508 | }) 509 | ``` 510 | 511 | You can optionally `return false` from this lifecycle to cancel a show based on a condition. 512 | 513 | ## onShown 514 | 515 | Invoked once the tippy has been fully transitioned in. 516 | 517 | Since this is achieved via CSS `transitionend`, it relies on your own event listeners if using a custom `render` function. You'll need to call the lifecycle manually in this case. 518 | 519 | ```js 520 | useTippy(target, { 521 | onShown(instance) { 522 | // ... 523 | }, 524 | }) 525 | ``` 526 | 527 | ## onTrigger 528 | 529 | Invoked once the tippy has been triggered by a DOM event (e.g. `mouseenter`). 530 | 531 | ```js 532 | useTippy(target, { 533 | onShown(instance, event) { 534 | // ... 535 | }, 536 | }) 537 | ``` 538 | 539 | ## onUntrigger 540 | 541 | Invoked once the tippy has been untriggered by a DOM event (e.g. `mouseleave`). 542 | 543 | ```js 544 | useTippy(target, { 545 | onUntrigger(instance, event) { 546 | // ... 547 | }, 548 | }) 549 | ``` 550 | 551 | ## placement 552 | 553 | The preferred placement of the tippy. Note that Popper's `flip` modifier can change this to the opposite placement if it has more space. 554 | 555 | ```js 556 | useTippy(target, { 557 | // default 558 | placement: 'top', 559 | 560 | // full list: 561 | placement: 'top-start', 562 | placement: 'top-end', 563 | 564 | placement: 'right', 565 | placement: 'right-start', 566 | placement: 'right-end', 567 | 568 | placement: 'bottom', 569 | placement: 'bottom-start', 570 | placement: 'bottom-end', 571 | 572 | placement: 'left', 573 | placement: 'left-start', 574 | placement: 'left-end', 575 | 576 | // choose the side with most space 577 | placement: 'auto', 578 | placement: 'auto-start', 579 | placement: 'auto-end', 580 | }) 581 | ``` 582 | 583 | ## popperOptions 584 | 585 | Specifies custom Popper options. This gives you full control over the tippy's positioning. See [Popper's docs](https://popper.js.org/docs/v2/) for details. 586 | 587 | ```js 588 | useTippy(target, { 589 | // default 590 | popperOptions: {}, 591 | // detailed example 592 | popperOptions: { 593 | strategy: 'fixed', 594 | modifiers: [ 595 | { 596 | name: 'flip', 597 | options: { 598 | fallbackPlacements: ['bottom', 'right'], 599 | }, 600 | }, 601 | { 602 | name: 'preventOverflow', 603 | options: { 604 | altAxis: true, 605 | tether: false, 606 | }, 607 | }, 608 | ], 609 | }, 610 | }) 611 | ``` 612 | 613 | ## role 614 | 615 | Specifies the `role` attribute on the tippy element. 616 | 617 | ```js 618 | useTippy(target, { 619 | // default 620 | role: 'tooltip', 621 | }) 622 | ``` 623 | 624 | ## showOnCreate 625 | 626 | Determines if the tippy is shown once it gets created, respecting `delay`. 627 | 628 | ```js 629 | useTippy(target, { 630 | // default 631 | showOnCreate: false, 632 | // enable it 633 | showOnCreate: true, 634 | }) 635 | ``` 636 | 637 | ## sticky 638 | 639 | Determines if the tippy sticks to the reference element while it is mounted. This is usually not needed, but is useful if the reference element's position is animating, or to automatically update the tippy position without needing to manually do it in certain cases where the DOM layout changes. 640 | 641 | This has a performance cost since checks are run on every animation frame. Use this only when necessary! 642 | 643 | ```js 644 | useTippy(target, { 645 | // default 646 | sticky: false, 647 | // enable it 648 | sticky: true, 649 | // only check the "reference" rect for changes 650 | sticky: 'reference', 651 | // only check the "popper" rect for changes 652 | sticky: 'popper', 653 | }) 654 | ``` 655 | 656 | ## themes 657 | 658 | Determines the theme of the tippy element. The core CSS defaults to a dark `#333` theme. This can be overridden by a custom theme. See [Themes](/themes) for details. 659 | 660 | ```js 661 | useTippy(target, { 662 | // default 663 | theme: '', 664 | // custom theme 665 | theme: 'tomato', 666 | }) 667 | ``` 668 | 669 | ## touch 670 | 671 | Determines the behavior on touch devices. 672 | 673 | ```js 674 | useTippy(target, { 675 | // default 676 | touch: true, 677 | // disable tippy from showing on touch devices 678 | touch: false, 679 | // require pressing & holding the screen to show it 680 | touch: 'hold', 681 | // same as above, but long-press behavior 682 | touch: ['hold', 500], 683 | }) 684 | ``` 685 | 686 | ## trigger 687 | 688 | Determines the events that cause the tippy to show. Multiple event names are separated by spaces. 689 | 690 | ```js 691 | useTippy(target, { 692 | // default 693 | trigger: 'mouseenter focus', 694 | // others: 695 | trigger: 'click', 696 | trigger: 'focusin', 697 | trigger: 'mouseenter click', 698 | // only programmatically trigger it 699 | trigger: 'manual', 700 | }) 701 | ``` 702 | 703 | ## triggerTarget 704 | 705 | The element(s) that the trigger event listeners are added to. Allows you to separate the tippy's positioning from its trigger source. 706 | 707 | ```js 708 | useTippy(target, { 709 | // default (reference is used) 710 | triggerTarget: null, 711 | // Element 712 | triggerTarget: someElement, 713 | // Element[] 714 | triggerTarget: [someElement1, someElement2], 715 | // import { ref } from 'vue' 716 | // const refTriggerTarget = ref(); // ... 717 | triggerTarget: refTriggerTarget, 718 | }) 719 | ``` 720 | ## zIndex 721 | 722 | Specifies the `z-index` CSS on the root popper node. 723 | 724 | ```js 725 | useTippy(target, { 726 | // default 727 | zIndex: 9999, 728 | }) 729 | ``` 730 | --------------------------------------------------------------------------------