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

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 (103) 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 +35 -1
  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 +589 -0
  84. package/src/components/filterComponent/viewSettings.vue +63 -2
  85. package/src/components/filterComponent/viewSort.vue +18 -5
  86. package/src/components/icon/index.vue +32 -9
  87. package/src/components/infoText/index.vue +2 -2
  88. package/src/components/infoText/infoText.spec.js +6 -1
  89. package/src/components/inputs/inputNumber/index.vue +14 -2
  90. package/src/components/inputs/searchInput/index.vue +19 -3
  91. package/src/components/inputs/select/index.vue +108 -19
  92. package/src/components/inputs/select/option/index.vue +5 -0
  93. package/src/components/modals/actionModal/actionModal.spec.js +52 -0
  94. package/src/components/modals/actionModal/actionModal.stories.js +53 -0
  95. package/src/components/modals/actionModal/index.vue +6 -6
  96. package/src/components/modals/infoModal/index.vue +49 -19
  97. package/src/components/modals/infoModal/infoModal.spec.js +55 -0
  98. package/src/components/modals/infoModal/infoModal.stories.js +47 -0
  99. package/src/components/modals/modal/index.vue +16 -5
  100. package/src/components/pageSubtitle/PageSubtitle.stories.js +0 -1
  101. package/src/helpers/isObjectEqual.js +22 -0
  102. package/src/helpers/translateLang.js +95 -24
  103. package/src/main.js +1 -0
@@ -0,0 +1,28 @@
1
+ <svg
2
+ width="50"
3
+ height="50"
4
+ viewBox="0 0 25 50"
5
+ fill='white'
6
+ >
7
+ <defs
8
+ id="defs4" />
9
+ <g
10
+ id="layer1">
11
+ <path
12
+ d="M 0.5,24.5 L 24.5,24.5"
13
+ style="fill:lime;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
14
+ id="path4098" />
15
+ <path
16
+ d="M 4.5,27.5 L 20.5,27.5"
17
+ style="fill:lime;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
18
+ id="path4100" />
19
+ <path
20
+ d="M 16.5,30.5 L 8.5,30.5"
21
+ style="fill:lime;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
22
+ id="path4102" />
23
+ <path
24
+ d="M 12.5,24.5 L 12.5,4.5"
25
+ style="fill:lime;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
26
+ id="path4104" />
27
+ </g>
28
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M0.875 6.36364H4.55V14H9.45V6.36364H13.125L7 0L0.875 6.36364Z" fill="#B2B9C5" transform="rotate(-90 7 7)"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M0.875 6.36364H4.55V14H9.45V6.36364H13.125L7 0L0.875 6.36364Z" fill="#B2B9C5" transform="rotate(90 7 7)"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <rect x="2.5" y="2.5" width="11" height="11" rx="0.5" stroke="white"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path class="fix" d="M11.3335 12.5833C12.7477 11.5529 13.6668 9.88392 13.6668 8.00023C13.6668 4.87061 11.1298 2.33356 8.00016 2.33356H7.66683M8.00016 13.6669C4.87055 13.6669 2.3335 11.1298 2.3335 8.00023C2.3335 6.11654 3.25261 4.44751 4.66683 3.41716M7.3335 14.9336L8.66683 13.6002L7.3335 12.2669M8.66683 3.73356L7.3335 2.40023L8.66683 1.06689" stroke="#6F20DC" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M3.5 3C3.22386 3 3 3.22386 3 3.5V4.5C3 4.77614 2.77614 5 2.5 5C2.22386 5 2 4.77614 2 4.5V3.5C2 2.67157 2.67157 2 3.5 2H8H12.5C13.3284 2 14 2.67157 14 3.5V4.5C14 4.77614 13.7761 5 13.5 5C13.2239 5 13 4.77614 13 4.5V3.5C13 3.22386 12.7761 3 12.5 3H8.5V13H10.5C10.7761 13 11 13.2239 11 13.5C11 13.7761 10.7761 14 10.5 14H8H5.5C5.22386 14 5 13.7761 5 13.5C5 13.2239 5.22386 13 5.5 13H7.5V3H3.5Z" fill="white"/>
3
+ </svg>
@@ -180,9 +180,25 @@ const theme = (() => {
180
180
  },
181
181
  }
182
182
 
