@energie360/ui-library 0.1.0 → 0.1.2

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.
Files changed (148) hide show
  1. package/base/_input-resets.scss +9 -3
  2. package/base/_resets.scss +5 -0
  3. package/base/abstracts/_mixins.scss +11 -5
  4. package/base/main-base.scss +1 -0
  5. package/components/accordion-item/accordion-item.scss +62 -0
  6. package/components/accordion-item/u-accordion-item.vue +44 -0
  7. package/components/card/card.scss +58 -0
  8. package/components/card/u-card.vue +26 -0
  9. package/components/card-header/card-header.scss +102 -0
  10. package/components/card-header/u-card-header.vue +51 -0
  11. package/components/card-hint/card-hint.scss +13 -0
  12. package/components/card-hint/u-card-hint.vue +23 -0
  13. package/components/card-price/card-price.scss +110 -0
  14. package/components/card-price/u-card-price.vue +47 -0
  15. package/components/card-table/card-table.scss +76 -0
  16. package/components/card-table/u-card-table.vue +77 -0
  17. package/components/card-toggle-switches/card-toggle-switches.scss +13 -0
  18. package/components/card-toggle-switches/u-card-toggle-switches.vue +30 -0
  19. package/components/collapsible/collapsible.scss +14 -0
  20. package/components/collapsible/u-collapsible.vue +81 -0
  21. package/components/collapsible-group/u-collapsible-group.vue +14 -0
  22. package/components/icon-teaser/icon-teaser.scss +58 -0
  23. package/components/icon-teaser/u-icon-teaser.vue +35 -0
  24. package/components/icon-teaser-group/icon-teaser-group.scss +10 -0
  25. package/components/icon-teaser-group/u-icon-teaser-group.vue +19 -0
  26. package/components/icon-text-block/{icon-text-block.vue → u-icon-text-block.vue} +11 -12
  27. package/components/index.js +14 -0
  28. package/components/language-nav/language-nav.scss +32 -0
  29. package/components/language-nav/u-language-nav.vue +27 -0
  30. package/components/panel/panel.scss +107 -0
  31. package/components/panel/u-panel.vue +48 -0
  32. package/components/progress-bar/progress-bar.scss +37 -0
  33. package/components/progress-bar/u-progress-bar.vue +21 -0
  34. package/components/richtext/richtext.scss +208 -0
  35. package/components/richtext/u-richtext.vue +21 -0
  36. package/components/table/cell-ctas.scss +12 -0
  37. package/components/table/cell-icon-group.scss +12 -0
  38. package/components/table/cell-icon-text.scss +22 -0
  39. package/components/table/cell-progress-bar.scss +23 -0
  40. package/components/table/table-cell.mixins.scss +60 -0
  41. package/components/table/table-cell.scss +24 -0
  42. package/components/table/table-header.scss +5 -0
  43. package/components/table/table-heading.scss +8 -0
  44. package/components/table/table-row.scss +20 -0
  45. package/components/table/table.scss +12 -0
  46. package/components/table/table.type.ts +31 -0
  47. package/components/table/u-cell-ctas.vue +28 -0
  48. package/components/table/u-cell-icon-group.vue +31 -0
  49. package/components/table/u-cell-icon-text.vue +23 -0
  50. package/components/table/u-cell-progress-bar.vue +22 -0
  51. package/components/table/u-table-cell.vue +37 -0
  52. package/components/table/u-table-header.vue +9 -0
  53. package/components/table/u-table-heading.vue +21 -0
  54. package/components/table/u-table-row.vue +17 -0
  55. package/components/table/u-table.vue +11 -0
  56. package/components/tooltip/dom.js +167 -0
  57. package/components/tooltip/popover.ts +208 -0
  58. package/components/tooltip/tooltip.scss +75 -0
  59. package/components/tooltip/u-tooltip.vue +72 -0
  60. package/components/tooltip/viewport.js +21 -0
  61. package/custom-elements.js +1 -0
  62. package/dist/base-style.css +409 -2
  63. package/dist/base-style.css.map +1 -0
  64. package/dist/elements/text-link.css +40 -0
  65. package/dist/elements/text-link.css.map +1 -0
  66. package/dist/layout/split.css +124 -0
  67. package/dist/layout/split.css.map +1 -0
  68. package/elements/button/_button-base.scss +1 -1
  69. package/elements/button/_button-filled-inverted.scss +3 -3
  70. package/elements/button/_button-filled.scss +3 -3
  71. package/elements/button/_button-outlined-inverted.scss +3 -3
  72. package/elements/button/_button-outlined.scss +3 -3
  73. package/elements/button/_button-plain.scss +3 -3
  74. package/elements/button/_button-secondary-outlined.scss +3 -3
  75. package/elements/button/button.js +2 -2
  76. package/elements/button/button.scss +1 -1
  77. package/elements/button/u-button.vue +41 -0
  78. package/elements/button-chip/button-chip.scss +83 -0
  79. package/elements/button-chip/u-button-chip.vue +45 -0
  80. package/elements/elements.js +35 -0
  81. package/elements/form-field/form-field-base.scss +141 -0
  82. package/elements/form-field/form-field-error.scss +20 -0
  83. package/elements/form-field/form-field-prefix-suffix.scss +80 -0
  84. package/elements/form-field/form-field-states.scss +59 -0
  85. package/elements/form-field/form-field.types.ts +8 -0
  86. package/elements/form-field/index.scss +4 -0
  87. package/elements/icon/icon.js +2 -2
  88. package/elements/icon/{icon.vue → u-icon.vue} +12 -18
  89. package/elements/icon-button/icon-button.js +2 -2
  90. package/elements/icon-button/{icon-button.vue → u-icon-button.vue} +14 -15
  91. package/elements/image/image.scss +3 -0
  92. package/elements/image/u-image.vue +17 -0
  93. package/elements/index.js +6 -31
  94. package/elements/loader/loader.js +2 -2
  95. package/elements/loader/{loader.vue → u-loader.vue} +6 -7
  96. package/elements/numeric-stepper/numeric-stepper.scss +110 -0
  97. package/elements/numeric-stepper/u-numeric-stepper.vue +135 -0
  98. package/elements/select/select.scss +32 -0
  99. package/elements/select/u-select.vue +130 -0
  100. package/elements/select-chip/select-chip.scss +18 -0
  101. package/elements/select-chip/u-select-chip.vue +50 -0
  102. package/elements/select-chips/select-chips.scss +5 -0
  103. package/elements/select-chips/u-select-chips.vue +23 -0
  104. package/elements/spectro/spectro.scss +10 -0
  105. package/elements/spectro/u-spectro.vue +11 -0
  106. package/elements/text-field/text-field.scss +30 -0
  107. package/elements/text-field/text-field.types.ts +6 -0
  108. package/elements/text-field/u-text-field.vue +180 -0
  109. package/elements/text-link/text-link.scss +57 -0
  110. package/elements/toggle-switch/toggle-switch-small.scss +40 -0
  111. package/elements/toggle-switch/toggle-switch.scss +149 -0
  112. package/elements/toggle-switch/u-toggle-switch.vue +68 -0
  113. package/elements/types.ts +19 -0
  114. package/env.d.ts +1 -0
  115. package/globals.js +1 -2
  116. package/helpers/transition-height.vue +39 -0
  117. package/i18n/i18n.ts +40 -0
  118. package/layout/grid/grid.mixin.scss +4 -11
  119. package/layout/grid/grid.scss +6 -7
  120. package/layout/split/split.scss +96 -0
  121. package/modules/footer/footer.scss +161 -0
  122. package/modules/footer/u-footer.vue +59 -0
  123. package/package.json +33 -13
  124. package/tsconfig.app.json +12 -0
  125. package/tsconfig.json +11 -0
  126. package/tsconfig.node.json +19 -0
  127. package/utility/elements/text-link.scss +1 -0
  128. package/utility/layout/split.scss +1 -0
  129. package/utility/utility-text.js +1 -0
  130. package/utils/object/deep-get.js +1 -2
  131. package/utils/translations/translate.js +13 -0
  132. package/{vite.config.js → vite.config.ts} +2 -1
  133. package/watch.js +27 -0
  134. package/wizard/index.js +4 -0
  135. package/wizard/wizard-intro/{wizard-intro.vue → u-wizard-intro.vue} +12 -9
  136. package/wizard/wizard-intro/wizard-intro.scss +4 -0
  137. package/wizard/wizard-layout/{wizard-layout-block.vue → u-wizard-layout-block.vue} +7 -5
  138. package/wizard/wizard-layout/{wizard-layout-element.vue → u-wizard-layout-element.vue} +1 -1
  139. package/wizard/wizard-layout/{wizard-layout.vue → u-wizard-layout.vue} +1 -1
  140. package/wizard/wizard-layout/wizard-layout.scss +6 -6
  141. package/dist/base-style.js +0 -2
  142. package/dist/base-style.js.map +0 -1
  143. package/dist/index.css +0 -1
  144. package/dist/index.js +0 -5194
  145. package/dist/index.js.map +0 -1
  146. package/elements/button/button.vue +0 -42
  147. package/index.js +0 -1
  148. /package/components/icon-text-block-group/{icon-text-block-group.vue → u-icon-text-block-group.vue} +0 -0
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Wrap an element with another element
3
+ *
4
+ * @param {Node} el
5
+ * @param {string} wrapper - Tag name of wrapper element. Can also have a classname (like emmet notation)
6
+ *
7
+ * @returns {Node}
8
+ */
9
+ export const wrap = (el, wrapper) => {
10
+ const parsedWrapper = wrapper.split('.')
11
+ const wrapperEl = document.createElement(parsedWrapper[0])
12
+
13
+ if (parsedWrapper.length > 1) {
14
+ wrapperEl.className = parsedWrapper.slice(1).join(' ')
15
+ }
16
+
17
+ el.parentNode.insertBefore(wrapperEl, el)
18
+ wrapperEl.appendChild(el)
19
+
20
+ return wrapperEl
21
+ }
22
+
23
+ /**
24
+ * Get element position relative to document.
25
+ *
26
+ * @param {Node} el
27
+ * @returns {{top: number, left: number, width: number, height: number}}
28
+ */
29
+ export const getElementPosition = (el) => {
30
+ const elRect = el.getBoundingClientRect()
31
+ const scrollTop = window.scrollY
32
+ const scrollLeft = window.scrollX
33
+
34
+ return {
35
+ top: elRect.top + scrollTop,
36
+ left: elRect.left + scrollLeft,
37
+ width: elRect.width,
38
+ height: elRect.height,
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Decode HTML encoded string.
44
+ *
45
+ * @param {string} str
46
+ * @returns {string}
47
+ */
48
+ export const htmlDecode = (str) => {
49
+ const doc = new DOMParser().parseFromString(str, 'text/html')
50
+ return doc.documentElement.textContent
51
+ }
52
+
53
+ export const htmlToDocFragment = (str) => {
54
+ const tpl = document.createElement('template')
55
+ tpl.innerHTML = str
56
+ return tpl.content.cloneNode(true)
57
+ }
58
+
59
+ /**
60
+ * Reverse order of direct child nodes of given parent.
61
+ *
62
+ * @param {Node} parent
63
+ */
64
+ export const reverseChildren = (parent) => {
65
+ for (let i = 1; i < parent.childNodes.length; i++) {
66
+ parent.insertBefore(parent.childNodes[i], parent.firstChild)
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Check if element is in a slotted element.
72
+ *
73
+ * @param {Element} el
74
+ * @returns {boolean}
75
+ */
76
+ const isInSlottedElement = (el) => {
77
+ const parentEl = el.parentElement
78
+
79
+ if (parentEl === document.body || !parentEl) {
80
+ return false
81
+ }
82
+
83
+ if (parentEl.assignedSlot) {
84
+ return true
85
+ } else {
86
+ return isInSlottedElement(parentEl)
87
+ }
88
+ }
89
+
90
+ /**
91
+ *
92
+ * @param {Element} el
93
+ * @returns {boolean}
94
+ */
95
+ const isOffsetParent = (el) => {
96
+ const style = getComputedStyle(el)
97
+ return style.position === 'relative' || style.position === 'absolute'
98
+ }
99
+
100
+ /**
101
+ * Get the expected offsetParent within nested shadow roots.
102
+ *
103
+ * Because elements are nested in web components or slotted in web components we can't get desired offsetParent.
104
+ *
105
+ * @param {Element} el
106
+ * @returns {Element}
107
+ */
108
+ export const getOffsetParent = (el) => {
109
+ if (el === document.body) {
110
+ // We arrived at the top.
111
+ // 'body' must be the offsetParent then.
112
+ return el
113
+ }
114
+
115
+ const offsetParent = el.offsetParent
116
+
117
+ if (offsetParent === document.body || offsetParent === null) {
118
+ // It's possible that we're a slotted element, in which case we'll always have the potentially 'wrong' offsetParent 'body'.
119
+ // Dig deeper...
120
+ if (el.assignedSlot) {
121
+ // A <slot> doesn't have an offsetParent. Let's use the parentNode.
122
+ const parentElement = el.assignedSlot.parentElement
123
+
124
+ // Handle case where the parent node actually is an offsetParent
125
+ if (isOffsetParent(parentElement)) {
126
+ return parentElement
127
+ }
128
+
129
+ return getOffsetParent(el.assignedSlot.parentElement)
130
+ }
131
+
132
+ if (isInSlottedElement(el)) {
133
+ return getOffsetParent(el.parentElement)
134
+ }
135
+
136
+ if (el.getRootNode() instanceof ShadowRoot) {
137
+ // If we're nested in another shadow root we'll also always get 'body' as offsetParent.
138
+ // Check the offsetParent of the current host element.
139
+ return getOffsetParent(el.getRootNode().host)
140
+ }
141
+ }
142
+
143
+ return offsetParent
144
+ }
145
+
146
+ /**
147
+ * Get offset positions of parent which is not 'static'.
148
+ *
149
+ * @param {Element} el
150
+ * @returns {Object}
151
+ */
152
+ export const getOffsetParentPosition = (el) => {
153
+ const offsetParent = getOffsetParent(el)
154
+ const elRect = el.getBoundingClientRect()
155
+ const offsetParentRect = offsetParent.getBoundingClientRect()
156
+
157
+ return {
158
+ top: elRect.top - offsetParentRect.top,
159
+ left: elRect.left - offsetParentRect.left,
160
+ }
161
+ }
162
+
163
+ export const moveChildren = (sourceParent, targetParent) => {
164
+ while (sourceParent.childNodes.length > 0) {
165
+ targetParent.appendChild(sourceParent.childNodes[0])
166
+ }
167
+ }
@@ -0,0 +1,208 @@
1
+ import { getViewportDimensions } from './viewport'
2
+ import { getOffsetParentPosition } from './dom'
3
+
4
+ export type Placement =
5
+ | 'top-right'
6
+ | 'top-left'
7
+ | 'top-center'
8
+ | 'bottom-right'
9
+ | 'bottom-left'
10
+ | 'bottom-center'
11
+ export interface PopoverPositionParams {
12
+ placement?: Placement
13
+ offset?: number
14
+ viewportPadding?: number
15
+ }
16
+
17
+ export interface PopoverPosition {
18
+ top: number
19
+ left: number
20
+ topCorrection: number
21
+ leftCorrection: number
22
+ }
23
+
24
+ const defaultOptions = {
25
+ offset: 0,
26
+ viewportPadding: 0,
27
+ placement: 'top-left',
28
+ }
29
+
30
+ /**
31
+ * This simply calculates the desired position of the popover and returns the coordinates.
32
+ * It won't change the DOM.
33
+ *
34
+ * @param {Element} refEl
35
+ * @param {Element} popoverEl
36
+ * @param {Object} opts - Options object
37
+ * @param {number} opts.offset - offset to refEl in px
38
+ * @param {number} opts.viewportPadding - Padding to viewport in px
39
+ * @param {string} opts.placement - One of the following keywords: top-right, top-left, top-center, bottom-right, bottom-left, bottom-center
40
+ */
41
+ export const getPopoverPosition = (
42
+ refEl: HTMLElement,
43
+ popoverEl: HTMLElement,
44
+ opts: PopoverPositionParams,
45
+ ) => {
46
+ const options = Object.assign({}, defaultOptions, opts)
47
+
48
+ const refRect = refEl.getBoundingClientRect()
49
+ let popoverRect = popoverEl.getBoundingClientRect()
50
+
51
+ const { top: offsetTop, left: offsetLeft } = getOffsetParentPosition(refEl)
52
+
53
+ let top = 0
54
+ let left = 0
55
+ const { vpWidth } = getViewportDimensions()
56
+ const docHeight = document.documentElement.scrollHeight
57
+ const scrollY = window.scrollY
58
+
59
+ if (popoverRect.width === 0 && popoverRect.height === 0) {
60
+ // Popover probably has display:none.
61
+ // Make it visible for a frame to get the correct dimensions.
62
+ popoverEl.style.display = 'block'
63
+ popoverRect = popoverEl.getBoundingClientRect()
64
+ popoverEl.style.display = ''
65
+ }
66
+
67
+ switch (options.placement) {
68
+ case 'top-right':
69
+ top = offsetTop - popoverRect.height - options.offset
70
+ left = offsetLeft + refRect.width - popoverRect.width
71
+ break
72
+
73
+ case 'top-left':
74
+ top = offsetTop - popoverRect.height - options.offset
75
+ left = offsetLeft
76
+ break
77
+
78
+ case 'bottom-right':
79
+ top = offsetTop + refRect.height + options.offset
80
+ left = offsetLeft + refRect.width - popoverRect.width
81
+ break
82
+
83
+ case 'bottom-left':
84
+ top = offsetTop + refRect.height + options.offset
85
+ left = offsetLeft
86
+ break
87
+
88
+ // TODO: add remaining placement options
89
+ case 'bottom-center':
90
+ break
91
+
92
+ case 'top-center':
93
+ }
94
+
95
+ // Add corrections to position if necessary
96
+
97
+ // Check left or right overflow
98
+ const leftDiff = refRect.left - offsetLeft
99
+ if (left + leftDiff < options.viewportPadding) {
100
+ left = 0 - leftDiff + options.viewportPadding
101
+ } else if (left + leftDiff + popoverRect.width > vpWidth - options.viewportPadding) {
102
+ left = vpWidth - leftDiff - popoverRect.width - options.viewportPadding
103
+ }
104
+
105
+ // Check top or bottom overflow
106
+ const topDiff = refRect.top - offsetTop
107
+ if (top + topDiff + scrollY < options.viewportPadding) {
108
+ // popover is too high.
109
+ // Switch position to bottom
110
+ top = offsetTop + refRect.height + options.offset
111
+ } else if (top + topDiff + scrollY + popoverRect.height > docHeight - options.viewportPadding) {
112
+ // popover is too low.
113
+ // Swith position to top.
114
+ top = offsetTop - popoverRect.height - options.offset
115
+ }
116
+
117
+ return { top, left }
118
+ }
119
+
120
+ /**
121
+ * Calculates the desired position of the popover relative to <body>.
122
+ *
123
+ * @param {Element} refEl
124
+ * @param {Element} popoverEl
125
+ * @param {Object} opts - Options object
126
+ * @param {number} opts.offset - offset to refEl in px
127
+ * @param {number} opts.viewportPadding - Padding to viewport in px
128
+ * @param {string} opts.placement - One of the following keywords: top-right, top-left, top-center, bottom-right, bottom-left, bottom-center
129
+ */
130
+ export function getPopoverPositionBody(
131
+ refEl: HTMLElement,
132
+ popoverEl: HTMLElement,
133
+ opts: PopoverPositionParams,
134
+ ): PopoverPosition {
135
+ const options = Object.assign({}, defaultOptions, opts)
136
+
137
+ const refRect = refEl.getBoundingClientRect()
138
+ let popoverRect = popoverEl.getBoundingClientRect()
139
+
140
+ let top = 0
141
+ let left = 0
142
+ const topCorrection = 0
143
+ let leftCorrection = 0
144
+
145
+ const scrollY = window.scrollY
146
+ const scrollX = window.scrollX
147
+ const { vpWidth } = getViewportDimensions()
148
+ // const docHeight = document.documentElement.scrollHeight
149
+ const refTop = refRect.top + scrollY
150
+ const refLeft = refRect.left + scrollX
151
+
152
+ if (popoverRect.width === 0 && popoverRect.height === 0) {
153
+ // Popover probably has display:none.
154
+ // Make it visible for a frame to get the correct dimensions.
155
+ popoverEl.style.display = 'block'
156
+ popoverRect = popoverEl.getBoundingClientRect()
157
+ popoverEl.style.display = ''
158
+ }
159
+
160
+ switch (options.placement) {
161
+ case 'top-right':
162
+ // top = offsetTop - popoverRect.height - options.offset
163
+ // left = offsetLeft + refRect.width - popoverRect.width
164
+ break
165
+
166
+ case 'top-left':
167
+ // top = offsetTop - popoverRect.height - options.offset
168
+ // left = offsetLeft
169
+ break
170
+
171
+ case 'bottom-right':
172
+ // top = offsetTop + refRect.height + options.offset
173
+ // left = offsetLeft + refRect.width - popoverRect.width
174
+ break
175
+
176
+ case 'bottom-left':
177
+ // top = offsetTop + refRect.height + options.offset
178
+ // left = offsetLeft
179
+ break
180
+
181
+ case 'bottom-center':
182
+ top = refTop + refRect.height + options.offset
183
+ left = refLeft + refRect.width / 2 - popoverRect.width / 2
184
+ break
185
+
186
+ case 'top-center':
187
+ top = refTop - options.offset - popoverRect.height
188
+ left = refLeft + refRect.width / 2 - popoverRect.width / 2
189
+ }
190
+
191
+ // Add corrections to position if necessary
192
+
193
+ // Check left or right overflow
194
+ if (left < options.viewportPadding) {
195
+ // too left
196
+ leftCorrection = Math.abs(left) + options.viewportPadding
197
+ left = options.viewportPadding
198
+ } else if (left + popoverRect.width > vpWidth - options.viewportPadding) {
199
+ // too right
200
+ leftCorrection = vpWidth - options.viewportPadding - popoverRect.width - left
201
+ left = vpWidth - options.viewportPadding - popoverRect.width
202
+ }
203
+
204
+ // Check top or bottom overflow
205
+ // TODO: Add these corrections
206
+
207
+ return { top, left, leftCorrection, topCorrection }
208
+ }
@@ -0,0 +1,75 @@
1
+ @use '../../base/abstracts/' as a;
2
+
3
+ .tooltip {
4
+ pointer-events: none;
5
+ z-index: a.$layer-tooltip;
6
+ position: absolute;
7
+ top: 0;
8
+ left: 0;
9
+ max-width: a.rem(360);
10
+ min-width: a.rem(84);
11
+ opacity: 0;
12
+ transform: translateY(-5px);
13
+ transition:
14
+ opacity a.$trs-default,
15
+ transform a.$trs-default;
16
+
17
+ @include a.bp(lg) {
18
+ max-width: a.rem(280);
19
+ }
20
+
21
+ .tooltip__content {
22
+ @include a.type(100);
23
+
24
+ // This nesting here is only to make the new sass compiler happy.
25
+ & {
26
+ z-index: 0;
27
+ position: relative;
28
+ padding: var(--e-space-2);
29
+ background-color: var(--e-c-mono-00);
30
+ box-shadow: var(--e-elevation-sm);
31
+ border: 1px solid var(--e-c-mono-100);
32
+ border-radius: var(--e-brd-radius-1);
33
+ }
34
+
35
+ @include a.bp(lg) {
36
+ @include a.type(50);
37
+
38
+ padding: var(--e-space-3);
39
+ }
40
+ }
41
+
42
+ .tooltip__pointer {
43
+ position: absolute;
44
+ bottom: -4px;
45
+ left: calc(50% - 6px);
46
+
47
+ &::before {
48
+ content: '';
49
+ display: block;
50
+ width: 10px;
51
+ height: 9.6px;
52
+ transform: rotate(53.13deg) skewX(15.36deg);
53
+ box-shadow: var(--e-elevation-sm);
54
+ background-color: var(--e-c-mono-00);
55
+ border-right: 1px solid var(--e-c-mono-100);
56
+ border-bottom: 1px solid var(--e-c-mono-100);
57
+ }
58
+
59
+ &::after {
60
+ content: '';
61
+ position: absolute;
62
+ bottom: 5px;
63
+ left: -2px;
64
+ width: 14px;
65
+ height: 7px;
66
+ background: var(--e-c-mono-00);
67
+ }
68
+ }
69
+
70
+ // States
71
+ &.tooltip--open {
72
+ opacity: 1;
73
+ transform: translateY(0);
74
+ }
75
+ }
@@ -0,0 +1,72 @@
1
+ <script setup lang="ts">
2
+ import { ref } from 'vue'
3
+ import { getPopoverPositionBody } from './popover'
4
+ import type { PopoverPositionParams } from './popover'
5
+
6
+ interface Props extends PopoverPositionParams {
7
+ title: string
8
+ }
9
+
10
+ const { placement = 'top-center', offset = 12, viewportPadding = 20 } = defineProps<Props>()
11
+
12
+ const tooltip = ref<HTMLDivElement | null>(null)
13
+ const root = ref<HTMLSpanElement | null>(null)
14
+ const pointer = ref<HTMLSpanElement | null>(null)
15
+ const isTooltipVisible = ref<boolean>(false)
16
+
17
+ // On desktop (or non-touch devices) mouse events will be fired before the pointer events.
18
+ // For mobile and touch devices it's th opposite, pointer events will be fired first.
19
+ // This way we can detect on what device we are and handle the behaviour accordingly.
20
+ function hideTooltip() {
21
+ isTooltipVisible.value = false
22
+ }
23
+
24
+ function showTooltip() {
25
+ updatePositions()
26
+ isTooltipVisible.value = true
27
+ }
28
+
29
+ function onTransitionend() {
30
+ if (isTooltipVisible.value) return
31
+ updatePositions(true)
32
+ isTooltipVisible.value = false
33
+ }
34
+
35
+ function updatePositions(resetPositions = false) {
36
+ const { top, left, leftCorrection } = getPopoverPositionBody(root.value!, tooltip.value!, {
37
+ offset,
38
+ placement,
39
+ viewportPadding,
40
+ })
41
+
42
+ tooltip.value!.style.top = resetPositions ? '' : `${top}px`
43
+ tooltip.value!.style.left = resetPositions ? '' : `${left}px`
44
+ // Fix pointer position if necessary
45
+ pointer.value!.style.transform =
46
+ !resetPositions && leftCorrection ? `translateX(${leftCorrection * -1}px)` : ''
47
+ }
48
+ </script>
49
+
50
+ <template>
51
+ <span ref="root" @mouseenter="showTooltip" @mouseleave="hideTooltip">
52
+ <slot />
53
+ </span>
54
+
55
+ <Teleport to="body">
56
+ <div
57
+ ref="tooltip"
58
+ :class="['tooltip', { 'tooltip--open': isTooltipVisible }]"
59
+ :aria-hidden="!isTooltipVisible"
60
+ @transitionend="onTransitionend"
61
+ >
62
+ <div class="tooltip__content">
63
+ {{ title }}
64
+ </div>
65
+ <span ref="pointer" class="tooltip__pointer" />
66
+ </div>
67
+ </Teleport>
68
+ </template>
69
+
70
+ <style scoped lang="scss">
71
+ @use './tooltip.scss';
72
+ </style>
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Get element offset relative to document.
3
+ *
4
+ * @param {Element} el
5
+ * @returns {{top: *, left: *, width, height}}
6
+ */
7
+ export const getElOffset = (el) => {
8
+ const rect = el.getBoundingClientRect()
9
+
10
+ return {
11
+ top: window.scrollY + rect.top,
12
+ left: window.scrollX + rect.left,
13
+ width: rect.width,
14
+ height: rect.height,
15
+ }
16
+ }
17
+
18
+ export const getViewportDimensions = () => ({
19
+ vpWidth: window.innerWidth,
20
+ vpHeight: window.innerHeight,
21
+ })
@@ -0,0 +1 @@
1
+ export * from './elements/elements'