mirror of
https://github.com/ByteAtATime/raycast-linux.git
synced 2025-08-31 03:07:23 +00:00
feat(grid): virtualize grid rows
This commit implements a virtual list in the Grid component, hopefully increasing performance.
This commit is contained in:
parent
87a7f946fa
commit
58b593fb81
2 changed files with 103 additions and 30 deletions
|
@ -5,6 +5,7 @@
|
|||
import GridItem from './GridItem.svelte';
|
||||
import { useGridView } from '$lib/views';
|
||||
import { useTypedNode } from '$lib/node.svelte';
|
||||
import { VList, type VListHandle } from 'virtua/svelte';
|
||||
|
||||
type Props = {
|
||||
nodeId: number;
|
||||
|
@ -26,30 +27,41 @@
|
|||
onSearchTextChange: !!gridProps?.onSearchTextChange,
|
||||
inset: gridProps?.inset
|
||||
}));
|
||||
|
||||
let vlist: VListHandle | null = null;
|
||||
$effect(() => {
|
||||
view.vlistInstance = vlist ?? undefined;
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:window onkeydown={view.handleKeydown} />
|
||||
|
||||
<div class="flex h-full flex-col">
|
||||
<div class="flex-grow overflow-y-auto px-4">
|
||||
<div
|
||||
class="grid h-full content-start gap-x-2.5 gap-y-2"
|
||||
style:grid-template-columns={`repeat(${gridProps?.columns ?? 6}, 1fr)`}
|
||||
>
|
||||
{#each view.flatList as item, index (item.id)}
|
||||
<VList bind:this={vlist} data={view.virtualListItems} getKey={(item) => item.id} class="h-full">
|
||||
{#snippet children(item)}
|
||||
<div class="h-2"></div>
|
||||
{#if item.type === 'header'}
|
||||
<GridSection props={item.props} />
|
||||
{:else if item.type === 'item'}
|
||||
<div id="item-{item.id}">
|
||||
<GridItem
|
||||
props={item.props as GridItemProps}
|
||||
selected={view.selectedItemIndex === index}
|
||||
onclick={() => view.setSelectedItemIndex(index)}
|
||||
inset={item.inset}
|
||||
/>
|
||||
{:else if item.type === 'row'}
|
||||
<div
|
||||
class="grid content-start gap-x-2.5"
|
||||
style:grid-template-columns={`repeat(${gridProps?.columns ?? 6}, 1fr)`}
|
||||
>
|
||||
{#each item.items as gridItem (gridItem.id)}
|
||||
{@const flatIndex = view.flatList.findIndex((f) => f.id === gridItem.id)}
|
||||
<div id="item-{gridItem.id}">
|
||||
<GridItem
|
||||
props={gridItem.props as GridItemProps}
|
||||
selected={view.selectedItemIndex === flatIndex}
|
||||
onclick={() => view.setSelectedItemIndex(flatIndex)}
|
||||
inset={gridItem.inset}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
{/snippet}
|
||||
</VList>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,15 @@
|
|||
import { _useBaseView, type BaseViewArgs } from './base.svelte';
|
||||
import type { GridInset, ViewSectionProps } from '$lib/props';
|
||||
import type { GridInset, ViewSectionProps, GridItemProps } from '$lib/props';
|
||||
import { focusManager } from '$lib/focus.svelte';
|
||||
import type { VListHandle } from 'virtua/svelte';
|
||||
|
||||
export type VirtualGridItem =
|
||||
| { id: string | number; type: 'header'; props: ViewSectionProps }
|
||||
| {
|
||||
id: string;
|
||||
type: 'row';
|
||||
items: { id: number; type: 'item'; props: GridItemProps; inset?: GridInset }[];
|
||||
};
|
||||
|
||||
export function useGridView(args: () => BaseViewArgs & { columns: number; inset?: GridInset }) {
|
||||
const base = _useBaseView(args, 'Grid.Item');
|
||||
|
@ -8,18 +17,15 @@ export function useGridView(args: () => BaseViewArgs & { columns: number; inset?
|
|||
|
||||
const processedFlatList = $derived.by(() => {
|
||||
const list = base.flatList;
|
||||
const newList: ((typeof list)[number] & { inset?: GridInset })[] = [];
|
||||
const newList: (typeof list)[number][] = [];
|
||||
let currentSectionInset: GridInset | undefined;
|
||||
|
||||
for (const item of list) {
|
||||
if (item.type === 'header') {
|
||||
const sectionProps = item.props as ViewSectionProps;
|
||||
if (item.id === -1) {
|
||||
// This is the synthetic section for top-level items, so it should inherit from the Grid.
|
||||
currentSectionInset = gridInset;
|
||||
} else {
|
||||
// This is a user-defined <Grid.Section>. It does not inherit.
|
||||
// If `sectionProps.inset` is undefined, it's treated as "none" by GridItem.
|
||||
currentSectionInset = sectionProps.inset;
|
||||
}
|
||||
newList.push(item);
|
||||
|
@ -30,14 +36,69 @@ export function useGridView(args: () => BaseViewArgs & { columns: number; inset?
|
|||
return newList;
|
||||
});
|
||||
|
||||
type GridMapItem = {
|
||||
const virtualListItems = $derived.by((): VirtualGridItem[] => {
|
||||
const list: VirtualGridItem[] = [];
|
||||
let currentRow: (typeof processedFlatList)[number][] = [];
|
||||
|
||||
for (const item of processedFlatList) {
|
||||
if (item.type === 'header') {
|
||||
if (currentRow.length > 0) {
|
||||
list.push({ id: `row-${list.length}`, type: 'row', items: currentRow });
|
||||
currentRow = [];
|
||||
}
|
||||
list.push({ id: `header-${item.id}`, type: 'header', props: item.props });
|
||||
} else if (item.type === 'item') {
|
||||
currentRow.push(item);
|
||||
if (currentRow.length === columns) {
|
||||
list.push({ id: `row-${list.length}`, type: 'row', items: currentRow });
|
||||
currentRow = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (currentRow.length > 0) {
|
||||
list.push({ id: `row-${list.length}`, type: 'row', items: currentRow });
|
||||
}
|
||||
return list;
|
||||
});
|
||||
|
||||
const flatIndexToVirtualRowIndexMap = $derived.by(() => {
|
||||
const map = new Map<number, number>();
|
||||
virtualListItems.forEach((vItem, vIndex) => {
|
||||
if (vItem.type === 'row') {
|
||||
vItem.items.forEach((item) => {
|
||||
const flatIndex = processedFlatList.findIndex((f) => f.id === item.id);
|
||||
if (flatIndex !== -1) {
|
||||
map.set(flatIndex, vIndex);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return map;
|
||||
});
|
||||
|
||||
let vlistInstance = $state<VListHandle | undefined>();
|
||||
|
||||
$effect(() => {
|
||||
if (base.selectedItemIndex >= 0 && vlistInstance) {
|
||||
const virtualRowIndex = flatIndexToVirtualRowIndexMap.get(base.selectedItemIndex);
|
||||
if (virtualRowIndex !== undefined) {
|
||||
vlistInstance.scrollToIndex(virtualRowIndex, { align: 'nearest' });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const gridMap: {
|
||||
flatListIndex: number;
|
||||
sectionIndex: number;
|
||||
rowIndex: number;
|
||||
colIndex: number;
|
||||
};
|
||||
const gridMap: GridMapItem[] = $derived.by(() => {
|
||||
const newGridMap: GridMapItem[] = [];
|
||||
}[] = $derived.by(() => {
|
||||
const newGridMap: {
|
||||
flatListIndex: number;
|
||||
sectionIndex: number;
|
||||
rowIndex: number;
|
||||
colIndex: number;
|
||||
}[] = [];
|
||||
let sectionIndex = -1,
|
||||
rowIndex = 0,
|
||||
colIndex = 0;
|
||||
|
@ -55,12 +116,6 @@ export function useGridView(args: () => BaseViewArgs & { columns: number; inset?
|
|||
return newGridMap;
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (base.selectedItemIndex < 0) return;
|
||||
const elementId = `item-${processedFlatList[base.selectedItemIndex]?.id}`;
|
||||
document.getElementById(elementId)?.scrollIntoView({ block: 'nearest' });
|
||||
});
|
||||
|
||||
const handleKeydown = (event: KeyboardEvent) => {
|
||||
if (focusManager.activeScope !== 'main-input') {
|
||||
return;
|
||||
|
@ -124,12 +179,18 @@ export function useGridView(args: () => BaseViewArgs & { columns: number; inset?
|
|||
get flatList() {
|
||||
return processedFlatList;
|
||||
},
|
||||
get virtualListItems() {
|
||||
return virtualListItems;
|
||||
},
|
||||
get selectedItemIndex() {
|
||||
return base.selectedItemIndex;
|
||||
},
|
||||
setSelectedItemIndex: (index: number) => {
|
||||
base.selectedItemIndex = index;
|
||||
},
|
||||
set vlistInstance(instance: VListHandle | undefined) {
|
||||
vlistInstance = instance;
|
||||
},
|
||||
handleKeydown
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue