zero-tooltip 1.0.2 → 1.0.4

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.ts CHANGED
@@ -1,11 +1,13 @@
1
- import { Directive } from "vue"
1
+ import { Directive, watch } from "vue"
2
2
  import TooltipConfig from "./types/tooltipConfig"
3
3
  import TooltipPosition from "./types/tooltipPosition"
4
4
  import TooltipPositions from "./types/tooltipPositions"
5
- import useHideOnScroll from './composables/useHideOnScroll'
6
5
  import TooltipLocalConfig from "./types/tooltipLocalConfig"
6
+ import useHideOnScroll from './composables/useHideOnScroll'
7
+ import useHideOnResize from "./composables/useHideOnResize"
7
8
 
8
9
  const { handleHideOnScroll } = useHideOnScroll()
10
+ const { handleHideOnResize, resetResizeReferences } = useHideOnResize()
9
11
 
10
12
  const tooltipElementClass = 'zero-tooltip__container'
11
13
  const textElementClass = 'zero-tooltip__text'
@@ -20,7 +22,7 @@ const defaultTooltipPositions: TooltipPositions = {
20
22
  bottom: ['bottom', 'top', 'right', 'left'],
21
23
  }
22
24
 
23
- let defaultTooltipPosition: TooltipPosition = 'top'
25
+ const defaultTooltipPosition: TooltipPosition = 'top'
24
26
  const defaultTooltipOffsetFromSource = 10
25
27
  const defaultTooltipOffsetFromViewport = 20
26
28
  const defaultTooltipMinWidth = 100
@@ -31,357 +33,437 @@ const defaultTextClasses = 'zt-text-sm zt-text-white zt-whitespace-pre-wrap zt-b
31
33
  const defaultArrowSize = 5
32
34
  const defaultArrowClasses = 'zt-absolute zt-border-solid zt-border-[#495057]'
33
35
  const defaultMinArrowOffsetFromTooltipCorner = 6
