@eturnity/eturnity_reusable_components 8.7.5-EPIC-8593.3 → 8.7.5-EPIC-8593.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/assets/icons/collapse_arrow_icon_white.svg +1 -0
- package/src/assets/svgIcons/ac_power.svg +4 -0
- package/src/assets/svgIcons/arrow_long_left.svg +3 -0
- package/src/assets/svgIcons/arrow_long_right.svg +3 -0
- package/src/assets/svgIcons/chassis_ground_symbol.svg +27 -0
- package/src/assets/svgIcons/dc_power.svg +8 -0
- package/src/assets/svgIcons/double_arrow_long.svg +4 -0
- package/src/assets/svgIcons/ed_ac.svg +3 -0
- package/src/assets/svgIcons/ed_acgrid.svg +4 -0
- package/src/assets/svgIcons/ed_arrow_both.svg +7 -0
- package/src/assets/svgIcons/ed_arrow_left.svg +7 -0
- package/src/assets/svgIcons/ed_arrow_right.svg +7 -0
- package/src/assets/svgIcons/ed_battery.svg +10 -0
- package/src/assets/svgIcons/ed_batteryacinverter.svg +16 -0
- package/src/assets/svgIcons/ed_batteryintegratedinverter.svg +19 -0
- package/src/assets/svgIcons/ed_cirquitbreaker.svg +4 -0
- package/src/assets/svgIcons/ed_cirquitbreaker_magnetic.svg +6 -0
- package/src/assets/svgIcons/ed_cirquitbreaker_thermal.svg +4 -0
- package/src/assets/svgIcons/ed_cirquitbreaker_thermal_magnetic.svg +5 -0
- package/src/assets/svgIcons/ed_consumption.svg +3 -0
- package/src/assets/svgIcons/ed_dc.svg +6 -0
- package/src/assets/svgIcons/ed_disconnector.svg +4 -0
- package/src/assets/svgIcons/ed_disconnector_fuse.svg +4 -0
- package/src/assets/svgIcons/ed_disconnector_fuse_switch.svg +4 -0
- package/src/assets/svgIcons/ed_disconnector_loadbreak switch.svg +4 -0
- package/src/assets/svgIcons/ed_disconnector_switch.svg +4 -0
- package/src/assets/svgIcons/ed_disconnector_switch_auto_release.svg +5 -0
- package/src/assets/svgIcons/ed_energymanagement_rectangle.svg +3 -0
- package/src/assets/svgIcons/ed_evcharger.svg +19 -0
- package/src/assets/svgIcons/ed_flexiblecomponent_circle.svg +3 -0
- package/src/assets/svgIcons/ed_flexiblecomponent_square.svg +3 -0
- package/src/assets/svgIcons/ed_fuse.svg +3 -0
- package/src/assets/svgIcons/ed_ground.svg +5 -0
- package/src/assets/svgIcons/ed_heatpump.svg +4 -0
- package/src/assets/svgIcons/ed_icon_battery.svg +9 -0
- package/src/assets/svgIcons/ed_icon_circle.svg +3 -0
- package/src/assets/svgIcons/ed_icon_heatpump.svg +3 -0
- package/src/assets/svgIcons/ed_icon_inverter.svg +8 -0
- package/src/assets/svgIcons/ed_icon_optimizer.svg +11 -0
- package/src/assets/svgIcons/ed_integratedbatteryinverter.svg +10 -0
- package/src/assets/svgIcons/ed_inverter-blank.svg +3 -0
- package/src/assets/svgIcons/ed_mainsconnection.svg +3 -0
- package/src/assets/svgIcons/ed_meter_arrowleft.svg +4 -0
- package/src/assets/svgIcons/ed_meter_arrowright.svg +4 -0
- package/src/assets/svgIcons/ed_meter_bidirectional.svg +5 -0
- package/src/assets/svgIcons/ed_networkandsystemprotection_double.svg +14 -0
- package/src/assets/svgIcons/ed_networkandsystemprotection_single.svg +7 -0
- package/src/assets/svgIcons/ed_pvpanel.svg +7 -0
- package/src/assets/svgIcons/ed_rcd.svg +5 -0
- package/src/assets/svgIcons/ed_rcd_simple.svg +3 -0
- package/src/assets/svgIcons/ed_spd.svg +6 -0
- package/src/assets/svgIcons/ed_stringwithoptimizer.svg +33 -0
- package/src/assets/svgIcons/ed_stringwithoutoptimizer.svg +17 -0
- package/src/assets/svgIcons/ed_transformer.svg +3 -0
- package/src/assets/svgIcons/ground_symbol.svg +28 -0
- package/src/assets/svgIcons/house_sun.svg +3 -0
- package/src/assets/svgIcons/move_left.svg +3 -0
- package/src/assets/svgIcons/move_right.svg +3 -0
- package/src/assets/svgIcons/rectangle.svg +3 -0
- package/src/assets/svgIcons/refresh.svg +3 -0
- package/src/assets/svgIcons/text_icon.svg +3 -0
- package/src/assets/theme.js +18 -1
- package/src/components/banner/infoBanner/InfoBanner.spec.js +29 -42
- package/src/components/barchart/BottomFields.vue +253 -0
- package/src/components/barchart/ChartControls.vue +113 -0
- package/src/components/barchart/SelectionBox.vue +150 -0
- package/src/components/barchart/composables/index.js +5 -0
- package/src/components/barchart/composables/useAxisCalculations.js +104 -0
- package/src/components/barchart/composables/useChartData.js +114 -0
- package/src/components/barchart/composables/useChartScroll.js +61 -0
- package/src/components/barchart/composables/useSelection.js +75 -0
- package/src/components/barchart/composables/useTooltip.js +100 -0
- package/src/components/barchart/index.vue +385 -0
- package/src/components/barchart/styles/bottomFields.js +66 -0
- package/src/components/barchart/styles/chart.js +272 -0
- package/src/components/barchart/styles/chartControls.js +59 -0
- package/src/components/buttons/buttonIcon/index.vue +5 -0
- package/src/components/buttons/splitButtons/index.vue +86 -0
- package/src/components/collapsableInfoText/index.vue +2 -2
- package/src/components/draggableCard/defaultProps.js +16 -0
- package/src/components/draggableCard/draggableCard.spec.js +99 -0
- package/src/components/draggableCard/draggableCard.stories.js +79 -0
- package/src/components/draggableCard/index.vue +363 -0
- package/src/components/errorMessage/errorMessage.spec.js +34 -0
- package/src/components/errorMessage/errorMessage.stories.js +35 -0
- package/src/components/filter/filterSettings.vue +2 -0
- package/src/components/icon/index.vue +32 -9
- package/src/components/infoText/index.vue +2 -2
- package/src/components/infoText/infoText.spec.js +6 -1
- package/src/components/inputs/checkbox/index.vue +2 -2
- package/src/components/inputs/inputNumber/index.vue +14 -2
- package/src/components/inputs/searchInput/index.vue +18 -2
- package/src/components/inputs/select/index.vue +104 -13
- package/src/components/modals/actionModal/actionModal.spec.js +52 -0
- package/src/components/modals/actionModal/actionModal.stories.js +53 -0
- package/src/components/modals/actionModal/index.vue +6 -6
- package/src/components/modals/infoModal/index.vue +49 -19
- package/src/components/modals/infoModal/infoModal.spec.js +55 -0
- package/src/components/modals/infoModal/infoModal.stories.js +47 -0
- package/src/components/modals/modal/index.vue +16 -5
- package/src/components/pageSubtitle/PageSubtitle.stories.js +0 -1
- package/src/components/spinnerGif/index.vue +3 -3
- package/src/helpers/dateTimeFormatting.js +51 -0
- package/src/helpers/isObjectEqual.js +22 -0
- package/src/main.js +1 -0
@@ -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="M8 0.5C8.27614 0.5 8.5 0.723858 8.5 1V2.86667C8.5 3.14281 8.27614 3.36667 8 3.36667C7.72386 3.36667 7.5 3.14281 7.5 2.86667V1C7.5 0.723858 7.72386 0.5 8 0.5ZM0.5 8C0.5 7.72386 0.723858 7.5 1 7.5H2.86667C3.14281 7.5 3.36667 7.72386 3.36667 8C3.36667 8.27614 3.14281 8.5 2.86667 8.5H1C0.723858 8.5 0.5 8.27614 0.5 8ZM12.6333 8C12.6333 7.72386 12.8572 7.5 13.1333 7.5H15C15.2761 7.5 15.5 7.72386 15.5 8C15.5 8.27614 15.2761 8.5 15 8.5H13.1333C12.8572 8.5 12.6333 8.27614 12.6333 8ZM8.99987 10.8145C9.09475 11.0816 9.39061 11.2244 9.64095 11.0915C10.059 10.8696 10.4293 10.5644 10.728 10.1927C11.1343 9.6872 11.3922 9.0787 11.4728 8.43514C11.5535 7.79158 11.4537 7.13827 11.1847 6.54812C10.9156 5.95796 10.4879 5.45417 9.94915 5.09297C9.41045 4.73177 8.78197 4.52736 8.13385 4.50256C7.48573 4.47776 6.84347 4.63353 6.27873 4.9525C5.71399 5.27148 5.24899 5.7411 4.93563 6.30896C4.70526 6.72642 4.56349 7.1849 4.51696 7.65586C4.48909 7.93792 4.73355 8.15738 5.01665 8.14374C5.29975 8.1301 5.51274 7.8874 5.55771 7.60756C5.60274 7.32731 5.69588 7.05561 5.83426 6.80486C6.05573 6.40352 6.38437 6.07162 6.7835 5.84619C7.18262 5.62076 7.63654 5.51066 8.0946 5.52819C8.55265 5.54572 8.99683 5.69019 9.37756 5.94546C9.75829 6.20074 10.0606 6.55679 10.2507 6.97388C10.4409 7.39098 10.5114 7.8527 10.4544 8.30754C10.3974 8.76237 10.2152 9.19242 9.928 9.54971C9.74858 9.77293 9.5326 9.96227 9.29044 10.1103C9.04864 10.2582 8.90499 10.5474 8.99987 10.8145ZM5.17261 9.95833C5.36097 9.79137 5.64409 9.79046 5.83352 9.95621L8.82925 12.5775C8.93776 12.6724 9 12.8096 9 12.9538V14.5C9 14.7761 9.22386 15 9.5 15C9.77614 15 10 14.7761 10 14.5V12.9538C10 12.5212 9.81328 12.1097 9.48776 11.8249L6.49202 9.20363C5.92373 8.70638 5.0744 8.7091 4.5093 9.20998L1.50504 11.8728C1.18385 12.1575 1 12.5662 1 12.9954V14.5C1 14.7761 1.22386 15 1.5 15C1.77614 15 2 14.7761 2 14.5V12.9954C2 12.8523 2.06128 12.7161 2.16835 12.6212L5.17261 9.95833ZM3.40391 2.69668C3.20865 2.50142 2.89206 2.50142 2.6968 2.69668C2.50154 2.89194 2.50154 3.20852 2.6968 3.40379L4.01673 4.72372C4.212 4.91898 4.52858 4.91898 4.72384 4.72372C4.9191 4.52846 4.9191 4.21187 4.72384 4.01661L3.40391 2.69668ZM11.2764 4.01661C11.0811 4.21187 11.0811 4.52846 11.2764 4.72372C11.4716 4.91898 11.7882 4.91898 11.9835 4.72372L13.3034 3.40379C13.4987 3.20852 13.4987 2.89194 13.3034 2.69668C13.1081 2.50142 12.7916 2.50142 12.5963 2.69668L11.2764 4.01661ZM11.9835 11.2762C11.7882 11.081 11.4716 11.081 11.2764 11.2762C11.0811 11.4715 11.0811 11.7881 11.2764 11.9833L12.5963 13.3033C12.7916 13.4985 13.1081 13.4985 13.3034 13.3033C13.4987 13.108 13.4987 12.7914 13.3034 12.5962L11.9835 11.2762Z" fill="black"/>
|
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>
|
package/src/assets/theme.js
CHANGED
@@ -71,6 +71,7 @@ const theme = (() => {
|
|
71
71
|
transparentWhite2: '#ffffff32',
|
72
72
|
transparentWhite1: '#ffffff16',
|
73
73
|
transparentBlack1: '#263238e6',
|
74
|
+
transparentBlack2: '#263238e5',
|
74
75
|
transparentBlue1: '#20a4cae6',
|
75
76
|
blueElectric: '#66dffa',
|
76
77
|
eturnityGrey: '#263238',
|
@@ -179,9 +180,25 @@ const theme = (() => {
|
|
179
180
|
},
|
180
181
|
}
|
181
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
|
+
|
182
198
|
return {
|
183
199
|
colors,
|
184
200
|
semanticColors,
|
201
|
+
chartGradients,
|
185
202
|
fonts: {
|
186
203
|
mainFont: '"Figtree", sans-serif',
|
187
204
|
},
|
@@ -311,7 +328,7 @@ const theme = (() => {
|
|
311
328
|
borderColor: semanticColors.grey[300],
|
312
329
|
},
|
313
330
|
hover: {
|
314
|
-
backgroundColor: semanticColors.
|
331
|
+
backgroundColor: semanticColors.purple[100],
|
315
332
|
textColor: semanticColors.purple[700],
|
316
333
|
borderColor: semanticColors.grey[300],
|
317
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
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
40
|
-
|
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(
|
42
|
+
expect(modalTitleEl.text()).toBe('Sample title')
|
54
43
|
const modalBodyEl = wrapper.find('[data-test-id="modal_body"]')
|
55
|
-
expect(modalBodyEl.text()).toBe(
|
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
|
-
|
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>
|
@@ -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>
|