├── .editorconfig ├── .gitattributes ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── dist ├── vue-custom-tooltip.esm.js ├── vue-custom-tooltip.min.js └── vue-custom-tooltip.ssr.js ├── img └── vue-custom-tooltip-examples.png ├── package.json ├── rollup.config.js ├── src ├── VueCustomTooltip.vue └── wrapper.js ├── vue.config.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace=true 11 | indent_style=space 12 | indent_size=2 13 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto eol=lf 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[Bug Report] " 5 | labels: bug 6 | assignees: adamdehaven 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Install via '...' 16 | 2. Import plugin '....' 17 | 3. Use component '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. Windows 10] 28 | - Browser [e.g. chrome, safari] 29 | - Package Version [e.g. v1.0.0] 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[Feature Request] " 5 | labels: enhancement 6 | assignees: adamdehaven 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I always need to do [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen or how the plugin/package can be improved. 15 | 16 | **Describe anything you've already tried** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | 4 | # env files 5 | .env 6 | 7 | # Log files 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | pnpm-debug.log* 12 | 13 | # Editor directories and files 14 | .idea 15 | .vscode 16 | *.suo 17 | *.ntvs* 18 | *.njsproj 19 | *.sln 20 | *.sw? 21 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Dist 2 | dist/ 3 | 4 | # node modules 5 | node_modules 6 | package.json 7 | package-lock.json 8 | 9 | # MacOS desktop services store 10 | .DS_Store 11 | 12 | # Log files 13 | *.log 14 | 15 | # Map files 16 | *.map 17 | 18 | # Editors 19 | .vscode 20 | 21 | # VuePress temp directory 22 | .temp 23 | 24 | # Source Control 25 | .github 26 | 27 | # Secure 28 | .env 29 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "bracketSpacing": true, 4 | "endOfLine": "lf", 5 | "htmlWhitespaceSensitivity": "css", 6 | "jsxBracketSameLine": true, 7 | "jsxSingleQuote": true, 8 | "printWidth": 120, 9 | "quoteProps": "consistent", 10 | "semi": false, 11 | "singleQuote": true, 12 | "tabWidth": 2, 13 | "trailingComma": "all", 14 | "useTabs": false, 15 | "vueIndentScriptAndStyle": true 16 | } 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Adam DeHaven 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vue-Custom-Tooltip 2 | 3 | A customizable, reusable, and reactive tooltip component for Vue 2 & 3 (and VuePress) projects. 4 | 5 | ![Tooltip Vue component examples](img/vue-custom-tooltip-examples.png) 6 | 7 | ## Installation 8 | 9 | Installation instructions depend on the version of Vue.js you are using (Vue `2.x` or `3.x`). 10 | 11 | ### Vue 2 12 | 13 | ```sh 14 | # With npm 15 | npm install @adamdehaven/vue-custom-tooltip 16 | 17 | # or Yarn 18 | yarn add @adamdehaven/vue-custom-tooltip 19 | ``` 20 | 21 | ### Vue 3 (including TypeScript compatibility) 22 | 23 | When installing, make sure to pass the `@next` tag 24 | 25 | ```sh 26 | # With npm 27 | npm install @adamdehaven/vue-custom-tooltip@next 28 | 29 | # or Yarn 30 | yarn add @adamdehaven/vue-custom-tooltip@next 31 | ``` 32 | 33 | ## Initialize Plugin 34 | 35 | ### Vue.js (globally available) 36 | 37 | It is recommended to initialize the plugin in your Vue project's entry file. For projects created with [`@vue/cli`](https://cli.vuejs.org/), this is likely your `main.{js|ts}` file where you are already importing `Vue`. 38 | 39 | ```ts 40 | // main.{js|ts} (or your Vue entry file) 41 | 42 | // =========================================================== 43 | // VUE 2.x 44 | // =========================================================== 45 | // Import Vue... you're probably already doing this 46 | import Vue from 'vue' 47 | 48 | // Import the tooltip component 49 | import VueCustomTooltip from '@adamdehaven/vue-custom-tooltip' 50 | 51 | // Initialize the plugin using ONE of the options below: 52 | // -------------------------------------------------- 53 | 54 | // 1. Initialize with default options 55 | Vue.use(VueCustomTooltip) 56 | 57 | // ===== OR ===== 58 | 59 | // 2. Initialize with custom options (defaults shown) 60 | Vue.use(VueCustomTooltip, { 61 | name: 'VueCustomTooltip', 62 | color: '#fff', 63 | background: '#000', 64 | borderRadius: 100, 65 | fontWeight: 400, 66 | }) 67 | 68 | // =========================================================== 69 | // VUE 3.x 70 | // =========================================================== 71 | import { createApp } from 'vue' 72 | import App from './App.vue' 73 | 74 | // Import the tooltip component and option types 75 | import VueCustomTooltip, { TooltipOptions } from '@adamdehaven/vue-custom-tooltip' 76 | 77 | const app = createApp(App) 78 | 79 | // Initialize the plugin using ONE of the options below: 80 | // -------------------------------------------------- 81 | 82 | // 1. Initialize with default options 83 | app.use(VueCustomTooltip) 84 | 85 | // ===== OR === 86 | 87 | // 2. Initialize with custom options (defaults shown) 88 | const opt: TooltipOptions = { 89 | name: 'VueCustomTooltip', 90 | color: '#fff', 91 | background: '#000', 92 | borderRadius: 100, 93 | fontWeight: 400, 94 | } 95 | 96 | app.use(VueCustomTooltip, opt) 97 | app.mount('#app') 98 | ``` 99 | 100 | ### In-Component (locally available) 101 | 102 | Alternatively, you may initialize the component directly within a single file in your project. 103 | 104 | **Notes on in-component initialization**: 105 | 106 | - Initializing within a component does not allow for customizing the [Plugin Options](#options); however, you may still utilize all [`props`](#props) on the `` element, or customize styles with [CSS Variables](#css-variables). 107 | 108 | ```html 109 | 110 | 111 | 123 | ``` 124 | 125 | ### Via CDN 126 | 127 | Load the tooltip component after first importing Vue. 128 | 129 | **Notes on CDN Import with Vue 2**: 130 | 131 | - Initializing via CDN with Vue 2 requires using the kebab-case component name. 132 | - Initializing via CDN with Vue 2 does not allow for customizing the [Plugin Options](#options); however, you may still utilize all [`props`](#props) on the `` element. 133 | 134 | ```html 135 | 136 |
137 | 138 |

This is a tooltip.

139 |
140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 168 | 169 | ``` 170 | 171 | ### Manual / Local Import 172 | 173 | Download the correct version of `dist/vue-custom-tooltip.min.js` based on your version of Vue, and include it in your file after importing Vue. 174 | 175 | **Notes on Manual / Local Import with Vue 2**: 176 | 177 | - Initializing manually with Vue 2 requires using the kebab-case component name. 178 | - Initializing manually with Vue 2 does not allow for customizing the [Plugin Options](#options); however, you may still utilize all [`props`](#props) on the `` element. 179 | 180 | ```html 181 | 182 |
183 | 184 |

This is a tooltip.

185 |
186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 215 | 216 | ``` 217 | 218 | ### VuePress (Global) 219 | 220 | > **VuePress Standalone Plugin** 221 | > 222 | > I have released a standalone VuePress plugin that wraps this component into an actual VuePress Plugin installable through the `.vuepress/config.js` or `.vuepress/theme/index.js` file. If you'd rather use the standalone plugin in your VuePress project, [head over to the `vuepress-plugin-custom-tooltip` repository](https://github.com/adamdehaven/vuepress-plugin-custom-tooltip). 223 | 224 | For [VuePress](https://vuepress.vuejs.org/) projects, the `theme/enhanceApp.js` is a good location to initialize plugins. 225 | 226 | ```js 227 | // theme/enhanceApp.js 228 | 229 | // Import Vue... you're probably already doing this 230 | import Vue from 'vue' 231 | 232 | // Import the tooltip component 233 | import VueCustomTooltip from '@adamdehaven/vue-custom-tooltip' 234 | 235 | export default ({ 236 | Vue, // the version of Vue being used in the VuePress app 237 | options, // the options for the root Vue instance 238 | router, // the router instance for the app 239 | siteData, // site metadata 240 | isServer, // is this enhancement applied in server-rendering or client 241 | }) => { 242 | // ...apply enhancements to the app 243 | 244 | // Install the plugin using ONE of the options below: 245 | // -------------------------------------------------- 246 | 247 | // 1. Install with default options 248 | Vue.use(VueCustomTooltip) 249 | 250 | // ===== OR ===== 251 | 252 | // 2. Install with custom options (defaults shown) 253 | Vue.use(VueCustomTooltip, { 254 | name: 'VueCustomTooltip', 255 | color: '#fff', 256 | background: '#000', 257 | borderRadius: 100, 258 | fontWeight: 400, 259 | }) 260 | } 261 | ``` 262 | 263 | ## Usage 264 | 265 | ```html 266 | 267 | What is a tooltip? 268 | 269 | 270 | What is 271 | a tooltip? 272 | 273 | 274 | 275 | 276 | 277 | Twitter 278 | 279 | 280 | ``` 281 | 282 | ## Options 283 | 284 | Pass any of the options listed below to `Vue.use(VueCustomTooltip, {...})` to customize the plugin for your project _(not available with [in-component installation](#in-component-install) - see the [CSS Variables section](#css-variables) below)_. 285 | 286 | > **A note on options tied to CSS properties** 287 | > 288 | > The `color`, `background`, `borderRadius`, and `fontWeight` attributes listed below are set on the psuedo element using [CSS Variables (Custom Properties)](https://caniuse.com/#feat=css-variables), meaning they will fallback to their default values in unsupported browsers (e.g. Internet Explorer). 289 | 290 | ### Type Declarations for tooltip options 291 | 292 | ```ts 293 | export interface TooltipOptions { 294 | name?: string 295 | color?: string 296 | background?: string 297 | borderRadius?: number 298 | fontWeight?: number 299 | } 300 | ``` 301 | 302 | You may import the TypeScript interface along with the plugin into your entry file as shown here: 303 | 304 | ```ts 305 | // main.ts 306 | 307 | import VueCustomTooltip, { TooltipOptions } from '@adamdehaven/vue-custom-tooltip' 308 | 309 | // Then, to import the plugin and use the interface 310 | const options: TooltipOptions = { 311 | background: '#0007ac1', 312 | } 313 | 314 | Vue.use(VueCustomTooltip, options) 315 | ``` 316 | 317 | ### `name` 318 | 319 | - Type: `String` 320 | - Default: `VueCustomTooltip` 321 | 322 | Customize the name of the component you will use in your project. **PascalCase names are preferred**, as this allows for PascalCase or kebab-case usage within your project. 323 | 324 | ```js 325 | Vue.use(VueCustomTooltip, { 326 | name: 'SuperCoolTooltip', // PascalCase preferred 327 | }) 328 | ``` 329 | 330 | If you registered the name using PascalCase, you can reference the tooltip component via PascalCase _or_ kebab-case: 331 | 332 | ```html 333 | 334 | 335 | 336 | Nice tooltip! 337 | 338 | Nice tooltip! 339 | 340 | 341 | 342 | 343 | Nice tooltip! 344 | 345 | Nice tooltip! 346 | ``` 347 | 348 | ### `color` 349 | 350 | - Type: `HEX Color` 351 | - Default: `#fff` 352 | 353 | Customize the color of the text displayed in the tooltip. 354 | 355 | ```js 356 | Vue.use(VueCustomTooltip, { 357 | color: '#c1403d', // 3 or 6 digit HEX color, including a leading hash (#) 358 | }) 359 | ``` 360 | 361 | ### `background` 362 | 363 | - Type: `HEX Color` 364 | - Default: `#000` 365 | 366 | Customize the background color (and the `underlined` text color) of the tooltip. 367 | 368 | ```js 369 | Vue.use(VueCustomTooltip, { 370 | background: '#1b2735', // 3 or 6 digit HEX color, including a leading hash (#) 371 | }) 372 | ``` 373 | 374 | ### `borderRadius` 375 | 376 | - Type: `Number` 377 | - Default: `100` 378 | 379 | Customize the border-radius of the tooltip. Must be an integer. 380 | 381 | ```js 382 | Vue.use(VueCustomTooltip, { 383 | borderRadius: 24, 384 | }) 385 | ``` 386 | 387 | ### `fontWeight` 388 | 389 | - Type: `Number` 390 | - Default: `400` 391 | 392 | Customize the font-weight of the tooltip text. Must be an integer that is a multiple of 100, between 100 - 900. 393 | 394 | ```js 395 | Vue.use(VueCustomTooltip, { 396 | fontWeight: 700, 397 | }) 398 | ``` 399 | 400 | ## CSS Variables 401 | 402 | In addition to customizing styles via the [Plugin Options](#options), you can alternatively choose to customize styles via CSS variables as shown below: 403 | 404 | ```css 405 | /* Default values are shown */ 406 | :root { 407 | --vue-custom-tooltip-color: #fff; 408 | --vue-custom-tooltip-background: #000; 409 | --vue-custom-tooltip-border-radius: 100px; 410 | --vue-custom-tooltip-font-weight: 400; 411 | } 412 | ``` 413 | 414 | ## Props 415 | 416 | In addition to the [Plugin Options](#options) above, you may also pass props to the component itself to customize both the look and behavior of the tooltip element. 417 | 418 | Props that accept a Boolean value may be passed simply by adding the attribute to the component tag, if a `true` value is desired. See the `sticky` example here: 419 | 420 | ```html 421 | text/element 422 | ``` 423 | 424 | All other props may be passed as normal attributes (if the corresponding value is a String, like the `label` prop, shown above) or with `v-bind` directives, as shown here: 425 | 426 | ```html 427 | text/element 428 | ``` 429 | 430 | All available props for the tooltip component are listed below: 431 | 432 | ### `label` 433 | 434 | - Type: `String` 435 | - Default: `null` 436 | 437 | The text that will display inside the tooltip. If the value for `label` is null, the tooltip will not be displayed. 438 | 439 | You may **not** pass HTML to the label prop. 440 | 441 | ### `active` 442 | 443 | - Type: `Boolean` 444 | - Default: `true` 445 | 446 | Determines whether the tooltip should display when hovered, or if the [`sticky`](#sticky) prop is present, if the tooltip should be visible. 447 | 448 | ### `position` 449 | 450 | - Type: `String` 451 | - Value: `is-top / is-bottom / is-left / is-right` 452 | - Default: `is-top` 453 | 454 | The position of the tooltip in relation to the text/element it is surrounding. 455 | 456 | ### `abbreviation` 457 | 458 | - Type: `Boolean` 459 | - Default: `false` 460 | 461 | Swaps out the component's standard `` element with a semantically-correct `` element, and sets the [`underlined`](#underlined) prop to `true`. This is useful when adding a tooltip to text within a page's content where you want to provide additional context to a word or phrase, or provide a definition of a word or acronym. 462 | 463 | ```html 464 | VuePress pages are served as an SPA. 465 | ``` 466 | 467 | ### `sticky` 468 | 469 | - Type: `Boolean` 470 | - Default: `false` 471 | 472 | Determines if the tooltip should always be displayed (including on component load/mounting), regardless of the element being hovered. 473 | 474 | ### `underlined` 475 | 476 | - Type: `Boolean` 477 | - Default: `false` 478 | 479 | Add a dotted border under the contained text (the same color as the [background](#background) HEX value). This value is automatically set to `true` if the [`abbreviation`](#abbreviation) prop is set to `true`. 480 | 481 | ### `multiline` 482 | 483 | - Type: `Boolean` 484 | - Default: `false` 485 | 486 | Allows the tooltip text to wrap to multiple lines as needed. Should be used in conjunction with the `size` property. 487 | 488 | ### `size` 489 | 490 | - Type: `String` 491 | - Value: `is-small / is-medium / is-large` 492 | - Default: `is-medium` 493 | 494 | The width of the tooltip, if the [`multiline`](#multiline) prop is set to `true`. 495 | 496 | ## Adding Custom Classes & Styles 497 | 498 | Just like any other Vue component, you can add classes or styles directly to the component tag that will be applied to the rendered `` tag (or `` tag, if `abbreviation` is set to `true`). 499 | 500 | ```html 501 | 502 | text 505 | ``` 506 | 507 | This is extremely helpful if you want to extend functionality or tooltip styles within your project, which allows you to tweak things like the display behavior of the tooltip element. 508 | 509 | The tooltip component is rendered as a `display: inline-block` element by default; however, you can override this by binding styles directly to the component, as shown above. 510 | 511 | ## License 512 | 513 | [MIT](https://github.com/adamdehaven/vue-custom-tooltip/blob/master/LICENSE) 514 | -------------------------------------------------------------------------------- /dist/vue-custom-tooltip.esm.js: -------------------------------------------------------------------------------- 1 | // 2 | // 3 | // 4 | // 5 | // 6 | // 7 | // 8 | // 9 | // 10 | // 11 | // 12 | // 13 | // 14 | // 15 | // 16 | // 17 | // 18 | // 19 | // 20 | // 21 | // 22 | // 23 | 24 | var script = { 25 | name: 'VueCustomTooltip', 26 | props: { 27 | label: String, 28 | active: { 29 | type: Boolean, 30 | default: true, 31 | }, 32 | sticky: Boolean, // Always showing 33 | multiline: Boolean, // Multiple lines 34 | underlined: Boolean, 35 | abbreviation: Boolean, 36 | position: { 37 | type: String, 38 | default: 'is-top', 39 | validator: function validator(value) { 40 | return ['is-top', 'is-bottom', 'is-left', 'is-right'].indexOf(value) > -1 41 | }, 42 | }, 43 | size: { 44 | type: String, 45 | default: 'is-medium', 46 | validator: function validator(value) { 47 | return ['is-small', 'is-medium', 'is-large'].indexOf(value) > -1 48 | }, 49 | }, 50 | }, 51 | data: function data() { 52 | return { 53 | labelText: this.label || null, 54 | isActive: this.active || true, 55 | isSticky: this.sticky || false, 56 | isMultiline: this.multiline || false, 57 | isUnderlined: this.underlined || false, 58 | isAbbreviation: this.abbreviation || false, 59 | hasPosition: this.position || 'is-top', 60 | hasSize: this.size || 'is-medium', 61 | } 62 | }, 63 | computed: { 64 | dynamicStyles: function dynamicStyles() { 65 | return { 66 | '--vue-custom-tooltip-color': 67 | this.$vueCustomTooltip && this.$vueCustomTooltip.hasOwnProperty('color') 68 | ? this.$vueCustomTooltip.color 69 | : null, 70 | '--vue-custom-tooltip-background': 71 | this.$vueCustomTooltip && this.$vueCustomTooltip.hasOwnProperty('background') 72 | ? this.$vueCustomTooltip.background 73 | : null, 74 | '--vue-custom-tooltip-border-radius': 75 | this.$vueCustomTooltip && this.$vueCustomTooltip.hasOwnProperty('borderRadius') 76 | ? this.$vueCustomTooltip.borderRadius 77 | : null, 78 | '--vue-custom-tooltip-font-weight': 79 | this.$vueCustomTooltip && this.$vueCustomTooltip.hasOwnProperty('fontWeight') 80 | ? this.$vueCustomTooltip.fontWeight 81 | : null, 82 | } 83 | }, 84 | }, 85 | watch: { 86 | label: { 87 | handler: function handler(value) { 88 | this.labelText = value; 89 | }, 90 | immediate: true, 91 | }, 92 | active: { 93 | handler: function handler(value) { 94 | this.isActive = value; 95 | }, 96 | immediate: true, 97 | }, 98 | sticky: { 99 | handler: function handler(value) { 100 | this.isSticky = value; 101 | }, 102 | immediate: true, 103 | }, 104 | multiline: { 105 | handler: function handler(value) { 106 | this.isMultiline = value; 107 | }, 108 | immediate: true, 109 | }, 110 | underlined: { 111 | handler: function handler(value) { 112 | this.isUnderlined = value; 113 | }, 114 | immediate: true, 115 | }, 116 | abbreviation: { 117 | handler: function handler(value) { 118 | this.isAbbreviation = value; 119 | }, 120 | immediate: true, 121 | }, 122 | position: { 123 | handler: function handler(value) { 124 | this.hasPosition = value; 125 | }, 126 | immediate: true, 127 | }, 128 | size: { 129 | handler: function handler(value) { 130 | this.hasSize = value; 131 | }, 132 | immediate: true, 133 | }, 134 | }, 135 | }; 136 | 137 | function normalizeComponent(template, style, script, scopeId, isFunctionalTemplate, moduleIdentifier /* server only */, shadowMode, createInjector, createInjectorSSR, createInjectorShadow) { 138 | if (typeof shadowMode !== 'boolean') { 139 | createInjectorSSR = createInjector; 140 | createInjector = shadowMode; 141 | shadowMode = false; 142 | } 143 | // Vue.extend constructor export interop. 144 | var options = typeof script === 'function' ? script.options : script; 145 | // render functions 146 | if (template && template.render) { 147 | options.render = template.render; 148 | options.staticRenderFns = template.staticRenderFns; 149 | options._compiled = true; 150 | // functional template 151 | if (isFunctionalTemplate) { 152 | options.functional = true; 153 | } 154 | } 155 | // scopedId 156 | if (scopeId) { 157 | options._scopeId = scopeId; 158 | } 159 | var hook; 160 | if (moduleIdentifier) { 161 | // server build 162 | hook = function (context) { 163 | // 2.3 injection 164 | context = 165 | context || // cached call 166 | (this.$vnode && this.$vnode.ssrContext) || // stateful 167 | (this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext); // functional 168 | // 2.2 with runInNewContext: true 169 | if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') { 170 | context = __VUE_SSR_CONTEXT__; 171 | } 172 | // inject component styles 173 | if (style) { 174 | style.call(this, createInjectorSSR(context)); 175 | } 176 | // register component module identifier for async chunk inference 177 | if (context && context._registeredComponents) { 178 | context._registeredComponents.add(moduleIdentifier); 179 | } 180 | }; 181 | // used by ssr in case component is cached and beforeCreate 182 | // never gets called 183 | options._ssrRegister = hook; 184 | } 185 | else if (style) { 186 | hook = shadowMode 187 | ? function (context) { 188 | style.call(this, createInjectorShadow(context, this.$root.$options.shadowRoot)); 189 | } 190 | : function (context) { 191 | style.call(this, createInjector(context)); 192 | }; 193 | } 194 | if (hook) { 195 | if (options.functional) { 196 | // register for functional component in vue file 197 | var originalRender = options.render; 198 | options.render = function renderWithStyleInjection(h, context) { 199 | hook.call(context); 200 | return originalRender(h, context); 201 | }; 202 | } 203 | else { 204 | // inject component registration as beforeCreate hook 205 | var existing = options.beforeCreate; 206 | options.beforeCreate = existing ? [].concat(existing, hook) : [hook]; 207 | } 208 | } 209 | return script; 210 | } 211 | 212 | var isOldIE = typeof navigator !== 'undefined' && 213 | /msie [6-9]\\b/.test(navigator.userAgent.toLowerCase()); 214 | function createInjector(context) { 215 | return function (id, style) { return addStyle(id, style); }; 216 | } 217 | var HEAD; 218 | var styles = {}; 219 | function addStyle(id, css) { 220 | var group = isOldIE ? css.media || 'default' : id; 221 | var style = styles[group] || (styles[group] = { ids: new Set(), styles: [] }); 222 | if (!style.ids.has(id)) { 223 | style.ids.add(id); 224 | var code = css.source; 225 | if (css.map) { 226 | // https://developer.chrome.com/devtools/docs/javascript-debugging 227 | // this makes source maps inside style tags work properly in Chrome 228 | code += '\n/*# sourceURL=' + css.map.sources[0] + ' */'; 229 | // http://stackoverflow.com/a/26603875 230 | code += 231 | '\n/*# sourceMappingURL=data:application/json;base64,' + 232 | btoa(unescape(encodeURIComponent(JSON.stringify(css.map)))) + 233 | ' */'; 234 | } 235 | if (!style.element) { 236 | style.element = document.createElement('style'); 237 | style.element.type = 'text/css'; 238 | if (css.media) 239 | { style.element.setAttribute('media', css.media); } 240 | if (HEAD === undefined) { 241 | HEAD = document.head || document.getElementsByTagName('head')[0]; 242 | } 243 | HEAD.appendChild(style.element); 244 | } 245 | if ('styleSheet' in style.element) { 246 | style.styles.push(code); 247 | style.element.styleSheet.cssText = style.styles 248 | .filter(Boolean) 249 | .join('\n'); 250 | } 251 | else { 252 | var index = style.ids.size - 1; 253 | var textNode = document.createTextNode(code); 254 | var nodes = style.element.childNodes; 255 | if (nodes[index]) 256 | { style.element.removeChild(nodes[index]); } 257 | if (nodes.length) 258 | { style.element.insertBefore(textNode, nodes[index]); } 259 | else 260 | { style.element.appendChild(textNode); } 261 | } 262 | } 263 | } 264 | 265 | /* script */ 266 | var __vue_script__ = script; 267 | 268 | /* template */ 269 | var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c(_vm.isAbbreviation ? 'abbr' : 'span',{tag:"component",class:[ 270 | _vm.hasPosition, 271 | _vm.hasSize, 272 | { 273 | 'vue-custom-tooltip': _vm.isActive && _vm.labelText, 274 | 'is-sticky': _vm.isSticky, 275 | 'has-multiline': _vm.isMultiline, 276 | 'is-underlined': _vm.isUnderlined || _vm.isAbbreviation, 277 | } ],style:([_vm.dynamicStyles, { cursor: _vm.isAbbreviation ? 'help' : 'pointer' }]),attrs:{"data-label":_vm.labelText,"aria-label":_vm.labelText,"role":"tooltip"}},[_vm._t("default")],2)}; 278 | var __vue_staticRenderFns__ = []; 279 | 280 | /* style */ 281 | var __vue_inject_styles__ = function (inject) { 282 | if (!inject) { return } 283 | inject("data-v-60bf38c6_0", { source: ".vue-custom-tooltip{--vue-custom-tooltip-color:#fff;--vue-custom-tooltip-background:#000;--vue-custom-tooltip-border-radius:100px;--vue-custom-tooltip-font-weight:400}", map: undefined, media: undefined }) 284 | ,inject("data-v-60bf38c6_1", { source: ".vue-custom-tooltip{position:relative;display:inline-block;text-decoration-line:none!important}.vue-custom-tooltip.is-top:after,.vue-custom-tooltip.is-top:before{top:auto;right:auto;bottom:calc(100% + 5px + 2px);left:50%;transform:translateX(-50%)}.vue-custom-tooltip.is-top:before{border-top:5px solid #000;border-top:5px solid var(--vue-custom-tooltip-background);border-right:5px solid transparent;border-left:5px solid transparent;bottom:calc(100% + 2px)}.vue-custom-tooltip.is-top.has-multiline.is-small:after{width:140px}.vue-custom-tooltip.is-top.has-multiline.is-medium:after{width:250px;padding:.6rem 1.25rem .65rem}.vue-custom-tooltip.is-top.has-multiline.is-large:after{width:480px;padding:0.6rem 1rem 0.65rem}.vue-custom-tooltip.is-right:after,.vue-custom-tooltip.is-right:before{top:50%;right:auto;bottom:auto;left:calc(100% + 5px + 2px);transform:translateY(-50%)}.vue-custom-tooltip.is-right:before{border-top:5px solid transparent;border-right:5px solid #000;border-right:5px solid var(--vue-custom-tooltip-background);border-bottom:5px solid transparent;left:calc(100% + 2px)}.vue-custom-tooltip.is-right.has-multiline.is-small:after{width:140px}.vue-custom-tooltip.is-right.has-multiline.is-medium:after{width:250px;padding:.6rem 1.25rem .65rem}.vue-custom-tooltip.is-right.has-multiline.is-large:after{width:480px;padding:0.6rem 1rem 0.65rem}.vue-custom-tooltip.is-bottom:after,.vue-custom-tooltip.is-bottom:before{top:calc(100% + 5px + 2px);right:auto;bottom:auto;left:50%;transform:translateX(-50%)}.vue-custom-tooltip.is-bottom:before{border-right:5px solid transparent;border-bottom:5px solid #000;border-bottom:5px solid var(--vue-custom-tooltip-background);border-left:5px solid transparent;top:calc(100% + 2px)}.vue-custom-tooltip.is-bottom.has-multiline.is-small:after{width:140px}.vue-custom-tooltip.is-bottom.has-multiline.is-medium:after{width:250px;padding:.6rem 1.25rem .65rem}.vue-custom-tooltip.is-bottom.has-multiline.is-large:after{width:480px;padding:0.6rem 1rem 0.65rem}.vue-custom-tooltip.is-left:after,.vue-custom-tooltip.is-left:before{top:50%;right:calc(100% + 5px + 2px);bottom:auto;left:auto;transform:translateY(-50%)}.vue-custom-tooltip.is-left:before{border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000;border-left:5px solid var(--vue-custom-tooltip-background);right:calc(100% + 2px)}.vue-custom-tooltip.is-left.has-multiline.is-small:after{width:140px}.vue-custom-tooltip.is-left.has-multiline.is-medium:after{width:250px;padding:.6rem 1.25rem .65rem}.vue-custom-tooltip.is-left.has-multiline.is-large:after{width:480px;padding:0.6rem 1rem 0.65rem}.vue-custom-tooltip.is-underlined{border-bottom:1px dotted #000;border-bottom:1px dotted var(--vue-custom-tooltip-background);line-height:1.2}.vue-custom-tooltip:after,.vue-custom-tooltip:before{position:absolute;content:'';opacity:0;visibility:hidden;pointer-events:none;transition:opacity 86ms ease-out,visibility 86ms ease-out}.vue-custom-tooltip:before{z-index:889}.vue-custom-tooltip:after{content:attr(data-label);color:#fff;color:var(--vue-custom-tooltip-color);background:#000;background:var(--vue-custom-tooltip-background);width:auto;max-width:100vw;padding:.35rem .75rem .45rem;border-radius:100px;border-radius:var(--vue-custom-tooltip-border-radius);font-size:.85rem!important;font-weight:400;font-weight:var(--vue-custom-tooltip-font-weight);line-height:1.3;letter-spacing:normal!important;text-transform:none;box-shadow:0 1px 2px 1px rgba(0,1,0,.2);z-index:888;white-space:nowrap}.vue-custom-tooltip:not([data-label='']):hover:after,.vue-custom-tooltip:not([data-label='']):hover:before{opacity:1;visibility:visible}:disabled .vue-custom-tooltip{pointer-events:none}.vue-custom-tooltip:not([data-label='']).is-sticky:after,.vue-custom-tooltip:not([data-label='']).is-sticky:before{opacity:1;visibility:visible}.vue-custom-tooltip.has-multiline:after{display:block;padding:.5rem .75rem .65rem;text-align:center;line-height:1.4;white-space:pre-wrap}", map: undefined, media: undefined }); 285 | 286 | }; 287 | /* scoped */ 288 | var __vue_scope_id__ = undefined; 289 | /* module identifier */ 290 | var __vue_module_identifier__ = undefined; 291 | /* functional template */ 292 | var __vue_is_functional_template__ = false; 293 | /* style inject SSR */ 294 | 295 | /* style inject shadow dom */ 296 | 297 | 298 | 299 | var __vue_component__ = /*#__PURE__*/normalizeComponent( 300 | { render: __vue_render__, staticRenderFns: __vue_staticRenderFns__ }, 301 | __vue_inject_styles__, 302 | __vue_script__, 303 | __vue_scope_id__, 304 | __vue_is_functional_template__, 305 | __vue_module_identifier__, 306 | false, 307 | createInjector, 308 | undefined, 309 | undefined 310 | ); 311 | 312 | // Import vue component 313 | 314 | var defaultOptions = { 315 | name: 'VueCustomTooltip', 316 | color: '#fff', 317 | background: '#000', 318 | borderRadius: 12, 319 | fontWeight: 400, 320 | }; 321 | 322 | // Declare install function executed by Vue.use() 323 | var install = function installMyComponent(Vue, opt) { 324 | // Don't install if already installed, or SSR 325 | if (install.installed || Vue.prototype.$isServer) { return } 326 | install.installed = true; 327 | 328 | // Grab user options 329 | var userOptions = Object.assign({}, opt); 330 | 331 | // HEX regex: Hash, plus 3 or 6 valid characters 332 | var hexRegex = /^#(?:[0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/; 333 | // Test color for valid HEX 334 | if (userOptions.hasOwnProperty('color') && !hexRegex.test(userOptions.color)) { 335 | delete userOptions.color; 336 | } 337 | // Test background for valid HEX 338 | if (userOptions.hasOwnProperty('background') && !hexRegex.test(userOptions.background)) { 339 | delete userOptions.background; 340 | } 341 | 342 | // borderRadius regex: number between 1-9, then any other numbers 343 | var borderRadiusRegex = /^[0-9]+$/; 344 | // Test borderRadius for integer 345 | if (userOptions.hasOwnProperty('borderRadius') && !borderRadiusRegex.test(userOptions.borderRadius)) { 346 | delete userOptions.borderRadius; 347 | } 348 | 349 | // fontWeight regex: number between 1-9 followed by two zeros 350 | var fontWeightRegex = /^[1-9]{1}00$/; 351 | // Test fontWeight for integer 352 | if (userOptions.hasOwnProperty('fontWeight') && !fontWeightRegex.test(userOptions.fontWeight)) { 353 | delete userOptions.fontWeight; 354 | } 355 | 356 | // Merge options 357 | var options = Object.assign({}, defaultOptions, userOptions); 358 | 359 | // Mutate borderRadius 360 | options.borderRadius = options.borderRadius + 'px'; 361 | 362 | // Add global property (mainly for passing styles) 363 | Vue.prototype.$vueCustomTooltip = options; 364 | 365 | // Register component, using options.name. 366 | // e.g. 367 | Vue.component(options.name, __vue_component__); 368 | }; 369 | 370 | // Create module definition for Vue.use() 371 | var plugin = { 372 | install: install, 373 | }; 374 | 375 | // Auto-install when vue is found (eg. in browser via 137 | 138 | 147 | 148 | 293 | -------------------------------------------------------------------------------- /src/wrapper.js: -------------------------------------------------------------------------------- 1 | // Import vue component 2 | import VueCustomTooltip from './VueCustomTooltip.vue' 3 | 4 | const defaultOptions = { 5 | name: 'VueCustomTooltip', 6 | color: '#fff', 7 | background: '#000', 8 | borderRadius: 100, 9 | fontWeight: 400, 10 | } 11 | 12 | // Declare install function executed by Vue.use() 13 | const install = function installMyComponent(Vue, opt) { 14 | // Don't install if already installed, or SSR 15 | if (install.installed || Vue.prototype.$isServer) return 16 | install.installed = true 17 | 18 | // Grab user options 19 | let userOptions = Object.assign({}, opt) 20 | 21 | // HEX regex: Hash, plus 3 or 6 valid characters 22 | const hexRegex = /^#(?:[0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/ 23 | // Test color for valid HEX 24 | if (userOptions.hasOwnProperty('color') && !hexRegex.test(userOptions.color)) { 25 | delete userOptions.color 26 | } 27 | // Test background for valid HEX 28 | if (userOptions.hasOwnProperty('background') && !hexRegex.test(userOptions.background)) { 29 | delete userOptions.background 30 | } 31 | 32 | // borderRadius regex: number between 1-9, then any other numbers 33 | const borderRadiusRegex = /^[0-9]+$/ 34 | // Test borderRadius for integer 35 | if (userOptions.hasOwnProperty('borderRadius') && !borderRadiusRegex.test(userOptions.borderRadius)) { 36 | delete userOptions.borderRadius 37 | } 38 | 39 | // fontWeight regex: number between 1-9 followed by two zeros 40 | const fontWeightRegex = /^[1-9]{1}00$/ 41 | // Test fontWeight for integer 42 | if (userOptions.hasOwnProperty('fontWeight') && !fontWeightRegex.test(userOptions.fontWeight)) { 43 | delete userOptions.fontWeight 44 | } 45 | 46 | // Merge options 47 | let options = Object.assign({}, defaultOptions, userOptions) 48 | 49 | // Mutate borderRadius 50 | options.borderRadius = options.borderRadius + 'px' 51 | 52 | // Add global property (mainly for passing styles) 53 | Vue.prototype.$vueCustomTooltip = options 54 | 55 | // Register component, using options.name. 56 | // e.g. 57 | Vue.component(options.name, VueCustomTooltip) 58 | } 59 | 60 | // Create module definition for Vue.use() 61 | const plugin = { 62 | install, 63 | } 64 | 65 | // Auto-install when vue is found (eg. in browser via