@eturnity/eturnity_reusable_components 8.16.2--EPDM-14330.5 → 8.16.2--EPDM-14330.6

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 (102) hide show
  1. package/package.json +1 -1
  2. package/src/assets/svgIcons/ac_power.svg +4 -0
  3. package/src/assets/svgIcons/arrow_long_left.svg +3 -0
  4. package/src/assets/svgIcons/arrow_long_right.svg +3 -0
  5. package/src/assets/svgIcons/chassis_ground_symbol.svg +27 -0
  6. package/src/assets/svgIcons/dc_power.svg +8 -0
  7. package/src/assets/svgIcons/double_arrow_long.svg +4 -0
  8. package/src/assets/svgIcons/ed_ac.svg +3 -0
  9. package/src/assets/svgIcons/ed_acgrid.svg +4 -0
  10. package/src/assets/svgIcons/ed_arrow_both.svg +7 -0
  11. package/src/assets/svgIcons/ed_arrow_left.svg +7 -0
  12. package/src/assets/svgIcons/ed_arrow_right.svg +7 -0
  13. package/src/assets/svgIcons/ed_battery.svg +10 -0
  14. package/src/assets/svgIcons/ed_batteryacinverter.svg +16 -0
  15. package/src/assets/svgIcons/ed_batteryintegratedinverter.svg +19 -0
  16. package/src/assets/svgIcons/ed_cirquitbreaker.svg +4 -0
  17. package/src/assets/svgIcons/ed_cirquitbreaker_magnetic.svg +6 -0
  18. package/src/assets/svgIcons/ed_cirquitbreaker_thermal.svg +4 -0
  19. package/src/assets/svgIcons/ed_cirquitbreaker_thermal_magnetic.svg +5 -0
  20. package/src/assets/svgIcons/ed_consumption.svg +3 -0
  21. package/src/assets/svgIcons/ed_dc.svg +6 -0
  22. package/src/assets/svgIcons/ed_disconnector.svg +4 -0
  23. package/src/assets/svgIcons/ed_disconnector_fuse.svg +4 -0
  24. package/src/assets/svgIcons/ed_disconnector_fuse_switch.svg +4 -0
  25. package/src/assets/svgIcons/ed_disconnector_loadbreak switch.svg +4 -0
  26. package/src/assets/svgIcons/ed_disconnector_switch.svg +4 -0
  27. package/src/assets/svgIcons/ed_disconnector_switch_auto_release.svg +5 -0
  28. package/src/assets/svgIcons/ed_energymanagement_rectangle.svg +3 -0
  29. package/src/assets/svgIcons/ed_evcharger.svg +19 -0
  30. package/src/assets/svgIcons/ed_flexiblecomponent_circle.svg +3 -0
  31. package/src/assets/svgIcons/ed_flexiblecomponent_square.svg +3 -0
  32. package/src/assets/svgIcons/ed_fuse.svg +3 -0
  33. package/src/assets/svgIcons/ed_ground.svg +5 -0
  34. package/src/assets/svgIcons/ed_heatpump.svg +4 -0
  35. package/src/assets/svgIcons/ed_icon_battery.svg +9 -0
  36. package/src/assets/svgIcons/ed_icon_circle.svg +3 -0
  37. package/src/assets/svgIcons/ed_icon_heatpump.svg +3 -0
  38. package/src/assets/svgIcons/ed_icon_inverter.svg +8 -0
  39. package/src/assets/svgIcons/ed_icon_optimizer.svg +11 -0
  40. package/src/assets/svgIcons/ed_integratedbatteryinverter.svg +10 -0
  41. package/src/assets/svgIcons/ed_inverter-blank.svg +3 -0
  42. package/src/assets/svgIcons/ed_mainsconnection.svg +3 -0
  43. package/src/assets/svgIcons/ed_meter_arrowleft.svg +4 -0
  44. package/src/assets/svgIcons/ed_meter_arrowright.svg +4 -0
  45. package/src/assets/svgIcons/ed_meter_bidirectional.svg +5 -0
  46. package/src/assets/svgIcons/ed_networkandsystemprotection_double.svg +14 -0
  47. package/src/assets/svgIcons/ed_networkandsystemprotection_single.svg +7 -0
  48. package/src/assets/svgIcons/ed_pvpanel.svg +7 -0
  49. package/src/assets/svgIcons/ed_rcd.svg +5 -0
  50. package/src/assets/svgIcons/ed_rcd_simple.svg +3 -0
  51. package/src/assets/svgIcons/ed_spd.svg +6 -0
  52. package/src/assets/svgIcons/ed_stringwithoptimizer.svg +33 -0
  53. package/src/assets/svgIcons/ed_stringwithoutoptimizer.svg +17 -0
  54. package/src/assets/svgIcons/ed_transformer.svg +3 -0
  55. package/src/assets/svgIcons/filter.svg +3 -0
  56. package/src/assets/svgIcons/ground_symbol.svg +28 -0
  57. package/src/assets/svgIcons/move_left.svg +3 -0
  58. package/src/assets/svgIcons/move_right.svg +3 -0
  59. package/src/assets/svgIcons/rectangle.svg +3 -0
  60. package/src/assets/svgIcons/refresh.svg +3 -0
  61. package/src/assets/svgIcons/text_icon.svg +3 -0
  62. package/src/assets/theme.js +17 -1
  63. package/src/components/banner/infoBanner/InfoBanner.spec.js +29 -42
  64. package/src/components/barchart/BottomFields.vue +253 -0
  65. package/src/components/barchart/ChartControls.vue +113 -0
  66. package/src/components/barchart/SelectionBox.vue +150 -0
  67. package/src/components/barchart/composables/index.js +5 -0
  68. package/src/components/barchart/composables/useAxisCalculations.js +104 -0
  69. package/src/components/barchart/composables/useChartData.js +114 -0
  70. package/src/components/barchart/composables/useChartScroll.js +61 -0
  71. package/src/components/barchart/composables/useSelection.js +75 -0
  72. package/src/components/barchart/composables/useTooltip.js +100 -0
  73. package/src/components/barchart/index.vue +385 -0
  74. package/src/components/barchart/styles/bottomFields.js +66 -0
  75. package/src/components/barchart/styles/chart.js +272 -0
  76. package/src/components/barchart/styles/chartControls.js +59 -0
  77. package/src/components/buttons/buttonIcon/index.vue +5 -0
  78. package/src/components/buttons/splitButtons/index.vue +86 -0
  79. package/src/components/collapsableInfoText/index.vue +2 -2
  80. package/src/components/errorMessage/errorMessage.spec.js +34 -0
  81. package/src/components/errorMessage/errorMessage.stories.js +35 -0
  82. package/src/components/filter/filterSettings.vue +2 -0
  83. package/src/components/filterComponent/viewFilter.vue +577 -0
  84. package/src/components/filterComponent/viewSort.vue +10 -5
  85. package/src/components/icon/index.vue +32 -9
  86. package/src/components/infoText/index.vue +2 -2
  87. package/src/components/infoText/infoText.spec.js +6 -1
  88. package/src/components/inputs/inputNumber/index.vue +14 -2
  89. package/src/components/inputs/searchInput/index.vue +18 -2
  90. package/src/components/inputs/select/index.vue +108 -19
  91. package/src/components/inputs/select/option/index.vue +5 -0
  92. package/src/components/modals/actionModal/actionModal.spec.js +52 -0
  93. package/src/components/modals/actionModal/actionModal.stories.js +53 -0
  94. package/src/components/modals/actionModal/index.vue +6 -6
  95. package/src/components/modals/infoModal/index.vue +49 -19
  96. package/src/components/modals/infoModal/infoModal.spec.js +55 -0
  97. package/src/components/modals/infoModal/infoModal.stories.js +47 -0
  98. package/src/components/modals/modal/index.vue +16 -5
  99. package/src/components/pageSubtitle/PageSubtitle.stories.js +0 -1
  100. package/src/helpers/isObjectEqual.js +22 -0
  101. package/src/helpers/translateLang.js +95 -24
  102. package/src/main.js +1 -0
