diff --git a/src/components/dialogs/GroupDialog/GroupDialog.vue b/src/components/dialogs/GroupDialog/GroupDialog.vue
index 028c5f53..0161d393 100644
--- a/src/components/dialogs/GroupDialog/GroupDialog.vue
+++ b/src/components/dialogs/GroupDialog/GroupDialog.vue
@@ -150,7 +150,7 @@
size="icon-lg"
style="margin-left: 5px"
@click="clearGroupRepresentation(groupDialog.id)">
-
+
@@ -161,7 +161,7 @@
size="icon-lg"
:disabled="groupDialog.ref.privacy === 'private'"
@click="setGroupRepresentation(groupDialog.id)">
-
+
@@ -1117,6 +1117,8 @@
import {
Bell,
BellOff,
+ Bookmark,
+ BookmarkCheck,
Check,
CheckCircle,
Copy,
@@ -1128,7 +1130,6 @@
RefreshCw,
Settings,
Share2,
- Star,
Tag,
Ticket,
Trash2,
diff --git a/src/composables/useVirtualizerAnchor.js b/src/composables/useVirtualizerAnchor.js
new file mode 100644
index 00000000..b063e7d6
--- /dev/null
+++ b/src/composables/useVirtualizerAnchor.js
@@ -0,0 +1,62 @@
+import { nextTick } from 'vue';
+
+export function useVirtualizerAnchor({
+ virtualizer,
+ virtualRows,
+ scrollViewportRef
+}) {
+ const captureScrollAnchor = () => {
+ const viewport = scrollViewportRef.value;
+ const items = virtualizer.value?.getVirtualItems?.() ?? [];
+ if (!viewport || !items.length) {
+ return null;
+ }
+ const firstItem = items[0];
+ const row = virtualRows.value[firstItem.index];
+ const key = row?.key ?? firstItem.key;
+ if (typeof key === 'undefined') {
+ return null;
+ }
+ return {
+ key,
+ offset: viewport.scrollTop - firstItem.start
+ };
+ };
+
+ const restoreScrollAnchor = (anchor) => {
+ if (!anchor) {
+ return;
+ }
+ const index = virtualRows.value.findIndex(
+ (row) => row?.key === anchor.key
+ );
+ if (index === -1) {
+ return;
+ }
+ const offsetInfo = virtualizer.value?.getOffsetForIndex?.(
+ index,
+ 'start'
+ );
+ const targetStart = Array.isArray(offsetInfo) ? offsetInfo[0] : null;
+ if (typeof targetStart !== 'number') {
+ return;
+ }
+ virtualizer.value?.scrollToOffset?.(targetStart + anchor.offset);
+ };
+
+ const measureWithAnchor = (measureFn) => {
+ const anchor = captureScrollAnchor();
+ nextTick(() => {
+ if (measureFn) {
+ measureFn();
+ }
+ restoreScrollAnchor(anchor);
+ });
+ };
+
+ return {
+ captureScrollAnchor,
+ restoreScrollAnchor,
+ measureWithAnchor
+ };
+}
diff --git a/src/views/Sidebar/components/FriendsSidebar.vue b/src/views/Sidebar/components/FriendsSidebar.vue
index a03391da..aa914c7d 100644
--- a/src/views/Sidebar/components/FriendsSidebar.vue
+++ b/src/views/Sidebar/components/FriendsSidebar.vue
@@ -99,6 +99,7 @@
} from '../../../stores';
import { isRealInstance, userImage, userStatusClass } from '../../../shared/utils';
import { getFriendsLocations } from '../../../shared/utils/location.js';
+ import { useVirtualizerAnchor } from '../../../composables/useVirtualizerAnchor';
import FriendItem from './FriendItem.vue';
import Location from '../../../components/Location.vue';
@@ -422,6 +423,12 @@
};
};
+ const { measureWithAnchor } = useVirtualizerAnchor({
+ virtualizer,
+ virtualRows,
+ scrollViewportRef
+ });
+
function saveFriendsGroupStates() {
configRepository.setBool('VRCX_isFriendsGroupMe', isFriendsGroupMe.value);
configRepository.setBool('VRCX_isFriendsGroupFavorites', isVIPFriends.value);
@@ -482,7 +489,7 @@
});
watch(virtualRows, () => {
- nextTick(() => {
+ measureWithAnchor(() => {
virtualizer.value?.measure?.();
});
});
diff --git a/src/views/Sidebar/components/GroupsSidebar.vue b/src/views/Sidebar/components/GroupsSidebar.vue
index 6c55d95c..d371ee37 100644
--- a/src/views/Sidebar/components/GroupsSidebar.vue
+++ b/src/views/Sidebar/components/GroupsSidebar.vue
@@ -56,6 +56,7 @@
import { useAppearanceSettingsStore, useGroupStore } from '../../../stores';
import { convertFileUrlToImageUrl } from '../../../shared/utils';
+ import { useVirtualizerAnchor } from '../../../composables/useVirtualizerAnchor';
import Location from '../../../components/Location.vue';
@@ -168,6 +169,12 @@
transform: `translateY(${item.virtualItem.start}px)`
});
+ const { measureWithAnchor } = useVirtualizerAnchor({
+ virtualizer,
+ virtualRows,
+ scrollViewportRef
+ });
+
function getSmallGroupIconUrl(url) {
return convertFileUrlToImageUrl(url);
}
@@ -188,7 +195,7 @@
});
watch(virtualRows, () => {
- nextTick(() => {
+ measureWithAnchor(() => {
virtualizer.value?.measure?.();
});
});