@vue-interface/tooltip 1.0.0-beta.8 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # @vue-interface/tooltip
2
+
3
+ ## 2.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - 7119322: Add support for TailwindCSS v4
@@ -0,0 +1,156 @@
1
+ ---
2
+ title: Tooltip
3
+ ---
4
+
5
+ <script setup>
6
+ import '../index.css'
7
+ import { ref, defineComponent, h, getCurrentInstance } from 'vue'
8
+ import { Tooltip, TooltipPlugin } from '../index.ts'
9
+
10
+ const dynamicEl = ref(false)
11
+ const directiveEl = ref(false)
12
+
13
+ const WithTooltip = defineComponent({
14
+ name: 'WithTooltip',
15
+ setup(_, { slots }) {
16
+ const el = ref()
17
+ return () =>
18
+ h(
19
+ 'a',
20
+ { ref: el, href: '#', class: 'text-blue-500 hover:underline' },
21
+ slots.default?.({ el })
22
+ )
23
+ }
24
+ })
25
+
26
+ const app = getCurrentInstance()?.appContext.app
27
+ if (app) {
28
+ app.use(TooltipPlugin)
29
+ app.component('Tooltip', Tooltip)
30
+ app.component('with-tooltip', WithTooltip)
31
+ }
32
+ </script>
33
+
34
+ # Tooltip
35
+
36
+ The `tooltip` component provides flexible tooltips with customizable placement options.
37
+
38
+ ## Basic Usage
39
+
40
+ Tooltips can be positioned at the top, bottom, left, or right of the target element.
41
+
42
+ <div class="flex gap-4">
43
+ <!-- #region basicUsage -->
44
+ <with-tooltip v-slot="{el}">
45
+ Top
46
+ <Tooltip :target="el">
47
+ Some Tooltip
48
+ </Tooltip>
49
+ </with-tooltip>
50
+ <with-tooltip v-slot="{el}">
51
+ Bottom
52
+ <Tooltip :target="el" placement="bottom">
53
+ Some Tooltip
54
+ </Tooltip>
55
+ </with-tooltip>
56
+ <with-tooltip v-slot="{el}">
57
+ Left
58
+ <Tooltip :target="el" placement="left">
59
+ Some Tooltip
60
+ </Tooltip>
61
+ </with-tooltip>
62
+ <with-tooltip v-slot="{el}">
63
+ Right
64
+ <Tooltip :target="el" placement="right">
65
+ Some Tooltip
66
+ </Tooltip>
67
+ </with-tooltip>
68
+ <!-- #endregion basicUsage -->
69
+ </div>
70
+
71
+ ::: details Show Code
72
+ <<< @/packages/tooltip/docs/tooltip.md#basicUsage{html}
73
+ :::
74
+
75
+ ## Via Plugin
76
+
77
+ Use the Tooltip plugin to automatically create tooltips from `title` attributes.
78
+
79
+ <div class="flex gap-4 [&>a]:text-blue-500 [&>a]:hover:underline">
80
+ <!-- #region viaPlugin -->
81
+ <a href="#" title="Some Tooltip" data-tooltip-placement="top">
82
+ Top
83
+ </a>
84
+ <a href="#" title="Some Tooltip" data-tooltip-placement="bottom">
85
+ Bottom
86
+ </a>
87
+ <a href="#" title="Some Tooltip" data-tooltip-placement="left">
88
+ Left
89
+ </a>
90
+ <a href="#" title="Some Tooltip" data-tooltip-placement="right">
91
+ Right
92
+ </a>
93
+ <!-- #endregion viaPlugin -->
94
+ </div>
95
+
96
+ ::: details Show Code
97
+ <<< @/packages/tooltip/docs/tooltip.md#viaPlugin{html}
98
+ :::
99
+
100
+ ## Dynamic Elements
101
+
102
+ Tooltips work with dynamically mounted and unmounted elements.
103
+
104
+ <div class="flex gap-4 [&>button]:p-2 [&>button]:rounded [&>button]:bg-blue-500">
105
+ <!-- #region dynamicElements -->
106
+ <button @click="dynamicEl = true">Mount Element</button>
107
+ <button v-if="dynamicEl" title="Some Tooltip" @click="dynamicEl = false">
108
+ Click to Unmount
109
+ </button>
110
+ <!-- #endregion dynamicElements -->
111
+ </div>
112
+
113
+ ::: details Show Code
114
+ <<< @/packages/tooltip/docs/tooltip.md#dynamicElements{html}
115
+ :::
116
+
117
+ ## Via Directive
118
+
119
+ Use the `v-tooltip` directive for a more concise syntax.
120
+
121
+ <div class="flex gap-4 [&>button]:p-2 [&>button]:rounded [&>button]:bg-blue-500">
122
+ <!-- #region viaDirective -->
123
+ <button @click="directiveEl = true">Mount Element</button>
124
+ <button v-if="directiveEl" v-tooltip="'Some Tooltip'" @click="directiveEl = false">
125
+ Click to Unmount
126
+ </button>
127
+ <button v-if="directiveEl" v-tooltip="{
128
+ title: 'Some Tooltip',
129
+ placement: 'top'
130
+ }" @click="directiveEl = false">
131
+ Top
132
+ </button>
133
+ <button v-if="directiveEl" v-tooltip="{
134
+ title: 'Some Tooltip',
135
+ placement: 'bottom'
136
+ }" @click="directiveEl = false">
137
+ Bottom
138
+ </button>
139
+ <button v-if="directiveEl" v-tooltip="{
140
+ title: 'Some Tooltip',
141
+ placement: 'left'
142
+ }" @click="directiveEl = false">
143
+ Left
144
+ </button>
145
+ <button v-if="directiveEl" v-tooltip="{
146
+ title: 'Some Tooltip',
147
+ placement: 'right'
148
+ }" @click="directiveEl = false">
149
+ Right
150
+ </button>
151
+ <!-- #endregion viaDirective -->
152
+ </div>
153
+
154
+ ::: details Show Code
155
+ <<< @/packages/tooltip/docs/tooltip.md#viaDirective{html}
156
+ :::
package/index.css ADDED
@@ -0,0 +1,71 @@
1
+ @import "tailwindcss";
2
+
3
+ @theme {
4
+ --tooltip-position: absolute;
5
+ --tooltip-hiding-z-index: -1;
6
+ --tooltip-showing-z-index: 100;
7
+ --tooltip-display: block;
8
+ --tooltip-font-size: .875rem;
9
+ --tooltip-word-wrap: break-word;
10
+ --tooltip-opacity-hidden: 0;
11
+ --tooltip-opacity-visible: .9;
12
+ --tooltip-transition: opacity .15s ease-out;
13
+ --tooltip-pointer-events: none;
14
+
15
+ --tooltip-inner-position: relative;
16
+ --tooltip-inner-max-width: 200px;
17
+ --tooltip-inner-padding-y: .25em;
18
+ --tooltip-inner-padding-x: .5em;
19
+ --tooltip-inner-background-color: var(--colors-black, black);
20
+ --tooltip-inner-color: var(--color-white, white);
21
+ --tooltip-inner-text-align: center;
22
+ --tooltip-inner-border-radius: .333em;
23
+ --tooltip-inner-z-index: 2;
24
+ --tooltip-inner-dark-color: var(--color-white, white);
25
+ --tooltip-inner-dark-background-color: var(--colors-black, black);
26
+
27
+ --tooltip-arrow-position: absolute;
28
+ --tooltip-arrow-display: block;
29
+ --tooltip-arrow-width: .5em;
30
+ --tooltip-arrow-height: .5em;
31
+ --tooltip-arrow-z-index: 1;
32
+ --tooltip-color: var(--colors-black, black);
33
+ --tooltip-arrow-background-color: var(--tooltip-inner-background-color);
34
+ --tooltip-arrow-dark-background-color: var(--tooltip-inner-dark-background-color);
35
+ }
36
+
37
+ @utility tooltip {
38
+ position: var(--tooltip-position);
39
+ z-index: var(--tooltip-z-hiding-index);
40
+ display: var(--tooltip-display);
41
+ font-size: var(--tooltip-font-size);
42
+ word-wrap: var(--tooltip-word-wrap);
43
+ opacity: var(--tooltip-opacity-hidden);
44
+ transition: var(--tooltip-transition);
45
+ pointer-events: var(--tooltip-pointer-events);
46
+
47
+ &.show {
48
+ opacity: var(--tooltip-opacity-visible);
49
+ z-index: var(--tooltip-z-showing-index);
50
+ }
51
+
52
+ .tooltip-inner {
53
+ position: var(--tooltip-inner-position);
54
+ max-width: var(--tooltip-inner-max-width);
55
+ padding: var(--tooltip-inner-padding-y) var(--tooltip-inner-padding-x);
56
+ background-color: var(--tooltip-inner-background-color);
57
+ color: var(--tooltip-inner-color);
58
+ text-align: var(--tooltip-inner-text-align);
59
+ border-radius: var(--tooltip-inner-border-radius);
60
+ z-index: var(--tooltip-inner-z-index);
61
+ }
62
+
63
+ .tooltip-arrow {
64
+ position: var(--tooltip-arrow-position);
65
+ display: var(--tooltip-arrow-display);
66
+ width: var(--tooltip-arrow-width);
67
+ height: var(--tooltip-arrow-height);
68
+ background-color: var(--tooltip-arrow-background-color);
69
+ z-index: var(--tooltip-arrow-index);
70
+ }
71
+ }
package/index.html ADDED
@@ -0,0 +1,140 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
6
+ <title>Tooltip</title>
7
+ </head>
8
+ <body class="dark:bg-neutral-900 dark:text-white">
9
+ <div id="app" class="container mx-auto flex flex-col gap-6">
10
+ <h1 class="text-4xl">tooltip</h1>
11
+
12
+ <div class="flex gap-4">
13
+ <with-tooltip v-slot="{el}">
14
+ Top
15
+ <Tooltip :target="el">
16
+ Some Toolip
17
+ </Tooltip>
18
+ </with-tooltip>
19
+
20
+ <with-tooltip v-slot="{el}">
21
+ Bottom
22
+ <Tooltip :target="el" placement="bottom">
23
+ Some Toolip
24
+ </Tooltip>
25
+ </with-tooltip>
26
+
27
+ <with-tooltip v-slot="{el}">
28
+ Left
29
+ <Tooltip :target="el" placement="left">
30
+ Some Toolip
31
+ </Tooltip>
32
+ </with-tooltip>
33
+
34
+ <with-tooltip v-slot="{el}">
35
+ Right
36
+ <Tooltip :target="el" placement="right">
37
+ Some Toolip
38
+ </Tooltip>
39
+ </with-tooltip>
40
+ </div>
41
+
42
+ <h2 class="text-2xl">Via Plugin</h2>
43
+
44
+ <div class="flex gap-4 [&>a]:text-blue-500 [&>a]:hover:underline">
45
+ <a href="#" title="Some Tooltip" data-tooltip-placement="top">
46
+ Top
47
+ </a>
48
+ <a href="#" title="Some Tooltip" data-tooltip-placement="bottom">
49
+ Bottom
50
+ </a>
51
+ <a href="#" title="Some Tooltip" data-tooltip-placement="left">
52
+ Left
53
+ </a>
54
+ <a href="#" title="Some Tooltip" data-tooltip-placement="right">
55
+ Right
56
+ </a>
57
+ </div>
58
+
59
+ <h2 class="text-2xl">Dynamic Elements</h2>
60
+
61
+ <div class="flex gap-4 [&>button]:p-2 [&>button]:rounded [&>button]:bg-blue-500">
62
+ <button @click="dynamicEl = true">Mount Element</button>
63
+
64
+ <button v-if="dynamicEl" title="Some Tooltip" @click="dynamicEl = false">
65
+ Click to Unmount
66
+ </button>
67
+ </div>
68
+
69
+ <h2 class="text-2xl">Via Directive</h2>
70
+
71
+ <div class="flex gap-4 [&>button]:p-2 [&>button]:rounded [&>button]:bg-blue-500">
72
+ <button @click="directiveEl = true">Mount Element</button>
73
+
74
+ <button v-if="directiveEl" v-tooltip="'Some Tooltip'" @click="directiveEl = false">
75
+ Click to Unmount
76
+ </button>
77
+
78
+ <button v-if="directiveEl" v-tooltip="{
79
+ title: 'Some Tooltip',
80
+ placement: 'top'
81
+ }" @click="directiveEl = false">
82
+ Top
83
+ </button>
84
+
85
+ <button v-if="directiveEl" v-tooltip="{
86
+ title: 'Some Tooltip',
87
+ placement: 'bottom'
88
+ }" @click="directiveEl = false">
89
+ Bottom
90
+ </button>
91
+
92
+ <button v-if="directiveEl" v-tooltip="{
93
+ title: 'Some Tooltip',
94
+ placement: 'left'
95
+ }" @click="directiveEl = false">
96
+ Left
97
+ </button>
98
+
99
+ <button v-if="directiveEl" v-tooltip="{
100
+ title: 'Some Tooltip',
101
+ placement: 'right'
102
+ }" @click="directiveEl = false">
103
+ Right
104
+ </button>
105
+ </div>
106
+ </div>
107
+ <script type="module">
108
+ import './index.css';
109
+ import { createApp, defineComponent, h, ref, onMounted } from 'vue/dist/vue.esm-bundler';
110
+ import { Tooltip, TooltipPlugin } from './index';
111
+
112
+ const WithTooltip = defineComponent({
113
+ setup(props, a) {
114
+ const el = ref();
115
+
116
+ return () => h('a', {
117
+ ref: el,
118
+ href: '#',
119
+ classList: 'text-blue-500 hover:underline'
120
+ }, a.slots.default({ el }))
121
+ }
122
+ });
123
+
124
+ const vue = createApp({
125
+ components: {
126
+ WithTooltip,
127
+ Tooltip
128
+ },
129
+ data() {
130
+ return {
131
+ dynamicEl: false,
132
+ directiveEl: false,
133
+ }
134
+ }
135
+ })
136
+ .use(TooltipPlugin)
137
+ .mount('#app');
138
+ </script>
139
+ </body>
140
+ </html>
package/index.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import Tooltip from './src/Tooltip.vue';
2
- import TooltipPlugin from './src/TooltipPlugin';
2
+ import { TooltipDirective, TooltipPlugin } from './src/TooltipPlugin';
3
3
 
