@gitlab/ui 52.6.0 → 52.7.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 +14 -0
- package/dist/components/base/accordion/accordion_item.js +13 -13
- package/dist/components/base/new_dropdowns/listbox/listbox.js +61 -2
- package/dist/index.css +1 -1
- package/dist/index.css.map +1 -1
- package/dist/utility_classes.css +1 -1
- package/dist/utility_classes.css.map +1 -1
- package/package.json +3 -3
- package/src/components/base/accordion/accordion.stories.js +2 -8
- package/src/components/base/accordion/accordion_item.stories.js +2 -5
- package/src/components/base/accordion/accordion_item.vue +13 -13
- package/src/components/base/new_dropdowns/listbox/listbox.spec.js +126 -0
- package/src/components/base/new_dropdowns/listbox/listbox.stories.js +58 -0
- package/src/components/base/new_dropdowns/listbox/listbox.vue +77 -1
- package/src/scss/utilities.scss +2 -0
- package/src/scss/utility-mixins/typography.scss +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gitlab/ui",
|
|
3
|
-
"version": "52.
|
|
3
|
+
"version": "52.7.0",
|
|
4
4
|
"description": "GitLab UI Components",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -85,9 +85,9 @@
|
|
|
85
85
|
"@babel/core": "^7.20.5",
|
|
86
86
|
"@babel/preset-env": "^7.20.2",
|
|
87
87
|
"@gitlab/eslint-plugin": "18.1.0",
|
|
88
|
-
"@gitlab/fonts": "^1.0.
|
|
88
|
+
"@gitlab/fonts": "^1.0.1",
|
|
89
89
|
"@gitlab/stylelint-config": "4.1.0",
|
|
90
|
-
"@gitlab/svgs": "3.
|
|
90
|
+
"@gitlab/svgs": "3.14.0",
|
|
91
91
|
"@rollup/plugin-commonjs": "^11.1.0",
|
|
92
92
|
"@rollup/plugin-node-resolve": "^7.1.3",
|
|
93
93
|
"@rollup/plugin-replace": "^2.3.2",
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { disableControls } from '../../../utils/stories_utils';
|
|
2
1
|
import readme from './accordion.md';
|
|
3
2
|
import GlAccordion from './accordion.vue';
|
|
4
3
|
import GlAccordionItem from './accordion_item.vue';
|
|
@@ -9,11 +8,11 @@ const template = `
|
|
|
9
8
|
Each accordion can be expanded or collapsed independently of others.
|
|
10
9
|
</gl-accordion-item>
|
|
11
10
|
|
|
12
|
-
<gl-accordion-item title="Item 2" :header-level="headerLevel">
|
|
11
|
+
<gl-accordion-item title="Item 2" :header-level="headerLevel" :visible="true">
|
|
13
12
|
If you want to have an accordion item to be initially visible, please see
|
|
14
13
|
<code>Initially Expanded</code> example for the <code>GLAccordionItem</code>.
|
|
15
14
|
</gl-accordion-item>
|
|
16
|
-
|
|
15
|
+
|
|
17
16
|
<gl-accordion-item title="Item 3" :header-level="headerLevel" :visible="autoCollapse">
|
|
18
17
|
If you want the other accordion items to collapse when one is open, please see
|
|
19
18
|
<code>Auto Collapse</code> example.
|
|
@@ -40,15 +39,11 @@ const Template = (args) => ({
|
|
|
40
39
|
export const Default = Template.bind({});
|
|
41
40
|
Default.args = generateProps();
|
|
42
41
|
|
|
43
|
-
export const AutoCollapse = Template.bind({});
|
|
44
|
-
AutoCollapse.args = generateProps({ autoCollapse: true });
|
|
45
|
-
|
|
46
42
|
export default {
|
|
47
43
|
title: 'base/accordion',
|
|
48
44
|
component: GlAccordion,
|
|
49
45
|
parameters: {
|
|
50
46
|
bootstrapComponent: 'b-collapse',
|
|
51
|
-
storyshots: { disable: true },
|
|
52
47
|
docs: {
|
|
53
48
|
description: {
|
|
54
49
|
component: readme,
|
|
@@ -60,6 +55,5 @@ export default {
|
|
|
60
55
|
options: [1, 2, 3, 4, 5, 6],
|
|
61
56
|
control: 'select',
|
|
62
57
|
},
|
|
63
|
-
...disableControls(['autoCollapse']),
|
|
64
58
|
},
|
|
65
59
|
};
|
|
@@ -4,7 +4,7 @@ import GlAccordionItem from './accordion_item.vue';
|
|
|
4
4
|
const template = `
|
|
5
5
|
<gl-accordion-item :title="title" :titleVisible="titleVisible" :visible="visible" :header-level="headerLevel">
|
|
6
6
|
Lorem ipsum dolor sit amet consectetur adipisicing elit. Delectus, maiores.
|
|
7
|
-
|
|
7
|
+
</gl-accordion-item>
|
|
8
8
|
`;
|
|
9
9
|
|
|
10
10
|
const defaultValue = (prop) => GlAccordionItem.props[prop].default;
|
|
@@ -34,10 +34,7 @@ const Template = (args) => ({
|
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
export const Default = Template.bind({});
|
|
37
|
-
Default.args = generateProps({ titleVisible: 'Accordion Item Title Expanded' });
|
|
38
|
-
|
|
39
|
-
export const InitiallyExpanded = Template.bind({});
|
|
40
|
-
InitiallyExpanded.args = generateProps({ visible: true, title: 'Item Content Initially Expanded' });
|
|
37
|
+
Default.args = generateProps({ visible: true, titleVisible: 'Accordion Item Title Expanded' });
|
|
41
38
|
|
|
42
39
|
export default {
|
|
43
40
|
title: 'base/accordion/accordion-item',
|
|
@@ -20,32 +20,32 @@ export default {
|
|
|
20
20
|
event: 'input',
|
|
21
21
|
},
|
|
22
22
|
props: {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Used to set the title of accordion link
|
|
25
|
+
*/
|
|
26
26
|
title: {
|
|
27
27
|
type: String,
|
|
28
28
|
required: true,
|
|
29
29
|
},
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Used to set the title of accordion link when the content is visible
|
|
32
|
+
* */
|
|
33
33
|
titleVisible: {
|
|
34
34
|
type: String,
|
|
35
35
|
default: null,
|
|
36
36
|
required: false,
|
|
37
37
|
},
|
|
38
|
-
|
|
39
|
-
When set, it will ensure the accordion item is initially visible
|
|
40
|
-
|
|
38
|
+
/**
|
|
39
|
+
* When set, it will ensure the accordion item is initially visible
|
|
40
|
+
*/
|
|
41
41
|
visible: {
|
|
42
42
|
type: Boolean,
|
|
43
43
|
default: false,
|
|
44
44
|
required: false,
|
|
45
45
|
},
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
*/
|
|
46
|
+
/**
|
|
47
|
+
* The header tag used in the accordion (h1/h2/h3/h4/h5/h6). This overrides the value provided by GlAccordion. For accessibility this should be set to an appropriate value in the context where the accordion is used.,
|
|
48
|
+
*/
|
|
49
49
|
headerLevel: {
|
|
50
50
|
type: Number,
|
|
51
51
|
required: false,
|
|
@@ -55,7 +55,7 @@ When set, it will ensure the accordion item is initially visible
|
|
|
55
55
|
},
|
|
56
56
|
},
|
|
57
57
|
/**
|
|
58
|
-
* Additional CSS class(es) to be applied to the header
|
|
58
|
+
* Additional CSS class(es) to be applied to the header
|
|
59
59
|
*/
|
|
60
60
|
headerClass: {
|
|
61
61
|
type: [String, Object, Array],
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { mount } from '@vue/test-utils';
|
|
2
2
|
import { nextTick } from 'vue';
|
|
3
|
+
import { useMockIntersectionObserver } from '~/utils/use_mock_intersection_observer';
|
|
3
4
|
import GlBaseDropdown from '../base_dropdown/base_dropdown.vue';
|
|
4
5
|
import {
|
|
5
6
|
GL_DROPDOWN_SHOWN,
|
|
@@ -9,6 +10,7 @@ import {
|
|
|
9
10
|
HOME,
|
|
10
11
|
END,
|
|
11
12
|
} from '../constants';
|
|
13
|
+
import GlIntersectionObserver from '../../../utilities/intersection_observer/intersection_observer.vue';
|
|
12
14
|
import GlListbox, { ITEM_SELECTOR } from './listbox.vue';
|
|
13
15
|
import GlListboxItem from './listbox_item.vue';
|
|
14
16
|
import GlListboxGroup from './listbox_group.vue';
|
|
@@ -25,6 +27,8 @@ describe('GlListbox', () => {
|
|
|
25
27
|
});
|
|
26
28
|
};
|
|
27
29
|
|
|
30
|
+
useMockIntersectionObserver();
|
|
31
|
+
|
|
28
32
|
const findBaseDropdown = () => wrapper.findComponent(GlBaseDropdown);
|
|
29
33
|
const findListContainer = () => wrapper.find('[role="listbox"]');
|
|
30
34
|
const findListboxItems = (root = wrapper) => root.findAllComponents(GlListboxItem);
|
|
@@ -36,6 +40,7 @@ describe('GlListbox', () => {
|
|
|
36
40
|
const findLoadingIcon = () => wrapper.find("[data-testid='listbox-search-loader']");
|
|
37
41
|
const findSRNumberOfResultsText = () => wrapper.find("[data-testid='listbox-number-of-results']");
|
|
38
42
|
const findResetButton = () => wrapper.find("[data-testid='listbox-reset-button']");
|
|
43
|
+
const findIntersectionObserver = () => wrapper.findComponent(GlIntersectionObserver);
|
|
39
44
|
|
|
40
45
|
describe('toggle text', () => {
|
|
41
46
|
describe.each`
|
|
@@ -435,4 +440,125 @@ describe('GlListbox', () => {
|
|
|
435
440
|
expect(wrapper.vm.closeAndFocus).toHaveBeenCalled();
|
|
436
441
|
});
|
|
437
442
|
});
|
|
443
|
+
|
|
444
|
+
describe('when `infiniteScroll` prop is `true`', () => {
|
|
445
|
+
it('should throw an error when items are groups', () => {
|
|
446
|
+
expect(() => {
|
|
447
|
+
buildWrapper({
|
|
448
|
+
items: mockGroups,
|
|
449
|
+
infiniteScroll: true,
|
|
450
|
+
});
|
|
451
|
+
}).toThrow(
|
|
452
|
+
'Infinite scroll does not support groups. Please set the "infiniteScroll" prop to "false"'
|
|
453
|
+
);
|
|
454
|
+
expect(wrapper).toHaveLoggedVueErrors();
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
it('renders `GlIntersectionObserver` component', () => {
|
|
458
|
+
buildWrapper({
|
|
459
|
+
headerText: 'Select assignee',
|
|
460
|
+
items: mockOptions,
|
|
461
|
+
infiniteScroll: true,
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
expect(findIntersectionObserver().exists()).toBe(true);
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
describe('when bottom of listbox is reached', () => {
|
|
468
|
+
it('emits `bottom-reached` event', () => {
|
|
469
|
+
buildWrapper({
|
|
470
|
+
items: mockOptions,
|
|
471
|
+
infiniteScroll: true,
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
findIntersectionObserver().vm.$emit('appear');
|
|
475
|
+
|
|
476
|
+
expect(wrapper.emitted('bottom-reached')).toEqual([[]]);
|
|
477
|
+
});
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
describe('when `loading` prop is `true`', () => {
|
|
481
|
+
it('does not render `GlIntersectionObserver` component', () => {
|
|
482
|
+
buildWrapper({
|
|
483
|
+
items: mockOptions,
|
|
484
|
+
infiniteScroll: true,
|
|
485
|
+
loading: true,
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
expect(findIntersectionObserver().exists()).toBe(false);
|
|
489
|
+
});
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
describe('when `searching` prop is `true`', () => {
|
|
493
|
+
it('does not render `GlIntersectionObserver` component', () => {
|
|
494
|
+
buildWrapper({
|
|
495
|
+
items: mockOptions,
|
|
496
|
+
infiniteScroll: true,
|
|
497
|
+
searching: true,
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
expect(findIntersectionObserver().exists()).toBe(false);
|
|
501
|
+
});
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
describe('when `infiniteScrollLoading` prop is `true`', () => {
|
|
505
|
+
beforeEach(() => {
|
|
506
|
+
buildWrapper({
|
|
507
|
+
items: mockOptions,
|
|
508
|
+
infiniteScroll: true,
|
|
509
|
+
infiniteScrollLoading: true,
|
|
510
|
+
});
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
it('shows loading icon', () => {
|
|
514
|
+
expect(wrapper.find('[data-testid="listbox-infinite-scroll-loader"]').exists()).toBe(true);
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
it('does not render `GlIntersectionObserver` component', () => {
|
|
518
|
+
expect(findIntersectionObserver().exists()).toBe(false);
|
|
519
|
+
});
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
describe('when `totalItems` prop is set', () => {
|
|
523
|
+
it('adds `aria-setsize` and `aria-posinset` attributes to listbox items', () => {
|
|
524
|
+
const totalItems = mockOptions.length;
|
|
525
|
+
|
|
526
|
+
buildWrapper({
|
|
527
|
+
items: mockOptions,
|
|
528
|
+
infiniteScroll: true,
|
|
529
|
+
totalItems,
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
findListboxItems().wrappers.forEach((listboxItem, index) => {
|
|
533
|
+
expect(listboxItem.attributes('aria-setsize')).toBe(totalItems.toString());
|
|
534
|
+
expect(listboxItem.attributes('aria-posinset')).toBe((index + 1).toString());
|
|
535
|
+
});
|
|
536
|
+
});
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
describe('when `totalItems` prop is not set', () => {
|
|
540
|
+
it('does not add `aria-setsize` and `aria-posinset` attributes to listbox items', () => {
|
|
541
|
+
buildWrapper({
|
|
542
|
+
items: mockOptions,
|
|
543
|
+
infiniteScroll: true,
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
findListboxItems().wrappers.forEach((listboxItem) => {
|
|
547
|
+
expect(listboxItem.attributes('aria-setsize')).toBe(undefined);
|
|
548
|
+
expect(listboxItem.attributes('aria-posinset')).toBe(undefined);
|
|
549
|
+
});
|
|
550
|
+
});
|
|
551
|
+
});
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
describe('when `infiniteScroll` prop is `false`', () => {
|
|
555
|
+
it('does not render `GlIntersectionObserver` component', () => {
|
|
556
|
+
buildWrapper({
|
|
557
|
+
items: mockOptions,
|
|
558
|
+
infiniteScroll: false,
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
expect(findIntersectionObserver().exists()).toBe(false);
|
|
562
|
+
});
|
|
563
|
+
});
|
|
438
564
|
});
|
|
@@ -13,6 +13,8 @@ import {
|
|
|
13
13
|
GlAvatar,
|
|
14
14
|
} from '../../../../index';
|
|
15
15
|
import { makeContainer } from '../../../../utils/story_decorators/container';
|
|
16
|
+
import { disableControls } from '../../../../utils/stories_utils';
|
|
17
|
+
import { setStoryTimeout } from '../../../../utils/test_utils';
|
|
16
18
|
import readme from './listbox.md';
|
|
17
19
|
import { mockOptions, mockGroups } from './mock_data';
|
|
18
20
|
import { flattenedOptions } from './utils';
|
|
@@ -28,6 +30,8 @@ const generateProps = ({
|
|
|
28
30
|
loading = defaultValue('loading'),
|
|
29
31
|
searchable = defaultValue('searchable'),
|
|
30
32
|
searching = defaultValue('searching'),
|
|
33
|
+
infiniteScroll = defaultValue('infiniteScroll'),
|
|
34
|
+
infiniteScrollLoading = defaultValue('infiniteScrollLoading'),
|
|
31
35
|
noResultsText = defaultValue('noResultsText'),
|
|
32
36
|
searchPlaceholder = defaultValue('searchPlaceholder'),
|
|
33
37
|
noCaret = defaultValue('noCaret'),
|
|
@@ -51,6 +55,8 @@ const generateProps = ({
|
|
|
51
55
|
loading,
|
|
52
56
|
searchable,
|
|
53
57
|
searching,
|
|
58
|
+
infiniteScroll,
|
|
59
|
+
infiniteScrollLoading,
|
|
54
60
|
noResultsText,
|
|
55
61
|
searchPlaceholder,
|
|
56
62
|
noCaret,
|
|
@@ -77,6 +83,8 @@ const makeBindings = (overrides = {}) =>
|
|
|
77
83
|
':loading': 'loading',
|
|
78
84
|
':searchable': 'searchable',
|
|
79
85
|
':searching': 'searching',
|
|
86
|
+
':infinite-scroll': 'infiniteScroll',
|
|
87
|
+
':infinite-scroll-loading': 'infiniteScrollLoading',
|
|
80
88
|
':no-results-text': 'noResultsText',
|
|
81
89
|
':search-placeholder': 'searchPlaceholder',
|
|
82
90
|
':no-caret': 'noCaret',
|
|
@@ -534,3 +542,53 @@ SearchableGroups.args = generateProps({
|
|
|
534
542
|
items: mockGroups,
|
|
535
543
|
});
|
|
536
544
|
SearchableGroups.decorators = [makeContainer({ height: '370px' })];
|
|
545
|
+
|
|
546
|
+
export const InfiniteScroll = (
|
|
547
|
+
args,
|
|
548
|
+
{ argTypes: { infiniteScroll, infiniteScrollLoading, items, ...argTypes } }
|
|
549
|
+
) => ({
|
|
550
|
+
props: Object.keys(argTypes),
|
|
551
|
+
components: {
|
|
552
|
+
GlListbox,
|
|
553
|
+
},
|
|
554
|
+
data() {
|
|
555
|
+
return {
|
|
556
|
+
selected: mockOptions[1].value,
|
|
557
|
+
items: mockOptions.slice(0, 10),
|
|
558
|
+
infiniteScrollLoading: false,
|
|
559
|
+
infiniteScroll: true,
|
|
560
|
+
};
|
|
561
|
+
},
|
|
562
|
+
mounted() {
|
|
563
|
+
if (this.startOpened) {
|
|
564
|
+
openListbox(this);
|
|
565
|
+
}
|
|
566
|
+
},
|
|
567
|
+
methods: {
|
|
568
|
+
onBottomReached() {
|
|
569
|
+
this.infiniteScrollLoading = true;
|
|
570
|
+
|
|
571
|
+
setStoryTimeout(() => {
|
|
572
|
+
this.items.push(...mockOptions.slice(10, 12));
|
|
573
|
+
this.infiniteScrollLoading = false;
|
|
574
|
+
this.infiniteScroll = false;
|
|
575
|
+
}, 1000);
|
|
576
|
+
},
|
|
577
|
+
},
|
|
578
|
+
template: template('', {
|
|
579
|
+
label: `<span class="gl-my-0" id="listbox-label">Select a department</span>`,
|
|
580
|
+
bindingOverrides: {
|
|
581
|
+
':items': 'items',
|
|
582
|
+
':infinite-scroll': 'infiniteScroll',
|
|
583
|
+
':infinite-scroll-loading': 'infiniteScrollLoading',
|
|
584
|
+
':total-items': 12,
|
|
585
|
+
'@bottom-reached': 'onBottomReached',
|
|
586
|
+
},
|
|
587
|
+
}),
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
InfiniteScroll.argTypes = {
|
|
591
|
+
...disableControls(['infiniteScroll', 'infiniteScrollLoading', 'items']),
|
|
592
|
+
};
|
|
593
|
+
InfiniteScroll.args = generateProps();
|
|
594
|
+
InfiniteScroll.decorators = [makeContainer({ height: '370px' })];
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
} from '../../../../utils/constants';
|
|
18
18
|
import GlButton from '../../button/button.vue';
|
|
19
19
|
import GlLoadingIcon from '../../loading_icon/loading_icon.vue';
|
|
20
|
+
import GlIntersectionObserver from '../../../utilities/intersection_observer/intersection_observer.vue';
|
|
20
21
|
import GlSearchBoxByType from '../../search_box_by_type/search_box_by_type.vue';
|
|
21
22
|
import GlBaseDropdown from '../base_dropdown/base_dropdown.vue';
|
|
22
23
|
import GlListboxItem from './listbox_item.vue';
|
|
@@ -43,6 +44,7 @@ export default {
|
|
|
43
44
|
GlSearchBoxByType,
|
|
44
45
|
GlListboxSearchInput,
|
|
45
46
|
GlLoadingIcon,
|
|
47
|
+
GlIntersectionObserver,
|
|
46
48
|
},
|
|
47
49
|
model: {
|
|
48
50
|
prop: 'selected',
|
|
@@ -215,6 +217,37 @@ export default {
|
|
|
215
217
|
required: false,
|
|
216
218
|
default: false,
|
|
217
219
|
},
|
|
220
|
+
/**
|
|
221
|
+
* Enables infinite scroll.
|
|
222
|
+
* When set to `true`, the `@bottom-reached` event will be fired when
|
|
223
|
+
* the bottom of the listbox is scrolled to.
|
|
224
|
+
* Does not support groups.
|
|
225
|
+
*/
|
|
226
|
+
infiniteScroll: {
|
|
227
|
+
type: Boolean,
|
|
228
|
+
required: false,
|
|
229
|
+
default: false,
|
|
230
|
+
},
|
|
231
|
+
/**
|
|
232
|
+
* This prop is used for infinite scroll.
|
|
233
|
+
* It represents the total number of items that exist,
|
|
234
|
+
* even if they have not yet been loaded.
|
|
235
|
+
* Do not set this prop if the total number of items is unknown.
|
|
236
|
+
*/
|
|
237
|
+
totalItems: {
|
|
238
|
+
type: Number,
|
|
239
|
+
required: false,
|
|
240
|
+
default: null,
|
|
241
|
+
},
|
|
242
|
+
/**
|
|
243
|
+
* This prop is used for infinite scroll.
|
|
244
|
+
* Set to `true` when more items are being loaded.
|
|
245
|
+
*/
|
|
246
|
+
infiniteScrollLoading: {
|
|
247
|
+
type: Boolean,
|
|
248
|
+
required: false,
|
|
249
|
+
default: false,
|
|
250
|
+
},
|
|
218
251
|
/**
|
|
219
252
|
* Message to be displayed when filtering produced no results
|
|
220
253
|
*/
|
|
@@ -298,6 +331,9 @@ export default {
|
|
|
298
331
|
}
|
|
299
332
|
return Boolean(this.selected);
|
|
300
333
|
},
|
|
334
|
+
showIntersectionObserver() {
|
|
335
|
+
return this.infiniteScroll && !this.infiniteScrollLoading && !this.loading && !this.searching;
|
|
336
|
+
},
|
|
301
337
|
},
|
|
302
338
|
watch: {
|
|
303
339
|
selected: {
|
|
@@ -324,6 +360,17 @@ export default {
|
|
|
324
360
|
},
|
|
325
361
|
},
|
|
326
362
|
},
|
|
363
|
+
created() {
|
|
364
|
+
if (
|
|
365
|
+
process.env.NODE_ENV !== 'production' &&
|
|
366
|
+
this.infiniteScroll &&
|
|
367
|
+
this.items.some((item) => !isOption(item))
|
|
368
|
+
) {
|
|
369
|
+
throw new Error(
|
|
370
|
+
'Infinite scroll does not support groups. Please set the "infiniteScroll" prop to "false"'
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
},
|
|
327
374
|
methods: {
|
|
328
375
|
open() {
|
|
329
376
|
this.$refs.baseDropdown.open();
|
|
@@ -467,6 +514,25 @@ export default {
|
|
|
467
514
|
closeAndFocus() {
|
|
468
515
|
this.$refs.baseDropdown.closeAndFocus();
|
|
469
516
|
},
|
|
517
|
+
onIntersectionObserverAppear() {
|
|
518
|
+
/**
|
|
519
|
+
* Emitted when bottom of listbox has been scrolled to.
|
|
520
|
+
* Used for infinite scroll.
|
|
521
|
+
*
|
|
522
|
+
* @event bottom-reached
|
|
523
|
+
*/
|
|
524
|
+
this.$emit('bottom-reached');
|
|
525
|
+
},
|
|
526
|
+
listboxItemMoreItemsAriaAttributes(index) {
|
|
527
|
+
if (this.totalItems === null) {
|
|
528
|
+
return {};
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
return {
|
|
532
|
+
'aria-setsize': this.totalItems,
|
|
533
|
+
'aria-posinset': index + 1,
|
|
534
|
+
};
|
|
535
|
+
},
|
|
470
536
|
isOption,
|
|
471
537
|
},
|
|
472
538
|
};
|
|
@@ -551,6 +617,7 @@ export default {
|
|
|
551
617
|
:is-selected="isSelected(item)"
|
|
552
618
|
:is-focused="isFocused(item)"
|
|
553
619
|
:is-check-centered="isCheckCentered"
|
|
620
|
+
v-bind="listboxItemMoreItemsAriaAttributes(index)"
|
|
554
621
|
@select="onSelect(item, $event)"
|
|
555
622
|
>
|
|
556
623
|
<!-- @slot Custom template of the listbox item -->
|
|
@@ -583,8 +650,17 @@ export default {
|
|
|
583
650
|
</gl-listbox-group>
|
|
584
651
|
</template>
|
|
585
652
|
</template>
|
|
653
|
+
<gl-intersection-observer
|
|
654
|
+
v-if="showIntersectionObserver"
|
|
655
|
+
@appear="onIntersectionObserverAppear"
|
|
656
|
+
/>
|
|
586
657
|
</component>
|
|
587
|
-
|
|
658
|
+
<gl-loading-icon
|
|
659
|
+
v-if="infiniteScrollLoading"
|
|
660
|
+
data-testid="listbox-infinite-scroll-loader"
|
|
661
|
+
size="md"
|
|
662
|
+
class="gl-my-3"
|
|
663
|
+
/>
|
|
588
664
|
<span
|
|
589
665
|
v-if="announceSRSearchResults"
|
|
590
666
|
data-testid="listbox-number-of-results"
|
package/src/scss/utilities.scss
CHANGED
|
@@ -7789,10 +7789,12 @@
|
|
|
7789
7789
|
}
|
|
7790
7790
|
.gl-font-monospace {
|
|
7791
7791
|
font-family: $gl-monospace-font;
|
|
7792
|
+
font-variant-ligatures: none;
|
|
7792
7793
|
}
|
|
7793
7794
|
|
|
7794
7795
|
.gl-font-monospace\! {
|
|
7795
7796
|
font-family: $gl-monospace-font !important;
|
|
7797
|
+
font-variant-ligatures: none !important;
|
|
7796
7798
|
}
|
|
7797
7799
|
|
|
7798
7800
|
.gl-font-regular {
|