@@ -0,0 +1,150 @@
1
+ <template>
2
+ <SelectionBoxWrapper
3
+ :transform="`translateX(${position}px)`"
4
+ :width="boxWidth + 'px'"
5
+ @mousedown="startDrag"
6
+ />
7
+ </template>
8
+
9
+ <script setup>
10
+ import { ref, computed, onMounted } from 'vue'
11
+ import styled from 'vue3-styled-components'
12
+
13
+ const SelectionBoxWrapper = styled('div', {
14
+ width: String,
15
+ transform: String,
16
+ })`
17
+ position: absolute;
18
+ height: 100%;
19
+ background: rgba(151, 71, 255, 0.1);
20
+ cursor: grab;
21
+ z-index: 0;
22
+ pointer-events: auto;
23
+ width: ${(props) => props.width};
24
+ transform: ${(props) => props.transform};
25
+ &:active {
26
+ cursor: grabbing;
27
+ }
28
+ `
29
+
30
+ const props = defineProps({
31
+ barsToShow: {
32
+ type: Number,
33
+ required: true,
34
+ },
35
+ barWidth: {
36
+ type: Number,
37
+ required: true,
38
+ },
39
+ totalBars: {
40
+ type: Number,
41
+ required: true,
42
+ },
43
+ gap: {
44
+ type: Number,
45
+ default: 8,
46
+ },
47
+ containerWidth: {
48
+ type: Number,
49
+ required: true,
50
+ },
51
+ isScrollable: {
52
+ type: Boolean,
53
+ required: true,
54
+ },
55
+ })
56
+
57
+ // Padding applied to the selection box
58
+ const PADDING = 6
59
+
60
+ const emit = defineEmits(['update-selection', 'drag-end'])
61
+
62
+ // Statess
63
+ const position = ref(0)
64
+ const isDragging = ref(false)
65
+ const currentSelection = ref({ startIndex: 0, endIndex: props.barsToShow })
66
+
67
+ // Variables for tracking drag state
68
+ let startX = 0
69
+ let startPos = 0
70
+
71
+ const actualBarWidth = computed(() => {
72
+ if (props.isScrollable) {
73
+ return props.barWidth
74
+ }
75
+
76
+ return props.containerWidth / props.totalBars
77
+ })
78
+
79
+ const boxWidth = computed(() => {
80
+ if (props.isScrollable) {
81
+ return (
82
+ (props.barWidth + props.gap) * props.barsToShow -
83
+ props.gap +
84
+ PADDING * 2
85
+ )
86
+ }
87
+
88
+ return actualBarWidth.value * props.barsToShow
89
+ })
90
+
91
+ const calculateSelection = () => {
92
+ if (props.isScrollable) {
93
+ const startIndex = Math.floor(
94
+ (position.value + PADDING) / (props.barWidth + props.gap)
95
+ )
96
+ const endIndex = Math.min(startIndex + props.barsToShow, props.totalBars)
97
+ return { startIndex, endIndex }
98
+ }
99
+
100
+ const boxStart = position.value
101
+ const boxEnd = position.value + boxWidth.value
102
+ const sectionWidth = props.containerWidth / props.totalBars
103
+
104
+ const startIndex = Math.round(boxStart / sectionWidth)
105
+ const endIndex = Math.min(
106
+ Math.round(boxEnd / sectionWidth),
107
+ props.totalBars
108
+ )
109
+ return { startIndex, endIndex }
110
+ }
111
+
112
+ const startDrag = (e) => {
113
+ isDragging.value = true
114
+ startX = e.clientX
115
+ startPos = position.value
116
+
117
+ document.addEventListener('mousemove', handleDrag)
118
+ document.addEventListener('mouseup', stopDrag)
119
+ }
120
+
121
+ const handleDrag = (e) => {
122
+ if (!isDragging.value) return
123
+
124
+ const delta = e.clientX - startX
125
+ const newPosition = startPos + delta
126
+
127
+ // Constrain position within valid bounds
128
+ const maxPosition = props.containerWidth - boxWidth.value + PADDING * 2
129
+ position.value = Math.max(-PADDING, Math.min(newPosition, maxPosition))
130
+
131
+ // Update current selection and emit event
132
+ currentSelection.value = calculateSelection()
133
+ emit('update-selection', currentSelection.value)
134
+ }
135
+
136
+ const stopDrag = () => {
137
+ isDragging.value = false
138
+ document.removeEventListener('mousemove', handleDrag)
139
+ document.removeEventListener('mouseup', stopDrag)
140
+
141
+ emit('update-selection', currentSelection.value)
142
+ emit('drag-end')
143
+ }
144
+
145
+ onMounted(() => {
146
+ // Ensure we start with valid selection indices
147
+ currentSelection.value = { startIndex: 0, endIndex: props.barsToShow }
148
+ emit('update-selection', currentSelection.value)
149
+ })
150
+ </script>
@@ -0,0 +1,5 @@
1
+ export { useTooltip } from './useTooltip'
2
+ export { useChartData } from './useChartData'
3
+ export { useAxisCalculations } from './useAxisCalculations'
4
+ export { useSelection } from './useSelection'
5
+ export { useChartScroll } from './useChartScroll'
@@ -0,0 +1,104 @@
1
+ import { computed } from 'vue'
2
+
3
+ export function useAxisCalculations(props, maxValue) {
4
+ const findNiceNumber = (value) => {
5
+ // Handle 0 or negative values
6
+ if (value <= 0) return 0
7
+
8
+ if (value < 1) {
9
+ return Math.ceil(value * 10) / 10
10
+ }
11
+
12
+ const exponent = Math.floor(Math.log10(value))
13
+ const factor = Math.pow(10, exponent)
14
+ const normalized = value / factor
15
+
16
+ const niceNumbers = [
17
+ 1.0, 1.2, 1.5, 1.6, 2.0, 2.5, 3.0, 4.0, 5.0, 6.0, 8.0, 10.0,
18
+ ]
19
+ const niceNormalized = niceNumbers.find((n) => n >= normalized) || 10.0
20
+
21
+ return niceNormalized * factor
22
+ }
23
+
24
+ // Calculate max value with padding, rounded to nice numbers
25
+ const paddedMaxValue = computed(() => {
26
+ const rawMax = maxValue.value
27
+ const withPadding = rawMax * 1.1
28
+ const niceNumber = findNiceNumber(withPadding)
29
+
30
+ return niceNumber
31
+ })
32
+
33
+ const calculateStepSize = (max) => {
34
+ const preferredDivisions = [5, 6, 7, 8, 9, 10]
35
+
36
+ for (const divisions of preferredDivisions) {
37
+ const roughStep = max / divisions
38
+ const niceStep = findNiceNumber(roughStep)
39
+
40
+ const numCompleteSteps = Math.floor(max / niceStep)
41
+ const actualMax = niceStep * numCompleteSteps
42
+
43
+ // Only use this step size if it gets us close to our target max
44
+ // and gives us the right number of ticks
45
+ if (
46
+ actualMax >= max * 0.95 && // Within 5% of target max
47
+ numCompleteSteps + 1 >= 5 &&
48
+ numCompleteSteps + 1 <= 10
49
+ ) {
50
+ return niceStep
51
+ }
52
+ }
53
+
54
+ // If no perfect match found, use max/6
55
+ return findNiceNumber(max / 6)
56
+ }
57
+
58
+ const yAxisLabels = computed(() => {
59
+ const max = paddedMaxValue.value
60
+
61
+ if (max === 0) {
62
+ return [0]
63
+ }
64
+
65
+ const stepSize = calculateStepSize(max)
66
+ const labels = []
67
+
68
+ // Generate labels including 0 up to numSteps
69
+ const numSteps = Math.floor(max / stepSize)
70
+ for (let i = 0; i <= numSteps; i++) {
71
+ labels.push(i * stepSize)
72
+ }
73
+
74
+ // Ensure we always have at least 2 labels (0 and max)
75
+ if (labels.length < 2) {
76
+ labels.push(max)
77
+ }
78
+
79
+ return labels
80
+ })
81
+
82
+ const yAxisHeight = computed(() => {
83
+ return '100%'
84
+ })
85
+
86
+ const yAxisWidth = computed(() => {
87
+ return !!props.yAxisTitle || props.isBottomFieldsShown ? '70px' : '60px'
88
+ })
89
+
90
+ const isChartControlsShown = (position) => {
91
+ return (
92
+ props.chartControlsPosition === position &&
93
+ (props.isLegendShown || props.splitButtonOptions.length)
94
+ )
95
+ }
96
+
97
+ return {
98
+ yAxisLabels,
99
+ yAxisHeight,
100
+ yAxisWidth,
101
+ isChartControlsShown,
102
+ paddedMaxValue,
103
+ }
104
+ }
@@ -0,0 +1,114 @@
1
+ import { computed } from 'vue'
2
+ import theme from '../../../assets/theme'
3
+
4
+ export function useChartData(props, paddedMaxValue) {
5
+ const GRADIENT_COLORS = theme.chartGradients
6
+
7
+ // Get stacked colors based on number of segments
8
+ const getStackedColors = computed(() => {
9
+ const colorMap = new Map()
10
+
11
+ return (totalSegments) => {
12
+ if (colorMap.has(totalSegments)) {
13
+ return colorMap.get(totalSegments)
14
+ }
15
+
16
+ let colors
17
+ switch (totalSegments) {
18
+ case 2:
19
+ colors = [GRADIENT_COLORS.stacked[3], GRADIENT_COLORS.stacked[0]]
20
+ break
21
+ case 3:
22
+ colors = [
23
+ GRADIENT_COLORS.stacked[5],
24
+ GRADIENT_COLORS.stacked[3],
25
+ GRADIENT_COLORS.stacked[1],
26
+ ]
27
+ break
28
+ case 4:
29
+ colors = [
30
+ GRADIENT_COLORS.stacked[5],
31
+ GRADIENT_COLORS.stacked[3],
32
+ GRADIENT_COLORS.stacked[1],
33
+ GRADIENT_COLORS.stacked[0],
34
+ ]
35
+ break
36
+ case 5:
37
+ colors = [
38
+ GRADIENT_COLORS.stacked[5],
39
+ GRADIENT_COLORS.stacked[3],
40
+ GRADIENT_COLORS.stacked[2],
41
+ GRADIENT_COLORS.stacked[1],
42
+ GRADIENT_COLORS.stacked[0],
43
+ ]
44
+ break
45
+ case 6:
46
+ colors = [...GRADIENT_COLORS.stacked].reverse()
47
+ break
48
+ default:
49
+ colors = [GRADIENT_COLORS.stacked[0]]
50
+ }
51
+
52
+ colorMap.set(totalSegments, colors)
53
+ return colors
54
+ }
55
+ })
56
+
57
+ // Normalize data for rendering
58
+ const normalizedData = computed(() => {
59
+ if (!props.data?.length) return []
60
+
61
+ if (props.series.length) {
62
+ const stackedColors = [
63
+ ...getStackedColors.value(props.series.length),
64
+ ].reverse()
65
+ return props.data.map((item) => {
66
+ let accumulated = 0
67
+ return {
68
+ label: item.label,
69
+ segments: [...props.series].reverse().map((series, index) => {
70
+ const value =
71
+ series.data.find((d) => d.label === item.label)?.value || 0
72
+ accumulated += value
73
+ return {
74
+ value,
75
+ percentage: Number(
76
+ ((accumulated / paddedMaxValue.value) * 100).toFixed(4)
77
+ ),
78
+ gradientFrom: stackedColors[index].from,
79
+ gradientTo: stackedColors[index].to,
80
+ name: series.name,
81
+ }
82
+ }),
83
+ total: accumulated,
84
+ }
85
+ })
86
+ }
87
+
88
+ return props.data.map((item) => ({
89
+ label: item.label,
90
+ segments: [
91
+ {
92
+ value: item.value,
93
+ percentage: Number(
94
+ ((item.value / paddedMaxValue.value) * 100).toFixed(4)
95
+ ),
96
+ gradientFrom: GRADIENT_COLORS.simple.from,
97
+ gradientTo: GRADIENT_COLORS.simple.to,
98
+ name: 'Value',
99
+ },
100
+ ],
101
+ }))
102
+ })
103
+
104
+ // Calculate total value of segments
105
+ const getTotalSegmentValue = (segments) => {
106
+ return segments.reduce((sum, segment) => sum + segment.value, 0)
107
+ }
108
+
109
+ return {
110
+ normalizedData,
111
+ getStackedColors,
112
+ getTotalSegmentValue,
113
+ }
114
+ }
@@ -0,0 +1,61 @@
1
+ import { ref, onMounted, onUnmounted } from 'vue'
2
+
3
+ export function useChartScroll(
4
+ chartId,
5
+ isInputFocused,
6
+ focusedBarData,
7
+ showTooltipFromInput
8
+ ) {
9
+ const chartContent = ref(null)
10
+ const chartContentWidth = ref(0)
11
+
12
+ const updateChartContentWidth = () => {
13
+ if (chartContent.value) {
14
+ chartContentWidth.value = chartContent.value.$el.offsetWidth
15
+ }
16
+ }
17
+
18
+ // Handle scroll from chart
19
+ const handleChartScroll = (event) => {
20
+ const fieldsContainer = document.querySelector(
21
+ `.fields-container-${chartId}`
22
+ )
23
+ if (fieldsContainer) {
24
+ fieldsContainer.scrollLeft = event.target.scrollLeft
25
+ }
26
+
27
+ if (isInputFocused.value && focusedBarData.value) {
28
+ showTooltipFromInput({
29
+ seriesName: focusedBarData.value.segments[0].name,
30
+ label: focusedBarData.value.label,
31
+ })
32
+ }
33
+ }
34
+
35
+ // Handle scroll from bottom fields
36
+ const handleBottomFieldsScroll = (scrollLeft) => {
37
+ const chartContainer = document.querySelector(
38
+ `.chart-scroll-container-${chartId}`
39
+ )
40
+ if (chartContainer) {
41
+ chartContainer.scrollLeft = scrollLeft
42
+ }
43
+ }
44
+
45
+ onMounted(() => {
46
+ updateChartContentWidth()
47
+ window.addEventListener('resize', updateChartContentWidth)
48
+ })
49
+
50
+ onUnmounted(() => {
51
+ window.removeEventListener('resize', updateChartContentWidth)
52
+ })
53
+
54
+ return {
55
+ chartContent,
56
+ chartContentWidth,
57
+ updateChartContentWidth,
58
+ handleChartScroll,
59
+ handleBottomFieldsScroll,
60
+ }
61
+ }
@@ -0,0 +1,75 @@
1
+ import { ref, computed, watch } from 'vue'
2
+ import theme from '../../../assets/theme'
3
+
4
+ export function useSelection(props, normalizedData, emit) {
5
+ const selectedIndices = ref({ start: 0, end: props.selectionSize })
6
+ const currentSelection = ref([])
7
+
8
+ watch(
9
+ () => [props.selectionSize, props.isSelectionEnabled],
10
+ ([newSize, isEnabled]) => {
11
+ if (isEnabled) {
12
+ selectedIndices.value = {
13
+ start: 0,
14
+ end: newSize,
15
+ }
16
+ }
17
+ },
18
+ { immediate: true }
19
+ )
20
+
21
+ // Update visual selection
22
+ const updateSelectedBars = ({ startIndex, endIndex }) => {
23
+ selectedIndices.value = { start: startIndex, end: endIndex }
24
+ currentSelection.value = normalizedData.value.slice(startIndex, endIndex)
25
+ }
26
+
27
+ const handleSelectionDragEnd = () => {
28
+ emit('selection-change', currentSelection.value)
29
+ }
30
+
31
+ // Check if bar is a selection boundary
32
+ const isSelectionBoundary = computed(() => {
33
+ return (index) => {
34
+ if (!props.isSelectionEnabled) return false
35
+ return (
36
+ index === selectedIndices.value.start ||
37
+ index === selectedIndices.value.end - 1
38
+ )
39
+ }
40
+ })
41
+
42
+ // Get segment gradient based on selection state
43
+ const getSegmentGradient = computed(() => {
44
+ return (index, segment) => {
45
+ if (!props.isSelectionEnabled) {
46
+ return {
47
+ from: segment.gradientFrom,
48
+ to: segment.gradientTo,
49
+ }
50
+ }
51
+
52
+ const isSelected =
53
+ index >= selectedIndices.value.start &&
54
+ index < selectedIndices.value.end
55
+
56
+ return isSelected
57
+ ? {
58
+ from: segment.gradientFrom,
59
+ to: segment.gradientTo,
60
+ }
61
+ : {
62
+ from: theme.semanticColors.grey[500],
63
+ to: theme.semanticColors.grey[500],
64
+ }
65
+ }
66
+ })
67
+
68
+ return {
69
+ selectedIndices,
70
+ updateSelectedBars,
71
+ handleSelectionDragEnd,
72
+ isSelectionBoundary,
73
+ getSegmentGradient,
74
+ }
75
+ }
@@ -0,0 +1,100 @@
1
+ import { ref, onUnmounted } from 'vue'
2
+ import isObjectEqual from '../../../helpers/isObjectEqual'
3
+
4
+ export function useTooltip(chartId, normalizedData) {
5
+ const showTooltipContent = ref(false)
6
+ const tooltipData = ref(null)
7
+ const tooltipStyle = ref({})
8
+
9
+ const isInputFocused = ref(false)
10
+ const focusedBarData = ref(null)
11
+ const tooltipTimeout = ref(null)
12
+
13
+ const showTooltip = (item, event, series) => {
14
+ if (isInputFocused.value) return
15
+
16
+ if (!showTooltipContent.value) {
17
+ showTooltipContent.value = true
18
+ }
19
+ if (isObjectEqual(item, tooltipData.value)) return
20
+
21
+ tooltipData.value = { ...item }
22
+
23
+ const targetElement = series.length
24
+ ? event.target
25
+ .closest('.bar-group')
26
+ .querySelector('.bar-segment:last-child')
27
+ : event.target
28
+ const rect = targetElement.getBoundingClientRect()
29
+
30
+ tooltipStyle.value = {
31
+ left: `${rect.left + rect.width / 2}px`,
32
+ top: `${rect.top - 0}px`,
33
+ }
34
+ }
35
+
36
+ const showTooltipFromInput = ({ seriesName, label }) => {
37
+ if (tooltipTimeout.value) {
38
+ clearTimeout(tooltipTimeout.value)
39
+ tooltipTimeout.value = null
40
+ }
41
+
42
+ isInputFocused.value = true
43
+ const barData = normalizedData.value.find((item) => item.label === label)
44
+
45
+ if (!barData) return
46
+ focusedBarData.value = barData
47
+ const barElement = document.querySelector(
48
+ `.barchart-${chartId} .bar-group:nth-child(${
49
+ normalizedData.value.indexOf(barData) + 1
50
+ })`
51
+ )
52
+
53
+ if (!barElement) return
54
+ // Get the last bar segment, samee as hover behavior
55
+ const targetElement = barElement.querySelector('.bar-segment:last-child')
56
+ const rect = targetElement.getBoundingClientRect()
57
+
58
+ tooltipStyle.value = {
59
+ left: `${rect.left + rect.width / 2}px`,
60
+ top: `${rect.top - 0}px`,
61
+ }
62
+ showTooltipContent.value = true
63
+ tooltipData.value = barData
64
+ }
65
+
66
+ // Small delay to prevent tooltip flicker when switching between inputs
67
+ const handleInputBlurAll = () => {
68
+ tooltipTimeout.value = setTimeout(() => {
69
+ hideTooltip(true)
70
+ isInputFocused.value = false
71
+ tooltipTimeout.value = null
72
+ }, 50)
73
+ }
74
+
75
+ const hideTooltip = (isFromInputBlur = false) => {
76
+ if (typeof isFromInputBlur !== 'boolean' && isInputFocused.value) {
77
+ return
78
+ }
79
+
80
+ showTooltipContent.value = false
81
+ }
82
+
83
+ onUnmounted(() => {
84
+ if (!tooltipTimeout.value) return
85
+ clearTimeout(tooltipTimeout.value)
86
+ tooltipTimeout.value = null
87
+ })
88
+
89
+ return {
90
+ showTooltipContent,
91
+ tooltipData,
92
+ tooltipStyle,
93
+ showTooltip,
94
+ hideTooltip,
95
+ isInputFocused,
96
+ focusedBarData,
97
+ showTooltipFromInput,
98
+ handleInputBlurAll,
99
+ }
100
+ }