4
4
  export {
5
5
  Tooltip,
6
+ TooltipDirective,
6
7
  TooltipPlugin
7
8
  };
package/package.json CHANGED
@@ -1,34 +1,25 @@
1
1
  {
2
2
  "name": "@vue-interface/tooltip",
3
- "version": "1.0.0-beta.8",
3
+ "version": "2.0.0",
4
4
  "description": "A Vue tooltip component.",
5
- "files": [
6
- "index.ts",
7
- "dist",
8
- "src",
9
- "tailwindcss"
10
- ],
11
5
  "type": "module",
12
6
  "main": "./dist/tooltip.umd.js",
13
7
  "module": "./dist/tooltip.js",
14
- "browserslist": "last 2 versions, > 0.5%, ie >= 11",
8
+ "types": "./dist/index.d.ts",
15
9
  "exports": {
16
10
  ".": {
11
+ "source": "./index.ts",
12
+ "types": "./index.ts",
17
13
  "import": "./dist/tooltip.js",
18
- "require": "./dist/tooltip.umd.js",
19
- "types": "./index.ts"
14
+ "require": "./dist/tooltip.umd.js"
20
15
  },
21
16
  "./tailwindcss": "./tailwindcss/index.cjs",
22
17
  "./tailwindcss/safelist": "./tailwindcss/safelist.cjs"
23
18
  },
24
- "scripts": {
25
- "dev": "vite",
26
- "build": "vite build",
27
- "preview": "vite preview"
28
- },
19
+ "browserslist": "last 2 versions, > 0.5%, ie >= 11",
29
20
  "repository": {
30
21
  "type": "git",
31
- "url": "git+https://github.com/vue-interface/tooltip.git"
22
+ "url": "git+https://github.com/vue-interface/tooltip"
32
23
  },
33
24
  "keywords": [
34
25
  "Tooltip",
@@ -40,27 +31,22 @@
40
31
  "author": "Justin Kimbrell",
41
32
  "license": "ISC",
42
33
  "bugs": {
43
- "url": "https://github.com/vue-interface/tooltip/issues"
34
+ "url": "https://github.com/vue-interface/tooltip"
44
35
  },
45
- "homepage": "https://github.com/vue-interface/tooltip",
36
+ "homepage": "https://github.com/vue-interface/vue-interface",
46
37
  "dependencies": {
47
38
  "@popperjs/core": "^2.4.4"
48
39
  },
49
40
  "peerDependencies": {
50
- "vue": "^3.0.0"
41
+ "@floating-ui/vue": "^1.1.7",
42
+ "vue": "^3.3.4"
51
43
  },
52
44
  "devDependencies": {
53
- "@vitejs/plugin-vue": "^3.0.1",
54
- "@vue-interface/eslint-config": "^1.0.0-beta.0",
55
- "autoprefixer": "^10.4.2",
56
- "change-case": "^4.1.2",
57
- "eslint": "^8.27.0",
58
- "pascalcase": "^2.0.0",
59
- "postcss": "^8.4.6",
60
- "tailwindcss": "^3.0.18",
61
- "typescript": "^5.0.2",
62
- "vite": "^3.0.0",
63
- "vite-plugin-dts": "^1.7.1",
64
- "vue": "^3.2.37"
45
+ "@floating-ui/dom": "^1.7.2"
46
+ },
47
+ "scripts": {
48
+ "dev": "vite",
49
+ "build": "vue-tsc && vite build",
50
+ "preview": "vite preview"
65
51
  }
66
- }
52
+ }
package/src/Tooltip.vue CHANGED
@@ -1,32 +1,174 @@
1
- <script lang="ts">
2
- import { defineComponent } from 'vue';
3
- import Popper from './Popper';
1
+ <script lang="ts" setup>
2
+ import { arrow, autoUpdate, flip as flipFn, FlipOptions, MaybeElement, offset as offsetFn, OffsetOptions, ReferenceElement, useFloating, UseFloatingOptions } from '@floating-ui/vue';
3
+ import { computed, isRef, onUnmounted, Ref, ref, shallowReadonly, ShallowRef, useTemplateRef, watchEffect } from 'vue';
4
4
 