36
+ const defaultZIndex = 1
37
+ const defaultShouldShow = true
38
+
39
+ // Tooltip config
40
+ let tooltipText: string
41
+ let tooltipPosition: TooltipPosition
42
+ let tooltipPositions: TooltipPositions
43
+ let tooltipOffsetFromSource: number
44
+ let tooltipOffsetFromViewport: number
45
+ let tooltipMinWidth: number
46
+ let tooltipMaxWidth: number
47
+ let tooltipBorderWidth: number
48
+ let tooltipClasses: string
49
+ let textClasses: string
50
+ let arrowSize: number
51
+ let arrowClasses: string
52
+ let arrowMinOffsetFromTooltipCorner: number
53
+ let zIndex: number
54
+ let shouldShow: boolean
55
+
56
+ // Tooltip elements
57
+ let anchorElement: HTMLElement
58
+ let tooltipTextElement: HTMLElement
59
+ let tooltipElement: HTMLElement
60
+
61
+ let isHovered = false
62
+
63
+ const ZeroTooltip = (globalConfig?: TooltipConfig): Directive => {
64
+ return {
65
+ mounted: (targetElement: HTMLElement, binding) => {
66
+ setTooltipConfig(binding.value, globalConfig, binding.arg as TooltipPosition)
67
+ initTooltip(targetElement)
68
+
69
+ if (typeof(binding.value) !== 'string') {
70
+ watch(binding.value, (newBindingValue) => {
71
+ setTooltipConfig(newBindingValue as string | TooltipLocalConfig, globalConfig, binding.arg as TooltipPosition)
72
+ initTooltip(targetElement)
73
+ })
74
+ }
75
+ },
34
76
 
35
- const ZeroTooltip = (config?: TooltipConfig): Directive => {
36
- if (config?.defaultPosition) {
37
- defaultTooltipPosition = config.defaultPosition
77
+ updated: (targetElement: HTMLElement, binding) => {
78
+ if (typeof(binding.value) === 'string') {
79
+ setTooltipConfig(binding.value, globalConfig, binding.arg as TooltipPosition)
80
+ initTooltip(targetElement)
81
+ }
82
+ }
38
83
  }
84
+ }
39
85
 
40
- // Get Tooltip config
41
- let tooltipPositions: TooltipPositions = {
42
- left: config?.positions?.left ?? defaultTooltipPositions.left,
43
- top: config?.positions?.top ?? defaultTooltipPositions.top,
44
- right: config?.positions?.right ?? defaultTooltipPositions.right,
45
- bottom: config?.positions?.bottom ?? defaultTooltipPositions.bottom,
86
+ function setTooltipConfig(localConfig: string | TooltipLocalConfig, globalConfig?: TooltipConfig, position?: TooltipPosition) {
87
+ tooltipText = getTooltipText(localConfig)
88
+
89
+ if (typeof(localConfig) !== 'string') {
90
+ tooltipPosition = position ?? localConfig.defaultPosition ?? globalConfig?.defaultPosition ?? defaultTooltipPosition;
91
+ tooltipPositions = {
92
+ left: localConfig.positions?.left ?? globalConfig?.positions?.left ?? defaultTooltipPositions.left,
93
+ top: localConfig.positions?.top ?? globalConfig?.positions?.top ?? defaultTooltipPositions.top,
94
+ right: localConfig.positions?.right ?? globalConfig?.positions?.right ?? defaultTooltipPositions.right,
95
+ bottom: localConfig.positions?.bottom ?? globalConfig?.positions?.bottom ?? defaultTooltipPositions.bottom,
96
+ }
97
+ tooltipOffsetFromSource = localConfig.offsetFromSource ?? globalConfig?.offsetFromSource ?? defaultTooltipOffsetFromSource
98
+ tooltipOffsetFromViewport = localConfig.offsetFromViewport ?? globalConfig?.offsetFromViewport ?? defaultTooltipOffsetFromViewport
99
+ tooltipMinWidth = localConfig.minWidth ?? globalConfig?.minWidth ?? defaultTooltipMinWidth
100
+ tooltipMaxWidth = localConfig.maxWidth ?? globalConfig?.maxWidth ?? defaultTooltipMaxWidth
101
+ tooltipBorderWidth = localConfig.tooltipBorderWidth ?? globalConfig?.tooltipBorderWidth ?? defaultTooltipBorderWidth
102
+ tooltipClasses = tooltipElementClass + ' ' + defaultTooltipClasses + ' ' + (localConfig.tooltipClasses ?? globalConfig?.tooltipClasses ?? '')
103
+ textClasses = textElementClass + ' ' + defaultTextClasses + ' ' + (localConfig.textClasses ?? globalConfig?.textClasses ?? '')
104
+ arrowSize = localConfig.arrowSize ?? globalConfig?.arrowSize ?? defaultArrowSize
105
+ arrowClasses = localConfig.arrowClasses ?? globalConfig?.arrowClasses ?? ''
106
+ arrowMinOffsetFromTooltipCorner = localConfig.arrowMinOffsetFromTooltipCorner ?? globalConfig?.arrowMinOffsetFromTooltipCorner ?? defaultMinArrowOffsetFromTooltipCorner
107
+ zIndex = localConfig.zIndex ?? globalConfig?.zIndex ?? defaultZIndex
108
+ shouldShow = localConfig.show ?? defaultShouldShow
46
109
  }
47
- let tooltipOffsetFromSource = config?.offsetFromSource ?? defaultTooltipOffsetFromSource
48
- let tooltipOffsetFromViewport = config?.offsetFromViewport ?? defaultTooltipOffsetFromViewport
49
- let tooltipMinWidth = config?.minWidth ?? defaultTooltipMinWidth
50
- let tooltipMaxWidth = config?.maxWidth ?? defaultTooltipMaxWidth
51
- let tooltipBorderWidth = config?.tooltipBorderWidth ?? defaultTooltipBorderWidth
52
- let tooltipClasses = tooltipElementClass + ' ' + defaultTooltipClasses + ' ' + config?.tooltipClasses ?? ''
53
- let textClasses = textElementClass + ' ' + defaultTextClasses + ' ' + config?.textClasses ?? ''
54
- let arrowSize = config?.arrowSize ?? defaultArrowSize
55
- let arrowMinOffsetFromTooltipCorner = config?.arrowMinOffsetFromTooltipCorner ?? defaultMinArrowOffsetFromTooltipCorner
56
110
 
57
- return {
58
- mounted: (anchorElement: HTMLElement, binding) => {
59
- // Get Tooltip position and text
60
- let tooltipPosition: TooltipPosition = (binding.arg ?? defaultTooltipPosition) as TooltipPosition
111
+ if (tooltipPosition === undefined) tooltipPosition = position ?? globalConfig?.defaultPosition ?? defaultTooltipPosition;
112
+ if (tooltipPositions === undefined) tooltipPositions = {
113
+ left: globalConfig?.positions?.left ?? defaultTooltipPositions.left,
114
+ top: globalConfig?.positions?.top ?? defaultTooltipPositions.top,
115
+ right: globalConfig?.positions?.right ?? defaultTooltipPositions.right,
116
+ bottom: globalConfig?.positions?.bottom ?? defaultTooltipPositions.bottom,
117
+ }
118
+ if (tooltipOffsetFromSource === undefined) tooltipOffsetFromSource = globalConfig?.offsetFromSource ?? defaultTooltipOffsetFromSource
119
+ if (tooltipOffsetFromViewport === undefined) tooltipOffsetFromViewport = globalConfig?.offsetFromViewport ?? defaultTooltipOffsetFromViewport
120
+ if (tooltipMinWidth === undefined) tooltipMinWidth = globalConfig?.minWidth ?? defaultTooltipMinWidth
121
+ if (tooltipMaxWidth === undefined) tooltipMaxWidth = globalConfig?.maxWidth ?? defaultTooltipMaxWidth
122
+ if (tooltipBorderWidth === undefined) tooltipBorderWidth = globalConfig?.tooltipBorderWidth ?? defaultTooltipBorderWidth
123
+ if (tooltipClasses === undefined) tooltipClasses = tooltipElementClass + ' ' + defaultTooltipClasses + ' ' + globalConfig?.tooltipClasses ?? ''
124
+ if (textClasses === undefined) textClasses = textElementClass + ' ' + defaultTextClasses + ' ' + globalConfig?.textClasses ?? ''
125
+ if (arrowSize === undefined) arrowSize = globalConfig?.arrowSize ?? defaultArrowSize
126
+ if (arrowClasses === undefined) arrowClasses = globalConfig?.arrowClasses ?? ''
127
+ if (arrowMinOffsetFromTooltipCorner === undefined) arrowMinOffsetFromTooltipCorner = globalConfig?.arrowMinOffsetFromTooltipCorner ?? defaultMinArrowOffsetFromTooltipCorner
128
+ if (zIndex === undefined) zIndex = globalConfig?.zIndex ?? defaultZIndex
129
+ if (shouldShow === undefined) shouldShow = defaultShouldShow
130
+ }
61
131
 
62
- if (typeof(binding.value) !== 'string') adjustTooltipSettings(binding.value)
132
+ function getTooltipText(localConfig: string | TooltipLocalConfig) {
133
+ const tooltipText = typeof(localConfig) === 'string' ? localConfig : localConfig.content
63
134
 
64
- const text: string = getTooltipText(binding.value)
65
-
66
- // Create Text element
67
- const textElement = document.createElement('p')
68
- textElement.classList.add(...textClasses.split(' '))
69
- textElement.innerHTML = text
70
-
71
- // Create Tooltip element
72
- const tooltipElement = document.createElement('div')
73
- tooltipElement.classList.add(...tooltipClasses.split(' '))
74
- tooltipElement.style.borderWidth = `${tooltipBorderWidth}px`
75
- tooltipElement.appendChild(textElement)
76
-
77
- // Add listener for showing Tooltip element
78
- anchorElement.addEventListener('mouseenter', () => {
79
- const anchorElementRect = anchorElement.getBoundingClientRect()
80
-
81
- // Mount Tooltip element to body
82
- const body = document.querySelector('body')
83
- body?.appendChild(tooltipElement)
84
-
85
- // Find suitable Tooltip position
86
- let hasNeededDisplaySpace = false
87
- let currentTooltipPosition = tooltipPosition
88
- for (let i = 0; i < 4; i++) {
89
- currentTooltipPosition = tooltipPositions[tooltipPosition][i]
90
-
91
- if (currentTooltipPosition === 'left') {
92
- hasNeededDisplaySpace = tryMountTooltipOnLeft(anchorElementRect)
93
- } else if (currentTooltipPosition === 'top') {
94
- hasNeededDisplaySpace = tryMountTooltipOnTop(anchorElementRect)
95
- } else if (currentTooltipPosition === 'right') {
96
- hasNeededDisplaySpace = tryMountTooltipOnRight(anchorElementRect)
97
- } else if (currentTooltipPosition === 'bottom') {
98
- hasNeededDisplaySpace = tryMountTooltipOnBottom(anchorElementRect)
99
- }
100
-
101
- if (hasNeededDisplaySpace) break
102
- }
103
-
104
- if (hasNeededDisplaySpace) {
105
- drawArrow(anchorElementRect, currentTooltipPosition)
106
-
107
- tooltipElement.style.opacity = '1'
108
- handleHideOnScroll(anchorElement, () => hideTooltip())
109
- }
110
- })
111
-
112
- // Add listener for hiding Tooltip element
113
- anchorElement.addEventListener('mouseleave', () => hideTooltip())
114
-
115
- // --- Helper functions (placed here because of variables scopes are local (don't wan to use a lot of parameters)) --- //
116
- function adjustTooltipSettings(bindingValue: TooltipLocalConfig) {
117
- if (bindingValue.defaultPosition) tooltipPosition = bindingValue.defaultPosition
118
- if (bindingValue.positions) tooltipPositions = {...tooltipPositions, ...bindingValue.positions}
119
- if (bindingValue.offsetFromSource) tooltipOffsetFromSource = bindingValue.offsetFromSource
120
- if (bindingValue.offsetFromViewport) tooltipOffsetFromViewport = bindingValue.offsetFromViewport
121
- if (bindingValue.minWidth) tooltipMinWidth = bindingValue.minWidth
122
- if (bindingValue.maxWidth) tooltipMaxWidth = bindingValue.maxWidth
123
- if (bindingValue.tooltipBorderWidth) tooltipBorderWidth = bindingValue.tooltipBorderWidth
124
- if (bindingValue.tooltipClasses) tooltipClasses = bindingValue.tooltipClasses
125
- if (bindingValue.textClasses) textClasses = bindingValue.textClasses
126
- if (bindingValue.arrowSize) arrowSize = bindingValue.arrowSize
127
- if (bindingValue.arrowMinOffsetFromTooltipCorner) arrowMinOffsetFromTooltipCorner = bindingValue.arrowMinOffsetFromTooltipCorner
128
- }
135
+ if (!tooltipText) {
136
+ throw new Error("Please enter valid tooltip value");
137
+ }
129
138
 
130
- function tryMountTooltipOnLeft(anchorElementRect: DOMRect) {
131
- // Check if Tooltip has enough available horizontal space, top and bottom offset from viewport
132
- const tooltipAvailableMaxWidth = Math.min(anchorElementRect.left - tooltipOffsetFromSource - tooltipOffsetFromViewport, tooltipMaxWidth)
133
- const isAnchorElementTopLowerThanOffsetFromViewport = anchorElementRect.top >= tooltipOffsetFromViewport
134
- const isAnchorElementBottomHigherThanOffsetFromViewport = (window.innerHeight - anchorElementRect.bottom) >= tooltipOffsetFromViewport
139
+ return tooltipText
140
+ }
135
141
 
136
- if (tooltipAvailableMaxWidth < tooltipMinWidth || !isAnchorElementTopLowerThanOffsetFromViewport || !isAnchorElementBottomHigherThanOffsetFromViewport) return false
142
+ function initTooltip(targetElement: HTMLElement) {
143
+ anchorElement = targetElement
144
+ anchorElement.removeEventListener('mouseenter', onMouseEnter)
145
+ anchorElement.removeEventListener('mouseleave', onMouseLeave)
137
146
 
138
- // Set Tooltip maxWidth
139
- tooltipElement.style.maxWidth = `${tooltipAvailableMaxWidth}px`
147
+ createTextElement()
148
+ createTooltipElement()
140
149
 
141
- // Calculate Tooltip position
142
- const tooltipElementRect = tooltipElement.getBoundingClientRect()
143
- let tooltipTop = anchorElementRect.top + (anchorElementRect.height / 2) - (tooltipElementRect.height / 2)
144
-
145
- if (tooltipTop < tooltipOffsetFromViewport) {
146
- tooltipTop = tooltipOffsetFromViewport
147
- } else if (tooltipTop + tooltipElementRect.height > window.innerHeight - tooltipOffsetFromViewport) {
148
- tooltipTop = window.innerHeight - tooltipOffsetFromViewport - tooltipElementRect.height
149
- }
150
+ anchorElement.addEventListener('mouseenter', onMouseEnter)
151
+ anchorElement.addEventListener('mouseleave', onMouseLeave)
150
152
 
151
- const tooltipLeft = anchorElementRect.left - tooltipOffsetFromSource - tooltipElementRect.width
153
+ if (isHovered) {
154
+ anchorElement.dispatchEvent(new Event('mouseleave'))
155
+ anchorElement.dispatchEvent(new Event('mouseenter'))
156
+ }
157
+ }
152
158
 
153
- // Check if anchor element is directly on right of Tooltip
154
- if (anchorElementRect.bottom < tooltipTop + arrowMinOffsetFromTooltipCorner * 2
155
- || anchorElementRect.top > tooltipTop + tooltipElementRect.height - arrowMinOffsetFromTooltipCorner * 2) return false
159
+ function createTextElement() {
160
+ tooltipTextElement = document.createElement('p')
161
+ tooltipTextElement.classList.add(...textClasses.trim().split(' '))
162
+ tooltipTextElement.innerHTML = tooltipText
163
+ }
156
164
 
157
- // Set Tooltip position
158
- tooltipElement.style.top = `${tooltipTop}px`
159
- tooltipElement.style.left = `${tooltipLeft}px`
165
+ function createTooltipElement() {
166
+ tooltipElement = document.createElement('div')
167
+ tooltipElement.classList.add(...tooltipClasses.trim().split(' '))
168
+ tooltipElement.style.borderWidth = `${tooltipBorderWidth}px`
169
+ tooltipElement.appendChild(tooltipTextElement)
170
+ }
160
171
 
161
- return true
162
- }
172
+ function onMouseEnter() {
173
+ isHovered = true
163
174
 
164
- function tryMountTooltipOnRight(anchorElementRect: DOMRect) {
165
- // Check if Tooltip has enough available horizontal space, top and bottom offset from viewport
166
- const tooltipAvailableMaxWidth = Math.min(window.innerWidth - (anchorElementRect.right + tooltipOffsetFromSource) - tooltipOffsetFromViewport, tooltipMaxWidth)
167
- const isAnchorElementTopLowerThanOffsetFromViewport = anchorElementRect.top >= tooltipOffsetFromViewport
168
- const isAnchorElementBottomHigherThanOffsetFromViewport = (window.innerHeight - anchorElementRect.bottom) >= tooltipOffsetFromViewport
175
+ if (!shouldShow) return
169
176
 
170
- if (tooltipAvailableMaxWidth < tooltipMinWidth || !isAnchorElementTopLowerThanOffsetFromViewport || !isAnchorElementBottomHigherThanOffsetFromViewport) return false
171
-
172
- // Set tooltip maxWidth
173
- tooltipElement.style.maxWidth = `${tooltipAvailableMaxWidth}px`
177
+ const anchorElementRect = anchorElement.getBoundingClientRect()
174
178
 
175
- // Calculate Tooltip position
176
- const tooltipElementRect = tooltipElement.getBoundingClientRect()
179
+ // Mount Tooltip element to body
180
+ const body = document.querySelector('body')
181
+ body?.appendChild(tooltipElement)
177
182
 
178
- let tooltipTop = anchorElementRect.top + (anchorElementRect.height / 2) - (tooltipElementRect.height / 2)
179
-
180
- if (tooltipTop < tooltipOffsetFromViewport) {
181
- tooltipTop = tooltipOffsetFromViewport
182
- } else if (tooltipTop + tooltipElementRect.height > window.innerHeight - tooltipOffsetFromViewport) {
183
- tooltipTop = window.innerHeight - tooltipOffsetFromViewport - tooltipElementRect.height
184
- }
183
+ // Find suitable Tooltip position
184
+ let hasNeededDisplaySpace = false
185
+ let currentTooltipPosition = tooltipPosition
186
+ for (let i = 0; i < 4; i++) {
187
+ currentTooltipPosition = tooltipPositions[tooltipPosition][i]
185
188
 
186
- const tooltipLeft = anchorElementRect.right + tooltipOffsetFromSource
189
+ if (currentTooltipPosition === 'left') {
190
+ hasNeededDisplaySpace = tryMountTooltipOnLeft(anchorElementRect)
191
+ } else if (currentTooltipPosition === 'top') {
192
+ hasNeededDisplaySpace = tryMountTooltipOnTop(anchorElementRect)
193
+ } else if (currentTooltipPosition === 'right') {
194
+ hasNeededDisplaySpace = tryMountTooltipOnRight(anchorElementRect)
195
+ } else if (currentTooltipPosition === 'bottom') {
196
+ hasNeededDisplaySpace = tryMountTooltipOnBottom(anchorElementRect)
197
+ }
187
198
 
188
- // Check if anchor element is directly on left of Tooltip
189
- if (anchorElementRect.bottom < tooltipTop + arrowMinOffsetFromTooltipCorner * 2
190
- || anchorElementRect.top > tooltipTop + tooltipElementRect.height - arrowMinOffsetFromTooltipCorner * 2) return false
199
+ if (hasNeededDisplaySpace) break
200
+ }
191
201
 
192
- // Set Tooltip position
193
- tooltipElement.style.top = `${tooltipTop}px`
194
- tooltipElement.style.left = `${tooltipLeft}px`
202
+ if (hasNeededDisplaySpace) {
203
+ drawArrow(anchorElementRect, currentTooltipPosition)
195
204
 
196
- return true
197
- }
205
+ tooltipElement.style.opacity = '1'
206
+ tooltipElement.style.zIndex = zIndex.toString()
198
207
 
199
- function tryMountTooltipOnTop(anchorElementRect: DOMRect) {
200
- // Calculate and set Tooltip width
201
- const tooltipAvailableMaxWidth = Math.min(window.innerWidth - (tooltipOffsetFromViewport * 2), tooltipMaxWidth)
202
- tooltipElement.style.maxWidth = `${tooltipAvailableMaxWidth}px`
208
+ handleHideOnScroll(anchorElement, () => hideTooltip())
209
+ handleHideOnResize(anchorElement, () => hideTooltip())
210
+ }
211
+ }
203
212
 
204
- // Calculate Tooltip top position
205
- const tooltipElementRect = tooltipElement.getBoundingClientRect()
206
- let tooltipTop = anchorElementRect.top - tooltipOffsetFromSource - tooltipElementRect.height
213
+ function onMouseLeave() {
214
+ hideTooltip()
215
+ }
207
216
 
208
- // Check if Tooltip has enough available on top
209
- if (tooltipTop < tooltipOffsetFromViewport) return false
217
+ function tryMountTooltipOnLeft(anchorElementRect: DOMRect) {
218
+ // Check if Tooltip has enough available horizontal space, top and bottom offset from viewport
219
+ const tooltipAvailableMaxWidth = Math.min(anchorElementRect.left - tooltipOffsetFromSource - tooltipOffsetFromViewport, tooltipMaxWidth)
220
+ const isAnchorElementTopLowerThanOffsetFromViewport = anchorElementRect.top >= tooltipOffsetFromViewport
221
+ const isAnchorElementBottomHigherThanOffsetFromViewport = (window.innerHeight - anchorElementRect.bottom) >= tooltipOffsetFromViewport
210
222
 
211
- // Calculate Tooltip left position
212
- let tooltipLeft = anchorElementRect.left + (anchorElementRect.width / 2) - (tooltipElementRect.width / 2)
223
+ if (tooltipAvailableMaxWidth < tooltipMinWidth || !isAnchorElementTopLowerThanOffsetFromViewport || !isAnchorElementBottomHigherThanOffsetFromViewport) return false
213
224
 
214
- if (tooltipLeft < tooltipOffsetFromViewport) {
215
- tooltipLeft = tooltipOffsetFromViewport
216
- } else if (tooltipLeft + tooltipElementRect.width > window.innerWidth - tooltipOffsetFromViewport) {
217
- tooltipLeft = window.innerWidth - tooltipOffsetFromViewport - tooltipElementRect.width
218
- }
225
+ // Set Tooltip maxWidth
226
+ tooltipElement.style.maxWidth = `${tooltipAvailableMaxWidth}px`
219
227
 
220
- // Check if anchor element is directly on below of Tooltip
221
- if (anchorElementRect.left > tooltipLeft + tooltipElementRect.width - arrowMinOffsetFromTooltipCorner * 2
222
- || anchorElementRect.right < tooltipLeft + arrowMinOffsetFromTooltipCorner * 2) return false
228
+ // Calculate Tooltip position
229
+ const tooltipElementRect = tooltipElement.getBoundingClientRect()
230
+ let tooltipTop = anchorElementRect.top + (anchorElementRect.height / 2) - (tooltipElementRect.height / 2)
231
+
232
+ if (tooltipTop < tooltipOffsetFromViewport) {
233
+ tooltipTop = tooltipOffsetFromViewport
234
+ } else if (tooltipTop + tooltipElementRect.height > window.innerHeight - tooltipOffsetFromViewport) {
235
+ tooltipTop = window.innerHeight - tooltipOffsetFromViewport - tooltipElementRect.height
236
+ }
223
237
 
224
- // Set Tooltip position
225
- tooltipElement.style.top = `${tooltipTop}px`
226
- tooltipElement.style.left = `${tooltipLeft}px`
238
+ const tooltipLeft = anchorElementRect.left - tooltipOffsetFromSource - tooltipElementRect.width
227
239
 
228
- return true
229
- }
240
+ // Check if anchor element is directly on right of Tooltip
241
+ if (anchorElementRect.bottom < tooltipTop + arrowMinOffsetFromTooltipCorner * 2
242
+ || anchorElementRect.top > tooltipTop + tooltipElementRect.height - arrowMinOffsetFromTooltipCorner * 2) return false
230
243
 
231
- function tryMountTooltipOnBottom(anchorElementRect: DOMRect) {
232
- // Calculate and set Tooltip width
233
- const tooltipAvailableMaxWidth = Math.min(window.innerWidth - (tooltipOffsetFromViewport * 2), tooltipMaxWidth)
234
- tooltipElement.style.maxWidth = `${tooltipAvailableMaxWidth}px`
244
+ // Set Tooltip position
245
+ tooltipElement.style.top = `${tooltipTop}px`
246
+ tooltipElement.style.left = `${tooltipLeft}px`
235
247
 
236
- // Calculate Tooltip top position
237
- const tooltipElementRect = tooltipElement.getBoundingClientRect()
238
- let tooltipTop = anchorElementRect.bottom + tooltipOffsetFromSource
248
+ return true
249
+ }
239
250
 
240
- // Check if Tooltip has enough available on bottom
241
- if (tooltipTop + tooltipElementRect.height > window.innerHeight - tooltipOffsetFromViewport) return false
242
-
243
- // Calculate Tooltip left position
244
- let tooltipLeft = anchorElementRect.left + (anchorElementRect.width / 2) - (tooltipElementRect.width / 2)
251
+ function tryMountTooltipOnRight(anchorElementRect: DOMRect) {
252
+ // Check if Tooltip has enough available horizontal space, top and bottom offset from viewport
253
+ const tooltipAvailableMaxWidth = Math.min(window.innerWidth - (anchorElementRect.right + tooltipOffsetFromSource) - tooltipOffsetFromViewport, tooltipMaxWidth)
254
+ const isAnchorElementTopLowerThanOffsetFromViewport = anchorElementRect.top >= tooltipOffsetFromViewport
255
+ const isAnchorElementBottomHigherThanOffsetFromViewport = (window.innerHeight - anchorElementRect.bottom) >= tooltipOffsetFromViewport
245
256
 
246
- if (tooltipLeft < tooltipOffsetFromViewport) {
247
- tooltipLeft = tooltipOffsetFromViewport
248
- } else if (tooltipLeft + tooltipElementRect.width > window.innerWidth - tooltipOffsetFromViewport) {
249
- tooltipLeft = window.innerWidth - tooltipOffsetFromViewport - tooltipElementRect.width
250
- }
257
+ if (tooltipAvailableMaxWidth < tooltipMinWidth || !isAnchorElementTopLowerThanOffsetFromViewport || !isAnchorElementBottomHigherThanOffsetFromViewport) return false
258
+
259
+ // Set tooltip maxWidth
260
+ tooltipElement.style.maxWidth = `${tooltipAvailableMaxWidth}px`
251
261
 
252
- // Check if anchor element is directly on top of Tooltip
253
- if (anchorElementRect.left > tooltipLeft + tooltipElementRect.width - arrowMinOffsetFromTooltipCorner * 2
254
- || anchorElementRect.right < tooltipLeft + arrowMinOffsetFromTooltipCorner * 2) return false
262
+ // Calculate Tooltip position
263
+ const tooltipElementRect = tooltipElement.getBoundingClientRect()
255
264
 
256
- // Set Tooltip position
257
- tooltipElement.style.top = `${tooltipTop}px`
258
- tooltipElement.style.left = `${tooltipLeft}px`
265
+ let tooltipTop = anchorElementRect.top + (anchorElementRect.height / 2) - (tooltipElementRect.height / 2)
266
+
267
+ if (tooltipTop < tooltipOffsetFromViewport) {
268
+ tooltipTop = tooltipOffsetFromViewport
269
+ } else if (tooltipTop + tooltipElementRect.height > window.innerHeight - tooltipOffsetFromViewport) {
270
+ tooltipTop = window.innerHeight - tooltipOffsetFromViewport - tooltipElementRect.height
271
+ }
259
272
 
260
- return true
261
- }
273
+ const tooltipLeft = anchorElementRect.right + tooltipOffsetFromSource
262
274
 
263
- function drawArrow(anchorElementRect: DOMRect, currentTooltipPosition: TooltipPosition) {
264
- // Create Arrow element
265
- const arrowElement = document.createElement('div')
266
-
267
- // Calculate Arrow element size, positions and style/angle classes
268
- const tooltipElementRect = tooltipElement.getBoundingClientRect()
269
- const arrowHalfLengthOfLongSide = Math.sin(45 * (180 / Math.PI)) * arrowSize
270
-
271
- // Arrow top/left 0 is Tooltip top/left 0
272
- let arrowTop = 0
273
- let arrowLeft = 0
274
-
275
- let arrowClassForCorrectAngle = ''
276
-
277
- switch (currentTooltipPosition) {
278
- case "left":
279
- arrowClassForCorrectAngle = '!zt-border-y-transparent !zt-border-r-transparent'
280
- arrowTop = anchorElementRect.top - tooltipElementRect.top + (anchorElementRect.height / 2) - arrowHalfLengthOfLongSide - tooltipBorderWidth
281
- arrowLeft = tooltipElementRect.width - tooltipBorderWidth
282
- break;
283
- case "top":
284
- arrowClassForCorrectAngle = '!zt-border-x-transparent !zt-border-b-transparent'
285
- arrowTop = tooltipElementRect.height - tooltipBorderWidth
286
- arrowLeft = anchorElementRect.left - tooltipElementRect.left + (anchorElementRect.width / 2) - arrowHalfLengthOfLongSide - tooltipBorderWidth
287
- break;
288
- case "right":
289
- arrowClassForCorrectAngle = '!zt-border-y-transparent !zt-border-l-transparent'
290
- arrowTop = anchorElementRect.top - tooltipElementRect.top + (anchorElementRect.height / 2) - arrowHalfLengthOfLongSide - tooltipBorderWidth
291
- arrowLeft = (-arrowSize * 2) - tooltipBorderWidth
292
- break;
293
- case "bottom":
294
- arrowClassForCorrectAngle = '!zt-border-x-transparent !zt-border-t-transparent'
295
- arrowTop = (-arrowSize * 2) - tooltipBorderWidth
296
- arrowLeft = anchorElementRect.left - tooltipElementRect.left + (anchorElementRect.width / 2) - arrowHalfLengthOfLongSide - tooltipBorderWidth
297
- break;
298
- }
299
-
300
- if (currentTooltipPosition === 'left' || currentTooltipPosition === 'right') {
301
- if (!isArrowPositionWithinLimits(currentTooltipPosition, tooltipElementRect, arrowTop)) {
302
- arrowTop = getArrowPositionMinLimit(currentTooltipPosition, tooltipElementRect, arrowTop)
303
- }
304
- } else {
305
- if (!isArrowPositionWithinLimits(currentTooltipPosition, tooltipElementRect, arrowLeft)) {
306
- arrowLeft = getArrowPositionMinLimit(currentTooltipPosition, tooltipElementRect, arrowLeft)
307
- }
308
- }
309
-
310
- // Set Arrow element id, styling/angle
311
- const arrowClasses = arrowElementClass + ' ' + defaultArrowClasses + ' ' + arrowClassForCorrectAngle + ' ' + config?.arrowClasses ?? ''
312
- arrowElement.classList.add(...arrowClasses.split(' '))
313
-
314
- // Set Arrow element size and position
315
- arrowElement.style.top = `${arrowTop}px`
316
- arrowElement.style.left = `${arrowLeft}px`
317
- arrowElement.style.borderWidth = `${arrowSize}px`
318
-
319
- // Mount Arrow element
320
- document.querySelector(`.${tooltipElementClass}`)?.appendChild(arrowElement)
321
- }
275
+ // Check if anchor element is directly on left of Tooltip
276
+ if (anchorElementRect.bottom < tooltipTop + arrowMinOffsetFromTooltipCorner * 2
277
+ || anchorElementRect.top > tooltipTop + tooltipElementRect.height - arrowMinOffsetFromTooltipCorner * 2) return false
322
278
 
323
- function isArrowPositionWithinLimits(currentTooltipPosition: TooltipPosition, tooltipElementRect: DOMRect, arrowPosition: number) {
324
- switch (currentTooltipPosition) {
325
- case "left":
326
- case "right":
327
- return arrowPosition > arrowMinOffsetFromTooltipCorner - tooltipBorderWidth
328
- && arrowPosition < tooltipElementRect.height + tooltipBorderWidth - arrowMinOffsetFromTooltipCorner - (arrowSize * 2)
329
- case "top":
330
- case "bottom":
331
- return arrowPosition > arrowMinOffsetFromTooltipCorner - tooltipBorderWidth
332
- && arrowPosition < tooltipElementRect.width + tooltipBorderWidth - arrowMinOffsetFromTooltipCorner - (arrowSize * 2)
333
- }
334
- }
279
+ // Set Tooltip position
280
+ tooltipElement.style.top = `${tooltipTop}px`
281
+ tooltipElement.style.left = `${tooltipLeft}px`
335
282
 
336
- function getArrowPositionMinLimit(currentTooltipPosition: TooltipPosition, tooltipElementRect: DOMRect, arrowPosition: number) {
337
- switch (currentTooltipPosition) {
338
- case "left":
339
- case "right":
340
- if (arrowPosition < arrowMinOffsetFromTooltipCorner - tooltipBorderWidth) {
341
- // Arrow too close to viewport top
342
- return arrowMinOffsetFromTooltipCorner - tooltipBorderWidth
343
- } else {
344
- // Arrow too close to viewport bottom
345
- return tooltipElementRect.height - tooltipBorderWidth - arrowMinOffsetFromTooltipCorner - (arrowSize * 2)
346
- }
347
- case "top":
348
- case "bottom":
349
- if (arrowPosition < arrowMinOffsetFromTooltipCorner - tooltipBorderWidth) {
350
- // Arrow too close to viewport left
351
- return arrowMinOffsetFromTooltipCorner - tooltipBorderWidth
352
- } else {
353
- // Arrow too close to viewport right
354
- return tooltipElementRect.width - tooltipBorderWidth - arrowMinOffsetFromTooltipCorner - (arrowSize * 2)
355
- }
356
- }
357
- }
358
- },
359
- }
283
+ return true
360
284
  }
361
285
 
362
- function hideTooltip() {
363
- const tooltipElement = document.querySelector(`.${tooltipElementClass}`)
286
+ function tryMountTooltipOnTop(anchorElementRect: DOMRect) {
287
+ // Calculate and set Tooltip width
288
+ const tooltipAvailableMaxWidth = Math.min(window.innerWidth - (tooltipOffsetFromViewport * 2), tooltipMaxWidth)
289
+ tooltipElement.style.maxWidth = `${tooltipAvailableMaxWidth}px`
290
+
291
+ // Calculate Tooltip top position
292
+ const tooltipElementRect = tooltipElement.getBoundingClientRect()
293
+ let tooltipTop = anchorElementRect.top - tooltipOffsetFromSource - tooltipElementRect.height
294
+
295
+ // Check if Tooltip has enough available on top
296
+ if (tooltipTop < tooltipOffsetFromViewport) return false
364
297
 
365
- // Remove Arrow element from Tooltip, because it needs to be rebuilt every time Tooltip is showed again
366
- tooltipElement?.querySelector(`.${arrowElementClass}`)?.remove()
298
+ // Calculate Tooltip left position
299
+ let tooltipLeft = anchorElementRect.left + (anchorElementRect.width / 2) - (tooltipElementRect.width / 2)
367
300
 
368
- tooltipElement?.remove()
301
+ if (tooltipLeft < tooltipOffsetFromViewport) {
302
+ tooltipLeft = tooltipOffsetFromViewport
303
+ } else if (tooltipLeft + tooltipElementRect.width > window.innerWidth - tooltipOffsetFromViewport) {
304
+ tooltipLeft = window.innerWidth - tooltipOffsetFromViewport - tooltipElementRect.width
305
+ }
306
+
307
+ // Check if anchor element is directly on below of Tooltip
308
+ if (anchorElementRect.left > tooltipLeft + tooltipElementRect.width - arrowMinOffsetFromTooltipCorner * 2
309
+ || anchorElementRect.right < tooltipLeft + arrowMinOffsetFromTooltipCorner * 2) return false
310
+
311
+ // Set Tooltip position
312
+ tooltipElement.style.top = `${tooltipTop}px`
313
+ tooltipElement.style.left = `${tooltipLeft}px`
314
+
315
+ return true
369
316
  }
370
317
 
371
- function getTooltipText(bindingValue: string | TooltipLocalConfig) {
372
- let tooltipText = ''
318
+ function tryMountTooltipOnBottom(anchorElementRect: DOMRect) {
319
+ // Calculate and set Tooltip width
320
+ const tooltipAvailableMaxWidth = Math.min(window.innerWidth - (tooltipOffsetFromViewport * 2), tooltipMaxWidth)
321
+ tooltipElement.style.maxWidth = `${tooltipAvailableMaxWidth}px`
322
+
323
+ // Calculate Tooltip top position
324
+ const tooltipElementRect = tooltipElement.getBoundingClientRect()
325
+ let tooltipTop = anchorElementRect.bottom + tooltipOffsetFromSource
373
326
 
374
- if (typeof(bindingValue) === 'string') {
375
- tooltipText = bindingValue
327
+ // Check if Tooltip has enough available on bottom
328
+ if (tooltipTop + tooltipElementRect.height > window.innerHeight - tooltipOffsetFromViewport) return false
329
+
330
+ // Calculate Tooltip left position
331
+ let tooltipLeft = anchorElementRect.left + (anchorElementRect.width / 2) - (tooltipElementRect.width / 2)
332
+
333
+ if (tooltipLeft < tooltipOffsetFromViewport) {
334
+ tooltipLeft = tooltipOffsetFromViewport
335
+ } else if (tooltipLeft + tooltipElementRect.width > window.innerWidth - tooltipOffsetFromViewport) {
336
+ tooltipLeft = window.innerWidth - tooltipOffsetFromViewport - tooltipElementRect.width
337
+ }
338
+
339
+ // Check if anchor element is directly on top of Tooltip
340
+ if (anchorElementRect.left > tooltipLeft + tooltipElementRect.width - arrowMinOffsetFromTooltipCorner * 2
341
+ || anchorElementRect.right < tooltipLeft + arrowMinOffsetFromTooltipCorner * 2) return false
342
+
343
+ // Set Tooltip position
344
+ tooltipElement.style.top = `${tooltipTop}px`
345
+ tooltipElement.style.left = `${tooltipLeft}px`
346
+
347
+ return true
348
+ }
349
+
350
+ function drawArrow(anchorElementRect: DOMRect, currentTooltipPosition: TooltipPosition) {
351
+ // Create Arrow element
352
+ const arrowElement = document.createElement('div')
353
+
354
+ // Calculate Arrow element size, positions and style/angle classes
355
+ const tooltipElementRect = tooltipElement.getBoundingClientRect()
356
+ const arrowHalfLengthOfLongSide = Math.sin(45 * (180 / Math.PI)) * arrowSize
357
+
358
+ // Adjusts arrow position by `x` pixels to handle browsers sometimes not rendering border in it's full width, e.g., 4.8px instead of 5px
359
+ const arrowPositionAdjuster = 1;
360
+
361
+ // Arrow top/left 0 is Tooltip top/left 0
362
+ let arrowTop = 0
363
+ let arrowLeft = 0
364
+
365
+ let arrowClassForCorrectAngle = ''
366
+
367
+ switch (currentTooltipPosition) {
368
+ case "left":
369
+ arrowClassForCorrectAngle = '!zt-border-y-transparent !zt-border-r-transparent'
370
+ arrowTop = anchorElementRect.top - tooltipElementRect.top + (anchorElementRect.height / 2) - arrowHalfLengthOfLongSide - tooltipBorderWidth
371
+ arrowLeft = tooltipElementRect.width - tooltipBorderWidth - arrowPositionAdjuster
372
+ break;
373
+ case "top":
374
+ arrowClassForCorrectAngle = '!zt-border-x-transparent !zt-border-b-transparent'
375
+ arrowTop = tooltipElementRect.height - tooltipBorderWidth - arrowPositionAdjuster
376
+ arrowLeft = anchorElementRect.left - tooltipElementRect.left + (anchorElementRect.width / 2) - arrowHalfLengthOfLongSide - tooltipBorderWidth
377
+ break;
378
+ case "right":
379
+ arrowClassForCorrectAngle = '!zt-border-y-transparent !zt-border-l-transparent'
380
+ arrowTop = anchorElementRect.top - tooltipElementRect.top + (anchorElementRect.height / 2) - arrowHalfLengthOfLongSide - tooltipBorderWidth
381
+ arrowLeft = (-arrowSize * 2) - tooltipBorderWidth + arrowPositionAdjuster
382
+ break;
383
+ case "bottom":
384
+ arrowClassForCorrectAngle = '!zt-border-x-transparent !zt-border-t-transparent'
385
+ arrowTop = (-arrowSize * 2) - tooltipBorderWidth + arrowPositionAdjuster
386
+ arrowLeft = anchorElementRect.left - tooltipElementRect.left + (anchorElementRect.width / 2) - arrowHalfLengthOfLongSide - tooltipBorderWidth
387
+ break;
388
+ }
389
+
390
+ if (currentTooltipPosition === 'left' || currentTooltipPosition === 'right') {
391
+ if (!isArrowPositionWithinLimits(currentTooltipPosition, tooltipElementRect, arrowTop)) {
392
+ arrowTop = getArrowPositionMinLimit(currentTooltipPosition, tooltipElementRect, arrowTop)
393
+ }
376
394
  } else {
377
- tooltipText = bindingValue.content
395
+ if (!isArrowPositionWithinLimits(currentTooltipPosition, tooltipElementRect, arrowLeft)) {
396
+ arrowLeft = getArrowPositionMinLimit(currentTooltipPosition, tooltipElementRect, arrowLeft)
397
+ }
378
398
  }
379
399
 
380
- if (!tooltipText) {
381
- throw new Error("Please enter valid tooltip value");
400
+ // Set Arrow element id, styling/angle
401
+ const adjustedArrowClasses = arrowElementClass + ' ' + defaultArrowClasses + ' ' + arrowClassForCorrectAngle + ' ' + arrowClasses
402
+
403
+ arrowElement.classList.add(...adjustedArrowClasses.trim().split(' '))
404
+
405
+ // Set Arrow element size and position
406
+ arrowElement.style.top = `${arrowTop}px`
407
+ arrowElement.style.left = `${arrowLeft}px`
408
+ arrowElement.style.borderWidth = `${arrowSize}px`
409
+
410
+ // Mount Arrow element
411
+ document.querySelector(`.${tooltipElementClass}`)?.appendChild(arrowElement)
412
+ }
413
+
414
+ function isArrowPositionWithinLimits(currentTooltipPosition: TooltipPosition, tooltipElementRect: DOMRect, arrowPosition: number) {
415
+ switch (currentTooltipPosition) {
416
+ case "left":
417
+ case "right":
418
+ return arrowPosition > arrowMinOffsetFromTooltipCorner - tooltipBorderWidth
419
+ && arrowPosition < tooltipElementRect.height + tooltipBorderWidth - arrowMinOffsetFromTooltipCorner - (arrowSize * 2)
420
+ case "top":
421
+ case "bottom":
422
+ return arrowPosition > arrowMinOffsetFromTooltipCorner - tooltipBorderWidth
423
+ && arrowPosition < tooltipElementRect.width + tooltipBorderWidth - arrowMinOffsetFromTooltipCorner - (arrowSize * 2)
382
424
  }
425
+ }
383
426
 
384
- return tooltipText
427
+ function getArrowPositionMinLimit(currentTooltipPosition: TooltipPosition, tooltipElementRect: DOMRect, arrowPosition: number) {
428
+ switch (currentTooltipPosition) {
429
+ case "left":
430
+ case "right":
431
+ if (arrowPosition < arrowMinOffsetFromTooltipCorner - tooltipBorderWidth) {
432
+ // Arrow too close to viewport top
433
+ return arrowMinOffsetFromTooltipCorner - tooltipBorderWidth
434
+ } else {
435
+ // Arrow too close to viewport bottom
436
+ return tooltipElementRect.height - tooltipBorderWidth - arrowMinOffsetFromTooltipCorner - (arrowSize * 2)
437
+ }
438
+ case "top":
439
+ case "bottom":
440
+ if (arrowPosition < arrowMinOffsetFromTooltipCorner - tooltipBorderWidth) {
441
+ // Arrow too close to viewport left
442
+ return arrowMinOffsetFromTooltipCorner - tooltipBorderWidth
443
+ } else {
444
+ // Arrow too close to viewport right
445
+ return tooltipElementRect.width - tooltipBorderWidth - arrowMinOffsetFromTooltipCorner - (arrowSize * 2)
446
+ }
447
+ }
448
+ }
449
+
450
+ function hideTooltip() {
451
+ const tooltipElement = document.querySelector(`.${tooltipElementClass}`)
452
+
453
+ if (tooltipElement && tooltipElement instanceof HTMLElement) {
454
+ resetResizeReferences()
455
+
456
+ // Remove Arrow element from Tooltip, because it needs to be rebuilt every time Tooltip is showed again
457
+ tooltipElement.querySelector(`.${arrowElementClass}`)?.remove()
458
+
459
+ // Reset position so that old position does not effect new position (when zooming old position could be off screen)
460
+ tooltipElement.style.left = '0'
461
+ tooltipElement.style.top = '0'
462
+
463
+ tooltipElement.remove()
464
+ }
465
+
466
+ isHovered = false
385
467
  }
386
468
 
387
469
  export default ZeroTooltip