@gitlab/ui 62.9.3 → 62.11.0
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/CHANGELOG.md +15 -0
- package/dist/components/base/new_dropdowns/disclosure/disclosure_dropdown.js +20 -3
- package/dist/components/base/new_dropdowns/disclosure/disclosure_dropdown_item.js +1 -1
- package/dist/components/charts/legend/legend.js +51 -3
- package/dist/utility_classes.css +1 -1
- package/dist/utility_classes.css.map +1 -1
- package/package.json +2 -2
- package/src/components/base/new_dropdowns/disclosure/disclosure_dropdown.md +1 -0
- package/src/components/base/new_dropdowns/disclosure/disclosure_dropdown.spec.js +16 -0
- package/src/components/base/new_dropdowns/disclosure/disclosure_dropdown.stories.js +13 -3
- package/src/components/base/new_dropdowns/disclosure/disclosure_dropdown.vue +21 -1
- package/src/components/base/new_dropdowns/disclosure/disclosure_dropdown_item.vue +1 -2
- package/src/components/charts/area/area.spec.js +11 -10
- package/src/components/charts/legend/legend.spec.js +47 -5
- package/src/components/charts/legend/legend.stories.js +21 -1
- package/src/components/charts/legend/legend.vue +52 -6
- package/src/scss/utilities.scss +8 -0
- package/src/scss/utility-mixins/cursor.scss +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gitlab/ui",
|
|
3
|
-
"version": "62.
|
|
3
|
+
"version": "62.11.0",
|
|
4
4
|
"description": "GitLab UI Components",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
"@gitlab/eslint-plugin": "19.0.0",
|
|
90
90
|
"@gitlab/fonts": "^1.2.0",
|
|
91
91
|
"@gitlab/stylelint-config": "4.1.0",
|
|
92
|
-
"@gitlab/svgs": "3.
|
|
92
|
+
"@gitlab/svgs": "3.45.0",
|
|
93
93
|
"@rollup/plugin-commonjs": "^11.1.0",
|
|
94
94
|
"@rollup/plugin-node-resolve": "^7.1.3",
|
|
95
95
|
"@rollup/plugin-replace": "^2.3.2",
|
|
@@ -323,4 +323,20 @@ describe('GlDisclosureDropdown', () => {
|
|
|
323
323
|
expect(findBaseDropdown().props('fluidWidth')).toBe(true);
|
|
324
324
|
});
|
|
325
325
|
});
|
|
326
|
+
|
|
327
|
+
describe('auto closing', () => {
|
|
328
|
+
it('closes the dropdown when `autoClose` is set on item click', () => {
|
|
329
|
+
buildWrapper({ items: mockItems });
|
|
330
|
+
const closeSpy = jest.spyOn(wrapper.vm.$refs.baseDropdown, 'closeAndFocus');
|
|
331
|
+
findListItem(0).trigger('click');
|
|
332
|
+
expect(closeSpy).toHaveBeenCalled();
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it('does not close the dropdown on item click when `autoClose` is set to `false`', () => {
|
|
336
|
+
buildWrapper({ items: mockItems, autoClose: false });
|
|
337
|
+
const closeSpy = jest.spyOn(wrapper.vm.$refs.baseDropdown, 'closeAndFocus');
|
|
338
|
+
findListItem(0).trigger('click');
|
|
339
|
+
expect(closeSpy).not.toHaveBeenCalled();
|
|
340
|
+
});
|
|
341
|
+
});
|
|
326
342
|
});
|
|
@@ -40,6 +40,7 @@ const makeBindings = (overrides = {}) =>
|
|
|
40
40
|
':toggle-aria-labelled-by': 'toggleAriaLabelledBy',
|
|
41
41
|
':list-aria-labelled-by': 'listAriaLabelledBy',
|
|
42
42
|
':fluid-width': 'fluidWidth',
|
|
43
|
+
':auto-close': 'autoClose',
|
|
43
44
|
...overrides,
|
|
44
45
|
})
|
|
45
46
|
.map(([key, value]) => `${key}="${value}"`)
|
|
@@ -216,7 +217,7 @@ export const CustomGroupsItemsAndToggle = makeGroupedExample({
|
|
|
216
217
|
</button>
|
|
217
218
|
</template>
|
|
218
219
|
<gl-disclosure-dropdown-group>
|
|
219
|
-
<gl-disclosure-dropdown-item>
|
|
220
|
+
<gl-disclosure-dropdown-item @action="closeDropdown">
|
|
220
221
|
<template #list-item>
|
|
221
222
|
<span class="gl-display-flex gl-flex-direction-column">
|
|
222
223
|
<span class="gl-font-weight-bold gl-white-space-nowrap">Orange Fox</span>
|
|
@@ -225,7 +226,7 @@ export const CustomGroupsItemsAndToggle = makeGroupedExample({
|
|
|
225
226
|
</template>
|
|
226
227
|
</gl-disclosure-dropdown-item>
|
|
227
228
|
</gl-disclosure-dropdown-group>
|
|
228
|
-
<gl-disclosure-dropdown-group bordered :group="$options.groups[0]">
|
|
229
|
+
<gl-disclosure-dropdown-group bordered :group="$options.groups[0]" @action="closeDropdown">
|
|
229
230
|
<template #list-item="{ item }">
|
|
230
231
|
<span class="gl-display-flex gl-align-items-center gl-justify-content-space-between">
|
|
231
232
|
{{item.text}}
|
|
@@ -249,7 +250,7 @@ export const CustomGroupsItemsAndToggle = makeGroupedExample({
|
|
|
249
250
|
<template #list-item>Provide feedback</template>
|
|
250
251
|
</gl-disclosure-dropdown-item>
|
|
251
252
|
</gl-disclosure-dropdown-group>
|
|
252
|
-
<gl-disclosure-dropdown-group bordered :group="$options.groups[1]"/>
|
|
253
|
+
<gl-disclosure-dropdown-group bordered :group="$options.groups[1]" @action="closeDropdown"/>
|
|
253
254
|
`,
|
|
254
255
|
{
|
|
255
256
|
after: `
|
|
@@ -266,11 +267,19 @@ export const CustomGroupsItemsAndToggle = makeGroupedExample({
|
|
|
266
267
|
};
|
|
267
268
|
},
|
|
268
269
|
methods: {
|
|
270
|
+
closeDropdown() {
|
|
271
|
+
this.$refs.disclosure.closeAndFocus();
|
|
272
|
+
},
|
|
269
273
|
toggleModalVisibility(value) {
|
|
270
274
|
this.feedBackModalVisible = value;
|
|
275
|
+
this.closeDropdown();
|
|
271
276
|
},
|
|
272
277
|
toggleNewNavigation() {
|
|
273
278
|
this.newNavigation = !this.newNavigation;
|
|
279
|
+
// eslint-disable-next-line no-restricted-globals
|
|
280
|
+
setTimeout(() => {
|
|
281
|
+
this.closeDropdown();
|
|
282
|
+
}, 500);
|
|
274
283
|
},
|
|
275
284
|
},
|
|
276
285
|
groups: mockProfileGroups,
|
|
@@ -279,6 +288,7 @@ CustomGroupsItemsAndToggle.args = {
|
|
|
279
288
|
icon: 'plus-square',
|
|
280
289
|
toggleText: 'User profile menu',
|
|
281
290
|
textSrOnly: true,
|
|
291
|
+
autoClose: false,
|
|
282
292
|
};
|
|
283
293
|
CustomGroupsItemsAndToggle.decorators = [makeContainer({ height: '400px' })];
|
|
284
294
|
|
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
GL_DROPDOWN_SHOWN,
|
|
8
8
|
GL_DROPDOWN_HIDDEN,
|
|
9
9
|
GL_DROPDOWN_FOCUS_CONTENT,
|
|
10
|
+
ENTER,
|
|
11
|
+
SPACE,
|
|
10
12
|
HOME,
|
|
11
13
|
END,
|
|
12
14
|
ARROW_DOWN,
|
|
@@ -23,6 +25,8 @@ import GlDisclosureDropdownItem, { ITEM_CLASS } from './disclosure_dropdown_item
|
|
|
23
25
|
import GlDisclosureDropdownGroup from './disclosure_dropdown_group.vue';
|
|
24
26
|
import { itemsValidator, isItem, hasOnlyListItems } from './utils';
|
|
25
27
|
|
|
28
|
+
export const ITEM_SELECTOR = `.${ITEM_CLASS}`;
|
|
29
|
+
|
|
26
30
|
export default {
|
|
27
31
|
name: 'GlDisclosureDropdown',
|
|
28
32
|
events: {
|
|
@@ -183,6 +187,14 @@ export default {
|
|
|
183
187
|
required: false,
|
|
184
188
|
default: false,
|
|
185
189
|
},
|
|
190
|
+
/**
|
|
191
|
+
* Close the dropdown on item click (action)
|
|
192
|
+
*/
|
|
193
|
+
autoClose: {
|
|
194
|
+
type: Boolean,
|
|
195
|
+
required: false,
|
|
196
|
+
default: true,
|
|
197
|
+
},
|
|
186
198
|
},
|
|
187
199
|
data() {
|
|
188
200
|
return {
|
|
@@ -241,6 +253,8 @@ export default {
|
|
|
241
253
|
this.focusNextItem(event, elements, -1);
|
|
242
254
|
} else if (code === ARROW_DOWN) {
|
|
243
255
|
this.focusNextItem(event, elements, 1);
|
|
256
|
+
} else if (code === ENTER || code === SPACE) {
|
|
257
|
+
this.handleAutoClose(event);
|
|
244
258
|
} else {
|
|
245
259
|
stop = false;
|
|
246
260
|
}
|
|
@@ -250,7 +264,7 @@ export default {
|
|
|
250
264
|
}
|
|
251
265
|
},
|
|
252
266
|
getFocusableListItemElements() {
|
|
253
|
-
const items = this.$refs.content?.querySelectorAll(
|
|
267
|
+
const items = this.$refs.content?.querySelectorAll(ITEM_SELECTOR);
|
|
254
268
|
return filterVisible(Array.from(items || []));
|
|
255
269
|
},
|
|
256
270
|
focusNextItem(event, elements, offset) {
|
|
@@ -276,6 +290,11 @@ export default {
|
|
|
276
290
|
*/
|
|
277
291
|
this.$emit('action', action);
|
|
278
292
|
},
|
|
293
|
+
handleAutoClose(e) {
|
|
294
|
+
if (this.autoClose && e.target.closest(ITEM_SELECTOR)) {
|
|
295
|
+
this.closeAndFocus();
|
|
296
|
+
}
|
|
297
|
+
},
|
|
279
298
|
uniqueItemId() {
|
|
280
299
|
return uniqueId(`disclosure-item-`);
|
|
281
300
|
},
|
|
@@ -324,6 +343,7 @@ export default {
|
|
|
324
343
|
class="gl-new-dropdown-contents"
|
|
325
344
|
tabindex="-1"
|
|
326
345
|
@keydown="onKeydown"
|
|
346
|
+
@click="handleAutoClose"
|
|
327
347
|
>
|
|
328
348
|
<slot>
|
|
329
349
|
<template v-for="(item, index) in items">
|
|
@@ -76,11 +76,10 @@ export default {
|
|
|
76
76
|
const { code } = event;
|
|
77
77
|
|
|
78
78
|
if (code === ENTER || code === SPACE) {
|
|
79
|
-
stopEvent(event);
|
|
80
|
-
|
|
81
79
|
if (this.isCustomContent) {
|
|
82
80
|
this.action();
|
|
83
81
|
} else {
|
|
82
|
+
stopEvent(event);
|
|
84
83
|
/** Instead of simply navigating or calling the action, we want
|
|
85
84
|
* the `a/button` to be the target of the event as it might have additional attributes.
|
|
86
85
|
* E.g. `a` might have `target` attribute.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { nextTick } from 'vue';
|
|
1
2
|
import { shallowMount } from '@vue/test-utils';
|
|
2
3
|
|
|
3
4
|
import { LEGEND_LAYOUT_INLINE, LEGEND_LAYOUT_TABLE } from '~/utils/charts/constants';
|
|
@@ -41,7 +42,7 @@ describe('area component', () => {
|
|
|
41
42
|
it('emits `created`, with the chart instance', async () => {
|
|
42
43
|
createShallowWrapper();
|
|
43
44
|
|
|
44
|
-
await
|
|
45
|
+
await nextTick();
|
|
45
46
|
|
|
46
47
|
expect(wrapper.emitted('created').length).toBe(1);
|
|
47
48
|
expect(wrapper.emitted('created')[0][0]).toBe(mockChartInstance);
|
|
@@ -51,7 +52,7 @@ describe('area component', () => {
|
|
|
51
52
|
it('are hidden by default', async () => {
|
|
52
53
|
createShallowWrapper();
|
|
53
54
|
|
|
54
|
-
await
|
|
55
|
+
await nextTick();
|
|
55
56
|
|
|
56
57
|
expect(findAnnotationsTooltip().exists()).toBe(false);
|
|
57
58
|
});
|
|
@@ -68,7 +69,7 @@ describe('area component', () => {
|
|
|
68
69
|
},
|
|
69
70
|
});
|
|
70
71
|
|
|
71
|
-
await
|
|
72
|
+
await nextTick();
|
|
72
73
|
|
|
73
74
|
expect(findAnnotationsTooltip().exists()).toBe(true);
|
|
74
75
|
});
|
|
@@ -94,7 +95,7 @@ describe('area component', () => {
|
|
|
94
95
|
},
|
|
95
96
|
});
|
|
96
97
|
|
|
97
|
-
await
|
|
98
|
+
await nextTick();
|
|
98
99
|
|
|
99
100
|
expect(findAnnotationsTooltip().exists()).toBe(true);
|
|
100
101
|
});
|
|
@@ -128,7 +129,7 @@ describe('area component', () => {
|
|
|
128
129
|
|
|
129
130
|
wrapper.vm.onChartDataPointMouseOver(params);
|
|
130
131
|
|
|
131
|
-
await
|
|
132
|
+
await nextTick();
|
|
132
133
|
|
|
133
134
|
expect(findAnnotationsTooltip().html()).toContain(params.data.xAxis);
|
|
134
135
|
expect(findAnnotationsTooltip().html()).toContain(params.data.tooltipData.content);
|
|
@@ -154,7 +155,7 @@ describe('area component', () => {
|
|
|
154
155
|
|
|
155
156
|
wrapper.setData({ dataTooltipPosition: { left, top }, dataTooltipTitle });
|
|
156
157
|
|
|
157
|
-
await
|
|
158
|
+
await nextTick();
|
|
158
159
|
|
|
159
160
|
expect(findDataTooltip().props('left')).toBe(`${left}`);
|
|
160
161
|
expect(findDataTooltip().props('top')).toBe(`${top}`);
|
|
@@ -166,7 +167,7 @@ describe('area component', () => {
|
|
|
166
167
|
it('is inline by default', async () => {
|
|
167
168
|
createShallowWrapper();
|
|
168
169
|
|
|
169
|
-
await
|
|
170
|
+
await nextTick();
|
|
170
171
|
|
|
171
172
|
expect(findLegend().props('layout')).toBe(LEGEND_LAYOUT_INLINE);
|
|
172
173
|
});
|
|
@@ -178,7 +179,7 @@ describe('area component', () => {
|
|
|
178
179
|
},
|
|
179
180
|
});
|
|
180
181
|
|
|
181
|
-
await
|
|
182
|
+
await nextTick();
|
|
182
183
|
|
|
183
184
|
expect(findLegend().props('layout')).toBe(LEGEND_LAYOUT_INLINE);
|
|
184
185
|
});
|
|
@@ -190,7 +191,7 @@ describe('area component', () => {
|
|
|
190
191
|
},
|
|
191
192
|
});
|
|
192
193
|
|
|
193
|
-
await
|
|
194
|
+
await nextTick();
|
|
194
195
|
|
|
195
196
|
expect(findLegend().props('layout')).toBe(LEGEND_LAYOUT_TABLE);
|
|
196
197
|
});
|
|
@@ -211,7 +212,7 @@ describe('area component', () => {
|
|
|
211
212
|
},
|
|
212
213
|
});
|
|
213
214
|
|
|
214
|
-
await
|
|
215
|
+
await nextTick();
|
|
215
216
|
|
|
216
217
|
expect(findLegend().props('seriesInfo')).toEqual(expect.arrayContaining(legendSeriesInfo));
|
|
217
218
|
});
|
|
@@ -12,7 +12,7 @@ jest.mock('echarts', () => ({
|
|
|
12
12
|
registerTheme: jest.fn(),
|
|
13
13
|
}));
|
|
14
14
|
|
|
15
|
-
const
|
|
15
|
+
const mockSeriesInfo = [
|
|
16
16
|
{
|
|
17
17
|
type: 'solid',
|
|
18
18
|
name: 'Example Title 1',
|
|
@@ -51,7 +51,7 @@ describe('chart legend component', () => {
|
|
|
51
51
|
},
|
|
52
52
|
];
|
|
53
53
|
|
|
54
|
-
const buildLegend = (propsData = {}) => {
|
|
54
|
+
const buildLegend = ({ propsData = {}, seriesInfo = mockSeriesInfo } = {}) => {
|
|
55
55
|
legendWrapper = shallowMount(Legend, {
|
|
56
56
|
propsData: {
|
|
57
57
|
...propsData,
|
|
@@ -79,13 +79,21 @@ describe('chart legend component', () => {
|
|
|
79
79
|
});
|
|
80
80
|
|
|
81
81
|
it('allows user to override max value label text using props', () => {
|
|
82
|
-
buildLegend({
|
|
82
|
+
buildLegend({
|
|
83
|
+
propsData: {
|
|
84
|
+
maxText: 'maxText',
|
|
85
|
+
},
|
|
86
|
+
});
|
|
83
87
|
|
|
84
88
|
expect(legendWrapper.text()).toMatch('maxText');
|
|
85
89
|
});
|
|
86
90
|
|
|
87
91
|
it('allows user to override average value label text using props', () => {
|
|
88
|
-
buildLegend({
|
|
92
|
+
buildLegend({
|
|
93
|
+
propsData: {
|
|
94
|
+
averageText: 'averageText',
|
|
95
|
+
},
|
|
96
|
+
});
|
|
89
97
|
|
|
90
98
|
expect(legendWrapper.text()).toMatch('averageText');
|
|
91
99
|
});
|
|
@@ -111,6 +119,36 @@ describe('chart legend component', () => {
|
|
|
111
119
|
legendWrapper.findComponent(GlChartSeriesLabel).trigger('click');
|
|
112
120
|
expect(chart.dispatchAction).toHaveBeenCalled();
|
|
113
121
|
});
|
|
122
|
+
|
|
123
|
+
it('does not dispatch a `legendToggleSelect` action when there is only one active series', () => {
|
|
124
|
+
buildLegend({ seriesInfo: [mockSeriesInfo[0]] });
|
|
125
|
+
|
|
126
|
+
legendWrapper.findComponent(GlChartSeriesLabel).trigger('click');
|
|
127
|
+
|
|
128
|
+
expect(chart.dispatchAction).toHaveBeenCalledTimes(0);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('does not dispatch a `legendToggleSelect` action on the chart when disabled=true', () => {
|
|
132
|
+
const disabledLegendItem = {
|
|
133
|
+
type: 'solid',
|
|
134
|
+
name: 'Example Title 4',
|
|
135
|
+
color: 'red',
|
|
136
|
+
data: [1, 2, 3, 4, 5],
|
|
137
|
+
disabled: true,
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const seriesInfo = [...mockSeriesInfo, disabledLegendItem];
|
|
141
|
+
const disabledLegendItemIndex = seriesInfo.length - 1;
|
|
142
|
+
|
|
143
|
+
buildLegend({ seriesInfo });
|
|
144
|
+
|
|
145
|
+
legendWrapper
|
|
146
|
+
.findAllComponents(GlChartSeriesLabel)
|
|
147
|
+
.at(disabledLegendItemIndex)
|
|
148
|
+
.trigger('click');
|
|
149
|
+
|
|
150
|
+
expect(chart.dispatchAction).toHaveBeenCalledTimes(0);
|
|
151
|
+
});
|
|
114
152
|
});
|
|
115
153
|
|
|
116
154
|
it('renders the inline layout by default', () => {
|
|
@@ -121,7 +159,11 @@ describe('chart legend component', () => {
|
|
|
121
159
|
|
|
122
160
|
describe('when setting the layout prop to table', () => {
|
|
123
161
|
beforeEach(() => {
|
|
124
|
-
buildLegend({
|
|
162
|
+
buildLegend({
|
|
163
|
+
propsData: {
|
|
164
|
+
layout: 'table',
|
|
165
|
+
},
|
|
166
|
+
});
|
|
125
167
|
legendWrapper.vm.$nextTick();
|
|
126
168
|
});
|
|
127
169
|
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
SERIES_NAME_LONG,
|
|
7
7
|
SERIES_NAME_LONG_WITHOUT_SPACES,
|
|
8
8
|
} from '../../../utils/stories_constants';
|
|
9
|
+
import { gray200 } from '../../../../scss_to_js/scss_variables';
|
|
9
10
|
|
|
10
11
|
const generateOptions = (seriesLength, seriesNameType) => {
|
|
11
12
|
return {
|
|
@@ -79,6 +80,13 @@ const baseStoryOptions = {
|
|
|
79
80
|
},
|
|
80
81
|
};
|
|
81
82
|
|
|
83
|
+
const disabledLegendItem = {
|
|
84
|
+
type: 'solid',
|
|
85
|
+
name: 'Disabled Item',
|
|
86
|
+
color: gray200,
|
|
87
|
+
disabled: true,
|
|
88
|
+
};
|
|
89
|
+
|
|
82
90
|
const getStoryOptions = (seriesLength, seriesNameType, legendLayoutType) => {
|
|
83
91
|
return {
|
|
84
92
|
...baseStoryOptions,
|
|
@@ -89,16 +97,28 @@ const getStoryOptions = (seriesLength, seriesNameType, legendLayoutType) => {
|
|
|
89
97
|
};
|
|
90
98
|
|
|
91
99
|
export const Default = () => getStoryOptions(10, SERIES_NAME_SHORT);
|
|
100
|
+
export const DefaultWithDisabledLegendItem = () => {
|
|
101
|
+
const storyOptions = getStoryOptions(10, SERIES_NAME_SHORT);
|
|
92
102
|
|
|
103
|
+
storyOptions.seriesInfo = [...storyOptions.seriesInfo, disabledLegendItem];
|
|
104
|
+
|
|
105
|
+
return storyOptions;
|
|
106
|
+
};
|
|
93
107
|
export const DefaultWithLongSeriesNames = () => getStoryOptions(10, SERIES_NAME_LONG);
|
|
94
108
|
|
|
95
109
|
export const DefaultWithLongSeriesNamesAndNoSpaces = () =>
|
|
96
110
|
getStoryOptions(10, SERIES_NAME_LONG_WITHOUT_SPACES);
|
|
97
111
|
|
|
98
112
|
export const WithTabularLayout = () => getStoryOptions(10, SERIES_NAME_SHORT, LEGEND_LAYOUT_TABLE);
|
|
113
|
+
export const WithTabularLayoutAndDisabledLegendItem = () => {
|
|
114
|
+
const storyOptions = getStoryOptions(10, SERIES_NAME_SHORT, LEGEND_LAYOUT_TABLE);
|
|
115
|
+
|
|
116
|
+
storyOptions.seriesInfo = [...storyOptions.seriesInfo, disabledLegendItem];
|
|
117
|
+
|
|
118
|
+
return storyOptions;
|
|
119
|
+
};
|
|
99
120
|
export const WithTabularLayoutAndLongSeriesNames = () =>
|
|
100
121
|
getStoryOptions(10, SERIES_NAME_LONG, LEGEND_LAYOUT_TABLE);
|
|
101
|
-
|
|
102
122
|
export const WithTabularLayoutAndLongSeriesNamesWithNoSpaces = () =>
|
|
103
123
|
getStoryOptions(10, SERIES_NAME_LONG_WITHOUT_SPACES, LEGEND_LAYOUT_TABLE);
|
|
104
124
|
|
|
@@ -82,6 +82,7 @@ export default {
|
|
|
82
82
|
data() {
|
|
83
83
|
return {
|
|
84
84
|
disabledSeries: {},
|
|
85
|
+
lastActiveSeriesLabel: null,
|
|
85
86
|
};
|
|
86
87
|
},
|
|
87
88
|
computed: {
|
|
@@ -91,6 +92,15 @@ export default {
|
|
|
91
92
|
fontSize: `${this.textStyle.fontSize || defaultFontSize}px`,
|
|
92
93
|
};
|
|
93
94
|
},
|
|
95
|
+
hasOneSeriesElement() {
|
|
96
|
+
return this.seriesInfo.length === 1;
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
created() {
|
|
100
|
+
this.chart.on('legendselectchanged', this.suppressLastActiveSeriesLabelToggle);
|
|
101
|
+
},
|
|
102
|
+
beforeDestroy() {
|
|
103
|
+
this.chart.off('legendselectchanged', this.suppressLastActiveSeriesLabelToggle);
|
|
94
104
|
},
|
|
95
105
|
methods: {
|
|
96
106
|
sanitizeSeriesData(seriesData) {
|
|
@@ -115,7 +125,9 @@ export default {
|
|
|
115
125
|
seriesNameIsLong(seriesName) {
|
|
116
126
|
return seriesName.length > 120;
|
|
117
127
|
},
|
|
118
|
-
handleClick(name, key) {
|
|
128
|
+
handleClick({ name, disabled }, key) {
|
|
129
|
+
if (this.hasOneSeriesElement || this.isToggleDisabled(name, disabled)) return;
|
|
130
|
+
|
|
119
131
|
this.chart.dispatchAction({ type: 'legendToggleSelect', name });
|
|
120
132
|
this.disabledSeries = { ...this.disabledSeries, [key]: !this.disabledSeries[key] };
|
|
121
133
|
},
|
|
@@ -128,6 +140,29 @@ export default {
|
|
|
128
140
|
getColor(color, key) {
|
|
129
141
|
return this.disabledSeries[key] ? gray200 : color;
|
|
130
142
|
},
|
|
143
|
+
suppressLastActiveSeriesLabelToggle({ selected }) {
|
|
144
|
+
const selectedSeriesLabels = Object.entries(selected).filter(([, isSelected]) =>
|
|
145
|
+
Boolean(isSelected)
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
this.lastActiveSeriesLabel = null;
|
|
149
|
+
|
|
150
|
+
if (selectedSeriesLabels.length === 1) {
|
|
151
|
+
const [lastActiveSeriesLabelName] = selectedSeriesLabels[0];
|
|
152
|
+
|
|
153
|
+
this.lastActiveSeriesLabel = lastActiveSeriesLabelName;
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
/**
|
|
157
|
+
* Disables toggling legend if it is the last active one or if its data series
|
|
158
|
+
* has a disabled property set to true
|
|
159
|
+
* @param {String} name Series name
|
|
160
|
+
* @param {Boolean} isDisabled Value of the series element's disabled property
|
|
161
|
+
* @returns {boolean}
|
|
162
|
+
*/
|
|
163
|
+
isToggleDisabled(name, isDisabled) {
|
|
164
|
+
return name === this.lastActiveSeriesLabel || isDisabled;
|
|
165
|
+
},
|
|
131
166
|
},
|
|
132
167
|
legendLayoutTypes: {
|
|
133
168
|
LEGEND_LAYOUT_INLINE,
|
|
@@ -137,17 +172,23 @@ export default {
|
|
|
137
172
|
</script>
|
|
138
173
|
|
|
139
174
|
<template>
|
|
140
|
-
<div>
|
|
175
|
+
<div data-testid="gl-chart-legend">
|
|
141
176
|
<template v-if="layout === $options.legendLayoutTypes.LEGEND_LAYOUT_INLINE">
|
|
142
177
|
<div class="gl-legend-inline">
|
|
143
178
|
<div
|
|
144
179
|
v-for="(series, key) in seriesInfo"
|
|
145
180
|
:key="key"
|
|
146
|
-
:class="{
|
|
181
|
+
:class="{
|
|
182
|
+
'text-muted': disabledSeries[key],
|
|
183
|
+
'w-100': seriesNameIsLong(series.name),
|
|
184
|
+
'gl-hover-cursor-not-allowed!':
|
|
185
|
+
hasOneSeriesElement || isToggleDisabled(series.name, series.disabled),
|
|
186
|
+
}"
|
|
147
187
|
class="gl-legend-inline-series"
|
|
148
188
|
:style="fontStyle"
|
|
189
|
+
:aria-disabled="hasOneSeriesElement || isToggleDisabled(series.name, series.disabled)"
|
|
149
190
|
role="button"
|
|
150
|
-
@click="handleClick(series
|
|
191
|
+
@click="handleClick(series, key)"
|
|
151
192
|
@mouseenter="handleMouseEnter(series.name)"
|
|
152
193
|
@mouseleave="handleMouseLeave(series.name)"
|
|
153
194
|
>
|
|
@@ -182,11 +223,16 @@ export default {
|
|
|
182
223
|
<div
|
|
183
224
|
v-for="(series, key) in seriesInfo"
|
|
184
225
|
:key="key"
|
|
185
|
-
:class="{
|
|
226
|
+
:class="{
|
|
227
|
+
'text-muted': disabledSeries[key],
|
|
228
|
+
'gl-hover-cursor-not-allowed!':
|
|
229
|
+
hasOneSeriesElement || isToggleDisabled(series.name, series.disabled),
|
|
230
|
+
}"
|
|
186
231
|
class="gl-legend-tabular-row"
|
|
187
232
|
:style="fontStyle"
|
|
233
|
+
:aria-disabled="isToggleDisabled(series.name, series.disabled)"
|
|
188
234
|
role="button"
|
|
189
|
-
@click="handleClick(series
|
|
235
|
+
@click="handleClick(series, key)"
|
|
190
236
|
@mouseenter="handleMouseEnter(series.name)"
|
|
191
237
|
@mouseleave="handleMouseLeave(series.name)"
|
|
192
238
|
>
|
package/src/scss/utilities.scss
CHANGED
|
@@ -2805,10 +2805,18 @@
|
|
|
2805
2805
|
cursor: not-allowed
|
|
2806
2806
|
}
|
|
2807
2807
|
|
|
2808
|
+
.gl-hover-cursor-not-allowed:hover {
|
|
2809
|
+
cursor: not-allowed
|
|
2810
|
+
}
|
|
2811
|
+
|
|
2808
2812
|
.gl-cursor-not-allowed\! {
|
|
2809
2813
|
cursor: not-allowed !important
|
|
2810
2814
|
}
|
|
2811
2815
|
|
|
2816
|
+
.gl-hover-cursor-not-allowed\!:hover {
|
|
2817
|
+
cursor: not-allowed !important
|
|
2818
|
+
}
|
|
2819
|
+
|
|
2812
2820
|
.gl-cursor-text {
|
|
2813
2821
|
cursor: text
|
|
2814
2822
|
}
|