5
- export default defineComponent({
6
- mixins: [
7
- Popper
8
- ]
5
+ export type TooltipProps = {
6
+ title?: string;
7
+ show?: boolean;
8
+ target?: Ref<MaybeElement<ReferenceElement>> | ReferenceElement;
9
+ placement?: UseFloatingOptions['placement'];
10
+ strategy?: UseFloatingOptions['strategy'];
11
+ middleware?: (arrow: Readonly<ShallowRef<HTMLDivElement | null>>) => UseFloatingOptions['middleware'];
12
+ flip?: FlipOptions;
13
+ offset?: OffsetOptions;
14
+ };
15
+
16
+ const props = withDefaults(defineProps<TooltipProps>(), {
17
+ title: undefined,
18
+ target: undefined,
19
+ placement: 'top',
20
+ middleware: undefined,
21
+ strategy: undefined,
22
+ flip: undefined,
23
+ offset: undefined,
24
+ });
25
+
26
+ defineSlots<{
27
+ default: () => void
28
+ }>();
29
+
30
+ const tooltipEl = useTemplateRef<HTMLDivElement>('tooltipEl');
31
+ const arrowEl = useTemplateRef<HTMLDivElement>('arrowEl');
32
+ const isShowing = ref(false);
33
+ const hash = Math.random().toString(36).slice(2, 12);
34
+
35
+ const targetEl = isRef(props.target)
36
+ ? props.target
37
+ : shallowReadonly(ref(props.target));
38
+
39
+ const id = computed(() => {
40
+ if(!(targetEl.value instanceof Element)) {
41
+ return;
42
+ }
43
+
44
+ return targetEl.value.getAttribute('data-tooltip-id');
45
+ });
46
+
47
+
48
+ watchEffect(() => {
49
+ if(!targetEl.value || id.value) {
50
+ return;
51
+ }
52
+
53
+ if(targetEl.value instanceof Element) {
54
+ targetEl.value.setAttribute('data-tooltip-id', hash);
55
+ targetEl.value.addEventListener('mouseover', open);
56
+ targetEl.value.addEventListener('mouseout', close);
57
+ }
58
+ });
59
+
60
+ watchEffect(() => {
61
+ isShowing.value = props.show;
62
+ });
63
+
64
+ const dynamicOffset = computed<OffsetOptions>(() => {
65
+ if(props.offset) {
66
+ return props.offset;
67
+ }
68
+
69
+ return () => {
70
+ const { height } = arrowEl.value ? getComputedStyle(arrowEl.value) : { height: '0px' };
71
+
72
+ return {
73
+ mainAxis: parseInt(height.replace('px', '')),
74
+ };
75
+ };
76
+ });
77
+
78
+ const { floatingStyles, middlewareData, placement } = useFloating(targetEl, tooltipEl, {
79
+ placement: props.placement,
80
+ middleware: props.middleware?.(arrowEl) ?? [
81
+ flipFn(props.flip),
82
+ offsetFn(dynamicOffset.value),
83
+ arrow({
84
+ element: arrowEl
85
+ }),
86
+ ],
87
+ whileElementsMounted: autoUpdate
88
+ });
89
+
90
+ const tooltipClasses = computed(() => {
91
+ return {
92
+ show: isShowing.value
93
+ };
94
+ });
95
+
96
+ type Side = 'bottom' | 'left' | 'top' | 'right';
97
+
98
+ const side = computed(() => placement.value.split('-')[0] as Side);
99
+
100
+ const arrowPosition = computed(() => {
101
+ return {
102
+ top: 'bottom',
103
+ right: 'left',
104
+ bottom: 'top',
105
+ left: 'right'
106
+ }[side.value] as Side;
107
+ });
108
+
109
+ const arrowRotation = computed<Record<Side,string>>(() => ({
110
+ top: 'rotate(225deg)',
111
+ right: 'rotate(-45deg)',
112
+ bottom: 'rotate(45deg)',
113
+ left: 'rotate(135deg)',
114
+ }));
115
+
116
+ function open() {
117
+ isShowing.value = true;
118
+ }
119
+
120
+ function close() {
121
+ isShowing.value = false;
122
+ }
123
+
124
+ onUnmounted(() => {
125
+ if(targetEl.value instanceof Element) {
126
+ targetEl.value.removeAttribute('data-tooltip-id');
127
+ }
128
+ });
129
+
130
+ defineExpose({
131
+ open,
132
+ close,
133
+ tooltipEl,
134
+ arrowEl,
135
+ isShowing,
136
+ hash,
9
137
  });
10
138
  </script>
11
139
 
12
140
  <template>
13
- <div
14
- class="tooltip"
15
- :class="tooltipClasses"
16
- role="tooltip">
17
- <div
18
- ref="arrow"
19
- class="tooltip-arrow" />
141
+ <Teleport to="body">
20
142
  <div
21
- ref="inner"
22
- class="tooltip-inner">
23
- <slot>{{ title }}</slot>
143
+ ref="tooltipEl"
144
+ class="tooltip"
145
+ role="tooltip"
146
+ :data-tooltip-id="hash"
147
+ :class="tooltipClasses"
148
+ :style="floatingStyles">
149
+ <div
150
+ ref="arrowEl"
151
+ class="tooltip-arrow"
152
+ :style="{
153
+ transform: arrowRotation[arrowPosition],
154
+ ...Object.assign({
155
+ left:
156
+ middlewareData.arrow?.x != null
157
+ ? `${middlewareData.arrow.x}px`
158
+ : '',
159
+ top:
160
+ middlewareData.arrow?.y != null
161
+ ? `${middlewareData.arrow.y}px`
162
+ : ''
163
+ }, {
164
+ [arrowPosition]: `calc(${-(arrowEl?.offsetWidth ?? 0) / 2}px)`
165
+ })
166
+ }" />
167
+ <div
168
+ ref="inner"
169
+ class="tooltip-inner">
170
+ <slot>{{ title }}</slot>
171
+ </div>
24
172
  </div>
25
- </div>
26
- </template>
27
-
28
- <style>
29
- .tooltip:not(.show) {
30
- z-index: -1;
31
- }
32
- </style>
173
+ </Teleport>
174
+ </template>