@vue-interface/tooltip 1.0.0-beta.0 → 1.0.0-beta.10

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/src/Tooltip.vue CHANGED
@@ -1,23 +1,30 @@
1
- <template>
2
- <div class="tooltip" :class="tooltipClasses" role="tooltip">
3
- <div ref="arrow" class="tooltip-arrow" />
4
- <div ref="inner" class="tooltip-inner">
5
- <slot />
6
- </div>
7
- </div>
8
- </template>
9
-
10
1
  <script lang="ts">
11
- // @ts-ignore
2
+ import { defineComponent } from 'vue';
12
3
  import Popper from './Popper';
13
4
 
14
- export default {
5
+ export default defineComponent({
15
6
  mixins: [
16
7
  Popper
17
8
  ]
18
- };
9
+ });
19
10
  </script>
20
11
 
12
+ <template>
13
+ <div
14
+ class="tooltip"
15
+ :class="tooltipClasses"
16
+ role="tooltip">
17
+ <div
18
+ ref="arrow"
19
+ class="tooltip-arrow" />
20
+ <div
21
+ ref="inner"
22
+ class="tooltip-inner">
23
+ <slot>{{ title }}</slot>
24
+ </div>
25
+ </div>
26
+ </template>
27
+
21
28
  <style>
22
29
  .tooltip:not(.show) {
23
30
  z-index: -1;
@@ -1,73 +1,191 @@
1
- import type { App, VNode } from 'vue';
1
+ import type { App } from 'vue';
2
2
  import { h, render } from 'vue';
3
3
  import Tooltip from './Tooltip.vue';
4
4
 
5
- export default function(app: App, options = {}) {
6
-
7
- function createTooltip(target: HTMLElement, args: Record<string,any> = {}): {el: Element, vnode: VNode, close: Function} {
5
+ type TooltipPluginOptions = {
6
+ delay?: number,
7
+ prefix: string,
8
+ progressiveEnhancement: boolean,
9
+ triggers: {
10
+ open: string[],
11
+ close: string[],
12
+ }
13
+ }
14
+
15
+ export default function (app: App, opts: Partial<TooltipPluginOptions> = {}) {
16
+ const tooltips: Map<string,Function> = new Map;
17
+
18
+ const options: TooltipPluginOptions = Object.assign({
19
+ delay: undefined,
20
+ prefix: 'data-tooltip',
21
+ progressiveEnhancement: true,
22
+ triggers: {
23
+ open: ['mouseover:350'],
24
+ close: ['mouseout:100'],
25
+ }
26
+ }, opts);
27
+
28
+ const prefix = options.prefix.replace(/[-]+$/, '');
29
+ const prefixRegExp = new RegExp(`^${prefix}\-`);
30
+
31
+ function getAttributes(el: Element): Record<string,any> {
32
+ return Array.from(el.attributes)
33
+ .map(a => [a.name, a.value])
34
+ .filter(([key]) => key === 'title' || key.match(prefixRegExp))
35
+ .map(([key, value]) => [key.replace(new RegExp(prefixRegExp), ''), value])
36
+ .reduce((carry, attr) => Object.assign(carry, { [attr[0]]: attr[1] }), {});
37
+ }
38
+
39
+ function createTooltip(target: Element, props: Record<string,any> = {}, hash: string): Function {
8
40
  const container = document.createElement('template');
9
- const title = target.getAttribute('data-tooltip') || '';
10
-
41
+
11
42
  const vnode = h(Tooltip, Object.assign({
12
43
  target,
13
44
  show: true
14
- }, args), () => title);
15
-
45
+ }, props));
46
+
16
47
  render(vnode, container);
17
-
48
+
18
49
  const [el] = [...container.children];
19
-
50
+
20
51
  document.body.append(el);
52
+
53
+ return () => {
54
+ tooltips.delete(hash);
21
55
 
22
- return {
23
- el,
24
- vnode,
25
- close() {
26
- // @ts-ignore
27
- vnode.component?.ctx.close();
28
-
29
- setTimeout(() => el.remove(), 150);
30
- }
56
+ // @ts-ignore
57
+ vnode.component?.ctx.close();
58
+
59
+ // @todo: Make the animation rate (150) dynamic. Should get value
60
+ // from the CSS transition duration.
61
+ setTimeout(() => el.remove(), 150);
31
62
  };
32
63
  }
33
-
34
- app.mixin({
35
- created() {
36
- console.log('created');
37
- }
38
- });
39
64
 
40
- app.directive('tooltip', (target, binding) => {
41
- if(!target.getAttribute('data-tooltip')) {
42
- target.setAttribute('data-tooltip', target.getAttribute('title'));
65
+ function init(target: Element, props = {}) {
66
+ const properties: Record<string,any> = Object.assign({
67
+ title: target.getAttribute(prefix) || target.getAttribute('title')
68
+ }, props, getAttributes(target));
69
+
70
+ // If the properties don't have a title, ignore this target.
71
+ if(!properties.title || target.hasAttribute(`${prefix}-id`)) {
72
+ return;
43
73
  }
74
+
75
+ // Create a unique "hash" to show the node has been initialized.
76
+ // This prevents double initializing on the same element.
77
+ const hash = Math.random().toString(36).slice(2, 7);
44
78
 
45
- target.removeAttribute('title');
46
-
47
- target.addEventListener('mouseover', (e: Event) => {
48
- clearTimeout(target.timer);
49
-
50
- if(!target.tooltip) {
51
- target.timer = setTimeout(() => {
52
- target.tooltip = createTooltip(target, {
53
- top: binding.modifiers.top,
54
- bottom: binding.modifiers.bottom,
55
- left: binding.modifiers.left,
56
- right: binding.modifiers.right,
57
- });
58
- }, 1000);
79
+ // Create the instance vars.
80
+ let tooltip: Function|null, timer: number;
81
+
82
+ //target.setAttribute(prefix, properties.title);
83
+ target.setAttribute(`${prefix}-id`, hash);
84
+ // target.removeAttribute('title');
85
+
86
+ function open(delay = 0) {
87
+ clearTimeout(timer);
88
+
89
+ if(!tooltip) {
90
+ timer = setTimeout(() => {
91
+ // Do a check before creating the tooltip to ensure the dom
92
+ // element still exists. Its possible for the element to
93
+ // be removed after the timeout delay runs.
94
+ if(document.contains(target)) {
95
+ tooltip = createTooltip(target, properties, hash);
96
+ tooltips.set(hash, tooltip);
97
+ }
98
+ }, delay);
59
99
  }
60
- });
100
+ }
61
101
 
62
- target.addEventListener('mouseout', (e: Event) => {
63
- clearTimeout(target.timer);
102
+ function close(delay = 0) {
103
+ clearTimeout(timer);
64
104
 
65
- if(target.tooltip) {
66
- target.timer = setTimeout(() => {
67
- target.tooltip && target.tooltip.close();
68
- target.tooltip = null;
69
- }, 1000);
105
+ if(tooltip) {
106
+ timer = setTimeout(() => {
107
+ tooltip && tooltip();
108
+ tooltip = null;
109
+ }, delay);
70
110
  }
71
- });
111
+ }
112
+
113
+ function addEventListener(trigger: string, fn: Function) {
114
+ const [ event, delayString ] = trigger.split(':');
115
+
116
+ target.addEventListener(event, () => fn(Number(delayString || 0)));
117
+ }
118
+
119
+ (target.getAttribute(`${prefix}-trigger-open`)?.split(',') || options.triggers.open)
120
+ .map(trigger => addEventListener(trigger, open));
121
+
122
+ (target.getAttribute(`${prefix}-trigger-close`)?.split(',') || options.triggers.close)
123
+ .map(trigger => addEventListener(trigger, close));
124
+ }
125
+
126
+ app.mixin({
127
+ mounted() {
128
+ if(!options.progressiveEnhancement) {
129
+ return;
130
+ }
131
+
132
+ let el = this.$el;
133
+
134
+ if(this.$el instanceof Text) {
135
+ el = this.$el.parentNode;
136
+ }
137
+
138
+ if(el instanceof HTMLElement) {
139
+ init(el);
140
+ }
141
+
142
+ // Create the tree walker.
143
+ const walker = document.createTreeWalker(
144
+ el,
145
+ NodeFilter.SHOW_ALL,
146
+ (node: Node) => {
147
+ if(!(node instanceof Element)) {
148
+ return NodeFilter.FILTER_REJECT;
149
+ }
150
+
151
+ return NodeFilter.FILTER_ACCEPT;
152
+ }
153
+ );
154
+
155
+ // Step through and alert all child nodes
156
+ while(walker.nextNode()) {
157
+ if(walker.currentNode instanceof Element) {
158
+ init(<Element> walker.currentNode);
159
+ }
160
+ }
161
+
162
+ const observer = new MutationObserver((changes) => {
163
+ for(const { removedNodes } of changes) {
164
+ for(const node of removedNodes) {
165
+ for(const el of (node as Element).querySelectorAll(`[${prefix}-id]`)) {
166
+ const tooltip = tooltips.get(
167
+ el.getAttribute(`${prefix}-id`) as string
168
+ );
169
+
170
+ tooltip && tooltip();
171
+ }
172
+ }
173
+ }
174
+ });
175
+
176
+ observer.observe(el, { childList: true });
177
+ }
178
+ });
179
+
180
+ app.directive('tooltip', {
181
+ beforeMount(target, binding) {
182
+ init(target, Object.assign({}, binding.modifiers, binding.value));
183
+ },
184
+ beforeUnmount(target) {
185
+ const id = target.getAttribute(`${prefix}-id`);
186
+ const tooltip = tooltips.get(id);
187
+
188
+ tooltip && tooltip();
189
+ }
72
190
  });
73
191
  }
@@ -8,11 +8,11 @@ module.exports = plugin(function({ addComponents, theme }) {
8
8
  },
9
9
 
10
10
  '.bs-tooltip-top .tooltip-arrow': {
11
- bottom: 0,
11
+ bottom: '1px',
12
12
  },
13
13
 
14
14
  '.bs-tooltip-top .tooltip-arrow::before': {
15
- top: 'calc(100% - 2px)',
15
+ bottom: '1px',
16
16
  borderWidth: `${theme('tooltip.arrow.height')} calc(${theme('tooltip.arrow.width')} / 2) 0`,
17
17
  borderTopColor: theme('tooltip.arrow.color'),
18
18
  }
@@ -24,11 +24,11 @@ module.exports = plugin(function({ addComponents, theme }) {
24
24
  },
25
25
 
26
26
  '.bs-tooltip-bottom .tooltip-arrow': {
27
- top: 0
27
+ top: '1px'
28
28
  },
29
29
 
30
30
  '.bs-tooltip-bottom .tooltip-arrow::before': {
31
- bottom: 'calc(100% - 2px)',
31
+ top: '1px',
32
32
  borderWidth: `0 calc(${theme('tooltip.arrow.width')} / 2) ${theme('tooltip.arrow.height')}`,
33
33
  borderBottomColor: theme('tooltip.arrow.color'),
34
34
  },
@@ -40,13 +40,13 @@ module.exports = plugin(function({ addComponents, theme }) {
40
40
  },
41
41
 
42
42
  '.bs-tooltip-left .tooltip-arrow': {
43
- right: 0,
43
+ right: '1px',
44
44
  width: theme('tooltip.arrow.height'),
45
45
  height: theme('tooltip.arrow.width')
46
46
  },
47
47
 
48
48
  '.bs-tooltip-left .tooltip-arrow::before': {
49
- left: 'calc(100% - 2px)',
49
+ right: '1px',
50
50
  borderWidth: `calc(${theme('tooltip.arrow.width')} / 2) 0 calc(${theme('tooltip.arrow.width')} / 2) ${theme('tooltip.arrow.height')}`,
51
51
  borderLeftColor: theme('tooltip.arrow.color')
52
52
  }
@@ -58,13 +58,13 @@ module.exports = plugin(function({ addComponents, theme }) {
58
58
  },
59
59
 
60
60
  '.bs-tooltip-right .tooltip-arrow': {
61
- left: 0,
61
+ left: '1px',
62
62
  width: theme('tooltip.arrow.height'),
63
63
  height: theme('tooltip.arrow.width'),
64
64
  },
65
65
 
66
66
  '.bs-tooltip-right .tooltip-arrow::before': {
67
- right: 'calc(100% - 2px)',
67
+ left: '1px',
68
68
  borderWidth: `calc(${theme('tooltip.arrow.width')} / 2) ${theme('tooltip.arrow.height')} calc(${theme('tooltip.arrow.width')} / 2) 0`,
69
69
  borderRightColor: theme('tooltip.arrow.color'),
70
70
  }
@@ -0,0 +1,12 @@
1
+ const plugin = require('tailwindcss/plugin');
2
+ const { colors } = require('tailwindcss/defaultTheme');
3
+
4
+ module.exports = () => [
5
+ 'tooltip',
6
+ 'tooltip-arrow',
7
+ 'tooltip-inner',
8
+ 'bs-tooltip-top',
9
+ 'bs-tooltip-left',
10
+ 'bs-tooltip-right',
11
+ 'bs-tooltip-bottom',
12
+ ]
@@ -1,4 +0,0 @@
1
- declare const _sfc_main: {
2
- mixins: any[];
3
- };
4
- export default _sfc_main;
@@ -1,2 +0,0 @@
1
- import type { App } from 'vue';
2
- export default function (app: App, options?: {}): void;