@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.
- package/base/_input-resets.scss +9 -3
- package/base/_resets.scss +5 -0
- package/base/abstracts/_mixins.scss +11 -5
- package/base/main-base.scss +1 -0
- package/components/accordion-item/accordion-item.scss +62 -0
- package/components/accordion-item/u-accordion-item.vue +44 -0
- package/components/card/card.scss +58 -0
- package/components/card/u-card.vue +26 -0
- package/components/card-header/card-header.scss +102 -0
- package/components/card-header/u-card-header.vue +51 -0
- package/components/card-hint/card-hint.scss +13 -0
- package/components/card-hint/u-card-hint.vue +23 -0
- package/components/card-price/card-price.scss +110 -0
- package/components/card-price/u-card-price.vue +47 -0
- package/components/card-table/card-table.scss +76 -0
- package/components/card-table/u-card-table.vue +77 -0
- package/components/card-toggle-switches/card-toggle-switches.scss +13 -0
- package/components/card-toggle-switches/u-card-toggle-switches.vue +30 -0
- package/components/collapsible/collapsible.scss +14 -0
- package/components/collapsible/u-collapsible.vue +81 -0
- package/components/collapsible-group/u-collapsible-group.vue +14 -0
- package/components/icon-teaser/icon-teaser.scss +58 -0
- package/components/icon-teaser/u-icon-teaser.vue +35 -0
- package/components/icon-teaser-group/icon-teaser-group.scss +10 -0
- package/components/icon-teaser-group/u-icon-teaser-group.vue +19 -0
- package/components/icon-text-block/{icon-text-block.vue → u-icon-text-block.vue} +11 -12
- package/components/index.js +14 -0
- package/components/language-nav/language-nav.scss +32 -0
- package/components/language-nav/u-language-nav.vue +27 -0
- package/components/panel/panel.scss +107 -0
- package/components/panel/u-panel.vue +48 -0
- package/components/progress-bar/progress-bar.scss +37 -0
- package/components/progress-bar/u-progress-bar.vue +21 -0
- package/components/richtext/richtext.scss +208 -0
- package/components/richtext/u-richtext.vue +21 -0
- package/components/table/cell-ctas.scss +12 -0
- package/components/table/cell-icon-group.scss +12 -0
- package/components/table/cell-icon-text.scss +22 -0
- package/components/table/cell-progress-bar.scss +23 -0
- package/components/table/table-cell.mixins.scss +60 -0
- package/components/table/table-cell.scss +24 -0
- package/components/table/table-header.scss +5 -0
- package/components/table/table-heading.scss +8 -0
- package/components/table/table-row.scss +20 -0
- package/components/table/table.scss +12 -0
- package/components/table/table.type.ts +31 -0
- package/components/table/u-cell-ctas.vue +28 -0
- package/components/table/u-cell-icon-group.vue +31 -0
- package/components/table/u-cell-icon-text.vue +23 -0
- package/components/table/u-cell-progress-bar.vue +22 -0
- package/components/table/u-table-cell.vue +37 -0
- package/components/table/u-table-header.vue +9 -0
- package/components/table/u-table-heading.vue +21 -0
- package/components/table/u-table-row.vue +17 -0
- package/components/table/u-table.vue +11 -0
- package/components/tooltip/dom.js +167 -0
- package/components/tooltip/popover.ts +208 -0
- package/components/tooltip/tooltip.scss +75 -0
- package/components/tooltip/u-tooltip.vue +72 -0
- package/components/tooltip/viewport.js +21 -0
- package/custom-elements.js +1 -0
- package/dist/base-style.css +409 -2
- package/dist/base-style.css.map +1 -0
- package/dist/elements/text-link.css +40 -0
- package/dist/elements/text-link.css.map +1 -0
- package/dist/layout/split.css +124 -0
- package/dist/layout/split.css.map +1 -0
- package/elements/button/_button-base.scss +1 -1
- package/elements/button/_button-filled-inverted.scss +3 -3
- package/elements/button/_button-filled.scss +3 -3
- package/elements/button/_button-outlined-inverted.scss +3 -3
- package/elements/button/_button-outlined.scss +3 -3
- package/elements/button/_button-plain.scss +3 -3
- package/elements/button/_button-secondary-outlined.scss +3 -3
- package/elements/button/button.js +2 -2
- package/elements/button/button.scss +1 -1
- package/elements/button/u-button.vue +41 -0
- package/elements/button-chip/button-chip.scss +83 -0
- package/elements/button-chip/u-button-chip.vue +45 -0
- package/elements/elements.js +35 -0
- package/elements/form-field/form-field-base.scss +141 -0
- package/elements/form-field/form-field-error.scss +20 -0
- package/elements/form-field/form-field-prefix-suffix.scss +80 -0
- package/elements/form-field/form-field-states.scss +59 -0
- package/elements/form-field/form-field.types.ts +8 -0
- package/elements/form-field/index.scss +4 -0
- package/elements/icon/icon.js +2 -2
- package/elements/icon/{icon.vue → u-icon.vue} +12 -18
- package/elements/icon-button/icon-button.js +2 -2
- package/elements/icon-button/{icon-button.vue → u-icon-button.vue} +14 -15
- package/elements/image/image.scss +3 -0
- package/elements/image/u-image.vue +17 -0
- package/elements/index.js +6 -31
- package/elements/loader/loader.js +2 -2
- package/elements/loader/{loader.vue → u-loader.vue} +6 -7
- package/elements/numeric-stepper/numeric-stepper.scss +110 -0
- package/elements/numeric-stepper/u-numeric-stepper.vue +135 -0
- package/elements/select/select.scss +32 -0
- package/elements/select/u-select.vue +130 -0
- package/elements/select-chip/select-chip.scss +18 -0
- package/elements/select-chip/u-select-chip.vue +50 -0
- package/elements/select-chips/select-chips.scss +5 -0
- package/elements/select-chips/u-select-chips.vue +23 -0
- package/elements/spectro/spectro.scss +10 -0
- package/elements/spectro/u-spectro.vue +11 -0
- package/elements/text-field/text-field.scss +30 -0
- package/elements/text-field/text-field.types.ts +6 -0
- package/elements/text-field/u-text-field.vue +180 -0
- package/elements/text-link/text-link.scss +57 -0
- package/elements/toggle-switch/toggle-switch-small.scss +40 -0
- package/elements/toggle-switch/toggle-switch.scss +149 -0
- package/elements/toggle-switch/u-toggle-switch.vue +68 -0
- package/elements/types.ts +19 -0
- package/env.d.ts +1 -0
- package/globals.js +1 -2
- package/helpers/transition-height.vue +39 -0
- package/i18n/i18n.ts +40 -0
- package/layout/grid/grid.mixin.scss +4 -11
- package/layout/grid/grid.scss +6 -7
- package/layout/split/split.scss +96 -0
- package/modules/footer/footer.scss +161 -0
- package/modules/footer/u-footer.vue +59 -0
- package/package.json +33 -13
- package/tsconfig.app.json +12 -0
- package/tsconfig.json +11 -0
- package/tsconfig.node.json +19 -0
- package/utility/elements/text-link.scss +1 -0
- package/utility/layout/split.scss +1 -0
- package/utility/utility-text.js +1 -0
- package/utils/object/deep-get.js +1 -2
- package/utils/translations/translate.js +13 -0
- package/{vite.config.js → vite.config.ts} +2 -1
- package/watch.js +27 -0
- package/wizard/index.js +4 -0
- package/wizard/wizard-intro/{wizard-intro.vue → u-wizard-intro.vue} +12 -9
- package/wizard/wizard-intro/wizard-intro.scss +4 -0
- package/wizard/wizard-layout/{wizard-layout-block.vue → u-wizard-layout-block.vue} +7 -5
- package/wizard/wizard-layout/{wizard-layout-element.vue → u-wizard-layout-element.vue} +1 -1
- package/wizard/wizard-layout/{wizard-layout.vue → u-wizard-layout.vue} +1 -1
- package/wizard/wizard-layout/wizard-layout.scss +6 -6
- package/dist/base-style.js +0 -2
- package/dist/base-style.js.map +0 -1
- package/dist/index.css +0 -1
- package/dist/index.js +0 -5194
- package/dist/index.js.map +0 -1
- package/elements/button/button.vue +0 -42
- package/index.js +0 -1
- /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'
|