Skip to content

Commit f592a59

Browse files
authored
Merge pull request #1060 from writer/AB-211
fix(ui): use lazy loader for dropdown - AB-211
2 parents aa18638 + 09dc957 commit f592a59

File tree

4 files changed

+100
-28
lines changed

4 files changed

+100
-28
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<script lang="ts" setup>
2+
/**
3+
* This component will avoid render the slot "content" until it's visible (relying on `IntersectionObserver`)
4+
*/
5+
import { onUnmounted, ref, useTemplateRef, watch } from "vue";
6+
7+
const container = useTemplateRef("container");
8+
const isVisible = ref(false);
9+
const currentObserver = ref<IntersectionObserver | undefined>();
10+
11+
function observeVisibility(element: Element) {
12+
// close the old IntersectionObserver if exists
13+
currentObserver.value?.disconnect();
14+
15+
const observer = new IntersectionObserver((entries) => {
16+
// check if any observed elements are visible
17+
const visible = entries.some((entry) => entry.isIntersecting);
18+
if (!visible) return;
19+
20+
// the element is visible, we don't need the observer anymore
21+
observer.unobserve(element);
22+
isVisible.value = true;
23+
});
24+
25+
// observe the element and save the observer to close it if necessary
26+
observer.observe(element);
27+
currentObserver.value = observer;
28+
}
29+
30+
watch(
31+
container,
32+
() => {
33+
// handle if the browser doesn't support IntersectionObserver
34+
if (!window.IntersectionObserver) return (isVisible.value = true);
35+
// if HTML ref exists, start to observe visibility
36+
if (container.value) observeVisibility(container.value);
37+
},
38+
{ immediate: true },
39+
);
40+
41+
// be sure to close IntersectionObserver if component is destroyed
42+
onUnmounted(() => currentObserver.value?.disconnect());
43+
</script>
44+
45+
<template>
46+
<div ref="container" class="SharedLazyLoader">
47+
<slot v-if="isVisible" name="content" />
48+
<slot v-else name="spinner" />
49+
</div>
50+
</template>

src/ui/src/wds/WdsDropdownMenu.spec.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { shallowMount } from "@vue/test-utils";
1+
import { mount } from "@vue/test-utils";
22
import { describe, expect, it } from "vitest";
33

44
import WdsDropdownMenu from "./WdsDropdownMenu.vue";
@@ -15,12 +15,17 @@ describe("WdsDropdownMenu", () => {
1515

1616
describe("single mode", () => {
1717
it("should select an option", async () => {
18-
const wrapper = shallowMount(WdsDropdownMenu, {
18+
const wrapper = mount(WdsDropdownMenu, {
1919
props: {
2020
selected: "a",
2121
enableMultiSelection: false,
2222
options,
2323
},
24+
global: {
25+
stubs: {
26+
WdsDropdownMenuItem: true,
27+
},
28+
},
2429
});
2530

2631
await wrapper
@@ -34,12 +39,17 @@ describe("WdsDropdownMenu", () => {
3439

3540
describe("multiple mode", () => {
3641
it("should support multiple mode", () => {
37-
const wrapper = shallowMount(WdsDropdownMenu, {
42+
const wrapper = mount(WdsDropdownMenu, {
3843
props: {
3944
selected: ["???"],
4045
enableMultiSelection: true,
4146
options,
4247
},
48+
global: {
49+
stubs: {
50+
WdsDropdownMenuItem: true,
51+
},
52+
},
4353
});
4454

4555
wrapper.getComponent(WdsCheckbox).vm.$emit("update:modelValue");

src/ui/src/wds/WdsDropdownMenu.vue

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -49,31 +49,37 @@
4949
</button>
5050
</template>
5151

52-
<template v-else-if="enableMultiSelection">
53-
<WdsCheckbox
54-
v-for="option in optionsFiltered"
55-
:key="option.value"
56-
class="WdsDropdownMenu__checkbox"
57-
:checked="isSelected(option.value)"
58-
:label="option.label"
59-
:detail="option.detail"
60-
:data-automation-key="option.value"
61-
:disabled="option.disabled"
62-
:model-value="isSelected(option.value)"
63-
@update:model-value="onSelect(option.value)"
64-
/>
65-
</template>
66-
67-
<template v-else>
68-
<WdsDropdownMenuItem
69-
v-for="option in optionsFiltered"
70-
:key="option.value"
71-
:option="option"
72-
:data-automation-key="option.value"
73-
:selected="isSelected(option.value)"
74-
@click.stop="onSelect(option.value)"
75-
/>
76-
</template>
52+
<SharedLazyLoader
53+
v-for="option in optionsFiltered"
54+
v-else
55+
:key="option.value"
56+
>
57+
<template #spinner>
58+
<div class="WdsDropdownMenu__itemLazyLoader">
59+
<WdsSkeletonLoader />
60+
</div>
61+
</template>
62+
<template #content>
63+
<WdsCheckbox
64+
v-if="enableMultiSelection"
65+
class="WdsDropdownMenu__checkbox"
66+
:checked="isSelected(option.value)"
67+
:label="option.label"
68+
:detail="option.detail"
69+
:data-automation-key="option.value"
70+
:disabled="option.disabled"
71+
:model-value="isSelected(option.value)"
72+
@update:model-value="onSelect(option.value)"
73+
/>
74+
<WdsDropdownMenuItem
75+
v-else
76+
:option="option"
77+
:data-automation-key="option.value"
78+
:selected="isSelected(option.value)"
79+
@click.stop="onSelect(option.value)"
80+
/>
81+
</template>
82+
</SharedLazyLoader>
7783
</div>
7884
</template>
7985

@@ -101,6 +107,7 @@ import WdsIcon from "./WdsIcon.vue";
101107
import WdsSkeletonLoader from "./WdsSkeletonLoader.vue";
102108
import WdsCheckbox from "./WdsCheckbox.vue";
103109
import WdsDropdownMenuItem from "./WdsDropdownMenuItem.vue";
110+
import SharedLazyLoader from "@/components/shared/SharedLazyLoader.vue";
104111
105112
const props = defineProps({
106113
options: {
@@ -224,6 +231,10 @@ watch(searchTerm, () => emits("search", searchTerm.value));
224231
pointer-events: all;
225232
}
226233
234+
.WdsDropdownMenu__itemLazyLoader {
235+
padding: 12px;
236+
}
237+
227238
.WdsDropdownMenu__header {
228239
position: sticky;
229240
top: 0px;

tests/e2e/tests/undoRedo.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ test.describe("undo and redo", () => {
7070
await expect(page.locator(COMPONENT_LOCATOR)).toHaveText("cool text");
7171

7272
await page.locator(COMPONENT_LOCATOR).click();
73+
await page.locator('[data-automation-action="expand-settings"]').click();
7374
const actionDropdown = page
7475
.locator(".BuilderSettings")
7576
.locator('[data-automation-action="settings-actions-dropdown"]');

0 commit comments

Comments
 (0)