183
+ const chartGradients = {
184
+ simple: {
185
+ from: semanticColors.purple[500],
186
+ to: semanticColors.purple[400],
187
+ },
188
+ stacked: [
189
+ { from: '#F5EDFF', to: '#DEC5FF' },
190
+ { from: '#CAA2FF', to: '#F2E8FF' },
191
+ { from: '#B987FC', to: '#904AEF' },
192
+ { from: '#8B40F2', to: '#4A1394' },
193
+ { from: '#6A05F2', to: '#4905A5' },
194
+ { from: '#5402C3', to: '#2B0362' },
195
+ ],
196
+ }
197
+
183
198
  return {
184
199
  colors,
185
200
  semanticColors,
201
+ chartGradients,
186
202
  fonts: {
187
203
  mainFont: '"Figtree", sans-serif',
188
204
  },
@@ -312,7 +328,7 @@ const theme = (() => {
312
328
  borderColor: semanticColors.grey[300],
313
329
  },
314
330
  hover: {
315
- backgroundColor: semanticColors.purple[200],
331
+ backgroundColor: semanticColors.purple[100],
316
332
  textColor: semanticColors.purple[700],
317
333
  borderColor: semanticColors.grey[300],
318
334
  },
@@ -1,5 +1,4 @@
1
1
  /* eslint-disable */
2
- import { h } from 'vue'
3
2
  import { mount } from '@vue/test-utils'
4
3
  import InfoBanner from '@/components/banner/infoBanner'
5
4
  import theme from '@/assets/theme'
@@ -10,59 +9,47 @@ jest.mock('@/components/icon/iconCache.mjs', () => ({
10
9
  }))
11
10
 
12
11
  describe('Info Banner Component', () => {
13
- const props = {
14
- isOpen: false,
15
- buttonLabel: 'Gotcha',
16
- }
17
-
18
- const global = {
19
- provide: {
20
- theme,
21
- },
22
- }
23
-
24
- it('info banner is shown when isOpen props is true', async () => {
25
- const wrapper = mount(InfoBanner, {
26
- props,
27
- global,
12
+ let wrapper
13
+
14
+ beforeEach(() => {
15
+ wrapper = mount(InfoBanner, {
16
+ props: {
17
+ isOpen: true,
18
+ buttonLabel: 'Gotcha',
19
+ },
20
+ slots: {
21
+ title: 'Sample title',
22
+ body: 'Sample body text',
23
+ },
24
+ global: {
25
+ provide: {
26
+ theme,
27
+ },
28
+ },
29
+ })
28
30
  })
29
31
 
32
+
33
+ it('info banner is shown when isOpen props is true', async () => {
34
+
30
35
  const bannerWrapper = wrapper.find('[data-test-id="info_banner_wrapper"]')
31
36
  expect(bannerWrapper.exists()).toBe(true)
32
- expect(bannerWrapper.classes()).not.toContain('visible')
33
- expect(bannerWrapper.classes()).toContain('hidden')
34
- await wrapper.setProps({ isOpen: true })
35
- expect(bannerWrapper.classes()).toContain('visible')
36
- expect(bannerWrapper.classes()).not.toContain('hidden')
37
37
  })
38
38
 
39
- it('info banner slots is display when user passed slots content', async () => {
40
- const titleText = 'Sample title'
41
- const bodyText = 'Sample body text'
42
-
43
- const wrapper = mount(InfoBanner, {
44
- props,
45
- slots: {
46
- title: titleText,
47
- body: bodyText,
48
- },
49
- global,
50
- })
51
-
39
+
40
+ it('info banner slots is display when user passed slots content', () => {
52
41
  const modalTitleEl = wrapper.find('[data-test-id="modal_title"]')
53
- expect(modalTitleEl.text()).toBe(titleText)
42
+ expect(modalTitleEl.text()).toBe('Sample title')
54
43
  const modalBodyEl = wrapper.find('[data-test-id="modal_body"]')
55
- expect(modalBodyEl.text()).toBe(bodyText)
44
+ expect(modalBodyEl.text()).toBe('Sample body text')
56
45
  })
57
46
 
58
47
  it('info banner on-close event is emitted when modal close button is clicked', async () => {
59
- const wrapper = mount(InfoBanner, {
60
- props,
61
- global,
62
- })
63
-
64
48
  const modalCloseButton = wrapper.find('.close')
65
- await modalCloseButton.trigger('click')
49
+
50
+ modalCloseButton.trigger('click')
51
+ await wrapper.vm.$nextTick()
52
+
66
53
  expect(wrapper.emitted('on-close')).toBeTruthy()
67
54
  const emittedEvent = wrapper.emitted('on-close')
68
55
  expect(emittedEvent).toHaveLength(1)
@@ -0,0 +1,253 @@
1
+ <template>
2
+ <Container :is-chart-controls-shown-in-bottom="isChartControlsShownInBottom">
3
+ <LabelsColumn :width="yAxisWidth">
4
+ <LabelRow v-for="series in props.series" :key="series.name">
5
+ {{ series.name }}
6
+ </LabelRow>
7
+ <TotalRow v-if="props.series.length && fieldMode === 'percentage'">
8
+ {{ $gettext ? $gettext('Total (%)') : 'Total (%)' }}
9
+ </TotalRow>
10
+ <TotalRow v-if="props.series.length">
11
+ {{ $gettext ? $gettext('Total (kWh)') : 'Total (kWh)' }}
12
+ </TotalRow>
13
+ </LabelsColumn>
14
+
15
+ <FieldsContainer
16
+ :class="`fields-container-${chartId}`"
17
+ @scroll="handleFieldsScroll"
18
+ >
19
+ <FieldsWrapper>
20
+ <!-- For stacked bar chart -->
21
+ <template v-if="props.series.length">
22
+ <InputRow
23
+ v-for="series in props.series"
24
+ :key="series.name"
25
+ :data-series-name="series.name"
26
+ >
27
+ <InputGroup
28
+ v-for="(item, index) in props.data"
29
+ :bar-width="barWidth"
30
+ :is-scrollable="isScrollable"
31
+ :key="index"
32
+ >
33
+ <InputNumber
34
+ :allow-negative="false"
35
+ input-height="36px"
36
+ :number-precision="0"
37
+ :min-decimals="0"
38
+ text-align="center"
39
+ :unit-name="fieldMode === 'percentage' ? '%' : ''"
40
+ :value="getDisplayValue(series.data, item.label)"
41
+ @input-blur="handleInputBlur($event, series.name, item.label)"
42
+ @input-focus="handleInputFocus(series.name, item.label)"
43
+ />
44
+ </InputGroup>
45
+ </InputRow>
46
+
47
+ <TotalInputRow v-if="fieldMode === 'percentage'">
48
+ <InputGroup
49
+ v-for="(item, index) in props.data"
50
+ :bar-width="barWidth"
51
+ :is-scrollable="isScrollable"
52
+ :key="index"
53
+ >
54
+ <InputNumber
55
+ :allow-negative="false"
56
+ input-height="36px"
57
+ :is-read-only="true"
58
+ :number-precision="0"
59
+ :min-decimals="0"
60
+ text-align="center"
61
+ :unit-name="fieldMode === 'percentage' ? '%' : ''"
62
+ :value="calculatePercentageTotal(item.label)"
63
+ />
64
+ </InputGroup>
65
+ </TotalInputRow>
66
+
67
+ <TotalInputRow>
68
+ <InputGroup
69
+ v-for="(item, index) in props.data"
70
+ :bar-width="barWidth"
71
+ :is-scrollable="isScrollable"
72
+ :key="index"
73
+ >
74
+ <InputNumber
75
+ input-height="36px"
76
+ :is-read-only="true"
77
+ :number-precision="2"
78
+ :min-decimals="0"
79
+ text-align="center"
80
+ :value="calculateTotal(item.label)"
81
+ />
82
+ </InputGroup>
83
+ </TotalInputRow>
84
+ </template>
85
+
86
+ <!-- For simple bar chart -->
87
+ <template v-else>
88
+ <InputRow>
89
+ <InputGroup
90
+ v-for="(item, index) in props.data"
91
+ :bar-width="barWidth"
92
+ :is-scrollable="isScrollable"
93
+ :key="index"
94
+ >
95
+ <InputNumber
96
+ input-height="36px"
97
+ :min-decimals="0"
98
+ :number-precision="2"
99
+ text-align="center"
100
+ :value="item.value"
101
+ @input-blur="handleInputBlur($event, null, item.label)"
102
+ @input-focus="handleInputFocus(null, item.label)"
103
+ />
104
+ </InputGroup>
105
+ </InputRow>
106
+ </template>
107
+ </FieldsWrapper>
108
+ </FieldsContainer>
109
+ </Container>
110
+ </template>
111
+
112
+ <script setup>
113
+ import { ref } from 'vue'
114
+ import InputNumber from '../inputs/inputNumber'
115
+
116
+ import {
117
+ Container,
118
+ LabelsColumn,
119
+ LabelRow,
120
+ TotalRow,
121
+ FieldsContainer,
122
+ FieldsWrapper,
123
+ InputRow,
124
+ TotalInputRow,
125
+ InputGroup,
126
+ } from './styles/bottomFields'
127
+
128
+ const props = defineProps({
129
+ chartId: {
130
+ type: String,
131
+ required: true,
132
+ },
133
+ data: {
134
+ type: Array,
135
+ required: true,
136
+ },
137
+ series: {
138
+ type: Array,
139
+ required: true,
140
+ },
141
+ barWidth: {
142
+ type: Number,
143
+ required: true,
144
+ },
145
+ yAxisWidth: {
146
+ type: String,
147
+ required: true,
148
+ },
149
+ isScrollable: {
150
+ type: Boolean,
151
+ default: false,
152
+ },
153
+ isChartControlsShownInBottom: {
154
+ type: Boolean,
155
+ default: false,
156
+ },
157
+ fieldMode: {
158
+ type: String,
159
+ default: 'absolute',
160
+ validator: (value) => ['absolute', 'percentage'].includes(value),
161
+ },
162
+ })
163
+
164
+ const emit = defineEmits([
165
+ 'sync-scroll',
166
+ 'input-blur',
167
+ 'input-focus',
168
+ 'input-blur-all',
169
+ ])
170
+
171
+ const focusedInput = ref(null)
172
+
173
+ const handleInputFocus = (seriesName, label) => {
174
+ focusedInput.value = { seriesName, label }
175
+ emit('input-focus', { seriesName, label })
176
+ }
177
+
178
+ const calculateTotal = (label) => {
179
+ return props.series.reduce((sum, series) => {
180
+ const value = series.data.find((d) => d.label === label)?.value || 0
181
+ return sum + value
182
+ }, 0)
183
+ }
184
+
185
+ const syncScroll = (scrollLeft) => {
186
+ const container = document.querySelector(
187
+ `.fields-container-${props.chartId}`
188
+ )
189
+ if (container) {
190
+ container.scrollLeft = scrollLeft
191
+ }
192
+ }
193
+ const getDisplayValue = (seriesData, label) => {
194
+ if (props.fieldMode === 'absolute') {
195
+ return seriesData.find((d) => d.label === label)?.value || ''
196
+ }
197
+
198
+ const value = seriesData.find((d) => d.label === label)?.value || 0
199
+ const total = calculateTotal(label)
200
+ return total ? Number(((value / total) * 100).toFixed(0)) : 0
201
+ }
202
+
203
+ const calculatePercentageTotal = (label) => {
204
+ return props.series.reduce((sum, series) => {
205
+ const value = series.data.find((d) => d.label === label)?.value || 0
206
+ const total = calculateTotal(label)
207
+ const percentage = total ? Number(((value / total) * 100).toFixed(0)) : 0
208
+ return sum + percentage
209
+ }, 0)
210
+ }
211
+
212
+ const handleInputBlur = (_value, seriesName, label) => {
213
+ let value = Number(_value)
214
+
215
+ if (props.fieldMode === 'percentage') {
216
+ const total = calculateTotal(label)
217
+ value = (value / 100) * total
218
+ }
219
+
220
+ const payload = seriesName ? { seriesName, label, value } : { label, value }
221
+ emit('input-blur', payload)
222
+ focusedInput.value = null
223
+
224
+ // Check if the related target (element receiving focus) is another input in our chart
225
+ const relatedTarget = document.activeElement
226
+ const currentChartContainer = document.querySelector(
227
+ `.fields-container-${props.chartId}`
228
+ )
229
+ const isMovingToAnotherInput =
230
+ currentChartContainer?.contains(relatedTarget) &&
231
+ relatedTarget?.classList.contains('input-number')
232
+
233
+ if (!isMovingToAnotherInput) {
234
+ emit('input-blur-all')
235
+ } else {
236
+ // If moving to another input, trigger focus event manually
237
+ const seriesName =
238
+ relatedTarget.closest('[data-series-name]')?.dataset.seriesName
239
+ const label = relatedTarget.closest('[data-label]')?.dataset.label
240
+ if (seriesName && label) {
241
+ handleInputFocus(seriesName, label)
242
+ }
243
+ }
244
+ }
245
+
246
+ const handleFieldsScroll = (event) => {
247
+ emit('sync-scroll', event.target.scrollLeft)
248
+ }
249
+
250
+ defineExpose({
251
+ syncScroll,
252
+ })
253
+ </script>
@@ -0,0 +1,113 @@
1
+ <template>
2
+ <Container>
3
+ <LegendAndControlsWrapper :leftMargin="yAxisWidth">
4
+ <LeftSection>
5
+ <SplitButtonsContainer
6
+ v-if="splitButtonOptions.length"
7
+ :position="position"
8
+ >
9
+ <SplitButtons
10
+ :modelValue="selectedSplitButton"
11
+ :options="splitButtonOptions"
12
+ @click="handleSplitButtonClick"
13
+ />
14
+ </SplitButtonsContainer>
15
+ </LeftSection>
16
+
17
+ <LegendGroups v-if="isLegendShown">
18
+ <LegendRow
19
+ v-for="(row, rowIndex) in groupedSeries"
20
+ :key="rowIndex"
21
+ :rowIndex="rowIndex"
22
+ >
23
+ <LegendItem v-for="(series, index) in row" :key="series.name">
24
+ <LegendColor
25
+ :gradientFrom="
26
+ reversedColors[rowIndex * legendsItemPerRow + index].from
27
+ "
28
+ :gradientTo="
29
+ reversedColors[rowIndex * legendsItemPerRow + index].to
30
+ "
31
+ />
32
+ <LegendText>{{ series.name }}</LegendText>
33
+ </LegendItem>
34
+ </LegendRow>
35
+ </LegendGroups>
36
+ </LegendAndControlsWrapper>
37
+ </Container>
38
+ </template>
39
+
40
+ <script setup>
41
+ import { computed } from 'vue'
42
+
43
+ import SplitButtons from '../buttons/splitButtons'
44
+
45
+ import {
46
+ Container,
47
+ LegendAndControlsWrapper,
48
+ LeftSection,
49
+ SplitButtonsContainer,
50
+ LegendGroups,
51
+ LegendRow,
52
+ LegendItem,
53
+ LegendColor,
54
+ LegendText,
55
+ } from './styles/chartControls'
56
+
57
+ const props = defineProps({
58
+ position: {
59
+ type: String,
60
+ required: true,
61
+ },
62
+ yAxisWidth: {
63
+ type: String,
64
+ required: true,
65
+ },
66
+ series: {
67
+ type: Array,
68
+ default: () => [],
69
+ },
70
+ stackedColors: {
71
+ type: Function,
72
+ required: true,
73
+ },
74
+ splitButtonOptions: {
75
+ type: Array,
76
+ default: () => [],
77
+ },
78
+ selectedSplitButton: {
79
+ type: String,
80
+ default: '',
81
+ },
82
+ isLegendShown: {
83
+ type: Boolean,
84
+ default: false,
85
+ },
86
+ legendsItemPerRow: {
87
+ type: Number,
88
+ required: true,
89
+ },
90
+ })
91
+
92
+ const emit = defineEmits(['select-split-button'])
93
+
94
+ const reversedColors = computed(() =>
95
+ [...props.stackedColors(props.series.length)].reverse()
96
+ )
97
+
98
+ const groupedSeries = computed(() => {
99
+ const { series, legendsItemPerRow } = props
100
+
101
+ return series.reduce((groups, item, index) => {
102
+ const groupIndex = Math.floor(index / legendsItemPerRow)
103
+ groups[groupIndex] = groups[groupIndex] || []
104
+ groups[groupIndex].push(item)
105
+
106
+ return groups
107
+ }, [])
108
+ })
109
+
110
+ const handleSplitButtonClick = (value) => {
111
+ emit('select-split-button', value)
112
+ }
113
+ </script>