CoolFace
Datasetpublic

gradio/frontend

sourceHugging Faceupdated 3h agoView on Hugging Face
1likes413kdownloads
VirtualTable.svelte380 linesDownload Raw Back to shared
1<script lang="ts">2	import { onMount, tick, createEventDispatcher } from "svelte";3 4	export let items: any[][] = [];5 6	export let max_height: number;7	export let actual_height: number;8	export let table_scrollbar_width: number;9	export let start = 0;10	export let end = 20;11	export let selected: number | false;12	export let disable_scroll = false;13	export let show_scroll_button = false;14	export let viewport: HTMLTableElement;15	export let label: string | null = null;16 17	const dispatch = createEventDispatcher<{18		scroll_top: number;19	}>();20 21	let height = "100%";22 23	let average_height = 30;24	let bottom = 0;25	let contents: HTMLTableSectionElement;26	let head_height = 0;27	let foot_height = 0;28	let height_map: number[] = [];29	let mounted: boolean;30	let rows: HTMLCollectionOf<HTMLTableRowElement>;31	let top = 0;32	let viewport_height = 200;33	let visible: { index: number; data: any[] }[] = [];34	let viewport_box: DOMRectReadOnly;35 36	$: viewport_height = viewport_box?.height || 200;37 38	const is_browser = typeof window !== "undefined";39	const raf = is_browser40		? window.requestAnimationFrame41		: (cb: (...args: any[]) => void) => cb();42 43	$: {44		if (mounted && viewport_height && viewport.offsetParent) {45			(sortedItems, raf(refresh_height_map));46		}47	}48 49	async function refresh_height_map(): Promise<void> {50		if (!viewport) {51			return;52		}53 54		if (sortedItems.length < start) {55			await scroll_to_index(sortedItems.length - 1, { behavior: "auto" });56		}57 58		const scrollTop = Math.max(0, viewport.scrollTop);59		show_scroll_button = scrollTop > 100;60		table_scrollbar_width = viewport.offsetWidth - viewport.clientWidth;61 62		// acquire height map for currently visible rows63		for (let v = 0; v < rows.length; v += 1) {64			height_map[start + v] = rows[v].getBoundingClientRect().height;65		}66		let i = 0;67		let y = head_height;68		// loop items to find new start69		while (i < sortedItems.length) {70			const row_height = height_map[i] || average_height;71			// keep a page of rows buffered above72			if (y + row_height > scrollTop - max_height) {73				start = i;74				top = y - head_height;75				break;76			}77			y += row_height;78			i += 1;79		}80 81		let content_height = head_height;82		while (i < sortedItems.length) {83			const row_height = height_map[i] || average_height;84			content_height += row_height;85			i += 1;86			// keep a page of rows buffered below87			if (content_height - head_height > 3 * max_height) {88				break;89			}90		}91 92		end = i;93		const remaining = sortedItems.length - end;94 95		const scrollbar_height = viewport.offsetHeight - viewport.clientHeight;96		if (scrollbar_height > 0) {97			content_height += scrollbar_height;98		}99 100		let filtered_height_map = height_map.filter((v) => typeof v === "number");101		average_height =102			filtered_height_map.reduce((a, b) => a + b, 0) /103				filtered_height_map.length || 30;104 105		bottom = remaining * average_height;106		if (!isFinite(bottom)) {107			bottom = 200000;108		}109		height_map.length = sortedItems.length;110		while (i < sortedItems.length) {111			i += 1;112			height_map[i] = average_height;113		}114		if (max_height && content_height > max_height) {115			actual_height = max_height;116		} else {117			actual_height = content_height;118		}119	}120 121	$: scroll_and_render(selected);122 123	async function scroll_and_render(n: number | false): Promise<void> {124		raf(async () => {125			if (typeof n !== "number") return;126			const direction = typeof n !== "number" ? false : is_in_view(n);127			if (direction === true) {128				return;129			}130			if (direction === "back") {131				await scroll_to_index(n, { behavior: "instant" });132			}133 134			if (direction === "forwards") {135				await scroll_to_index(n, { behavior: "instant" }, true);136			}137		});138	}139 140	function is_in_view(n: number): "back" | "forwards" | true {141		const current = rows && rows[n - start];142		if (!current && n < start) {143			return "back";144		}145		if (!current && n >= end - 1) {146			return "forwards";147		}148 149		const { top: viewport_top } = viewport.getBoundingClientRect();150		const { top, bottom } = current.getBoundingClientRect();151 152		if (top - viewport_top < 37) {153			return "back";154		}155 156		if (bottom - viewport_top > viewport_height) {157			return "forwards";158		}159 160		return true;161	}162 163	export async function scroll_to_index(164		index: number,165		opts: ScrollToOptions,166		align_end = false167	): Promise<void> {168		await tick();169 170		const _itemHeight = average_height;171 172		let distance = index * _itemHeight;173		if (align_end) {174			distance = distance - viewport_height + _itemHeight + head_height;175		}176 177		const scrollbar_height = viewport.offsetHeight - viewport.clientHeight;178		if (scrollbar_height > 0) {179			distance += scrollbar_height;180		}181 182		const _opts = {183			top: distance,184			behavior: "smooth" as ScrollBehavior,185			...opts186		};187 188		viewport.scrollTo(_opts);189	}190 191	$: sortedItems = items;192 193	$: visible = is_browser194		? sortedItems.slice(start, end).map((data, i) => {195				return { index: i + start, data };196			})197		: sortedItems198				.slice(0, (max_height / sortedItems.length) * average_height + 1)199				.map((data, i) => {200					return { index: i + start, data };201				});202 203	onMount(() => {204		rows = contents.children as HTMLCollectionOf<HTMLTableRowElement>;205		mounted = true;206	});207</script>208 209<svelte-virtual-table-viewport>210	<div>211		<table212			class="table"213			class:disable-scroll={disable_scroll}214			bind:this={viewport}215			bind:contentRect={viewport_box}216			on:scroll={refresh_height_map}217			style="height: {height}; --bw-svt-p-top: {top}px; --bw-svt-p-bottom: {bottom}px; --bw-svt-head-height: {head_height}px; --bw-svt-foot-height: {foot_height}px; --bw-svt-avg-row-height: {average_height}px; --max-height: {max_height}px"218		>219			{#if label && label.length !== 0}220				<caption class="sr-only">{label}</caption>221			{/if}222			<thead class="thead" bind:offsetHeight={head_height}>223				<slot name="thead" />224			</thead>225			<tbody bind:this={contents} class="tbody">226				{#if visible.length && visible[0].data.length}227					{#each visible as item (item.data[0].id)}228						<slot name="tbody" item={item.data} index={item.index}>229							<tr>230								<td>Missing Table Row</td>231							</tr>232						</slot>233					{/each}234				{/if}235			</tbody>236			<tfoot class="tfoot" bind:offsetHeight={foot_height}>237				<slot name="tfoot" />238			</tfoot>239		</table>240	</div>241</svelte-virtual-table-viewport>242 243<style type="text/css">244	table {245		position: relative;246		overflow: auto;247		-webkit-overflow-scrolling: touch;248		max-height: var(--max-height);249		box-sizing: border-box;250		display: block;251		padding: 0;252		margin: 0;253		color: var(--body-text-color);254		font-size: var(--input-text-size);255		line-height: var(--line-md);256		font-family: var(--font-mono);257		border-spacing: 0;258		width: 100%;259		scroll-snap-type: x proximity;260		border-collapse: separate;261		scrollbar-width: thin;262		scrollbar-color: rgba(128, 128, 128, 0.5) transparent;263	}264 265	table::-webkit-scrollbar {266		width: 4px;267		height: 4px;268	}269 270	table::-webkit-scrollbar-track {271		background: transparent;272	}273 274	table::-webkit-scrollbar-thumb {275		background-color: rgba(128, 128, 128, 0.5);276		border-radius: 4px;277	}278 279	table:hover {280		scrollbar-color: rgba(160, 160, 160, 0.7) transparent;281	}282 283	table:hover::-webkit-scrollbar-thumb {284		background-color: rgba(160, 160, 160, 0.7);285		border-radius: 4px;286		width: 4px;287	}288 289	@media (hover: none) {290		table {291			scrollbar-color: rgba(160, 160, 160, 0.7) transparent;292		}293 294		table::-webkit-scrollbar-thumb {295			background-color: rgba(160, 160, 160, 0.7);296			border-radius: 4px;297		}298	}299 300	@media (pointer: coarse) {301		table::-webkit-scrollbar {302			width: 8px;303			height: 8px;304		}305	}306 307	table :is(thead, tfoot, tbody) {308		display: table;309		table-layout: fixed;310		width: 100%;311		box-sizing: border-box;312	}313 314	tbody {315		overflow-x: scroll;316		overflow-y: hidden;317	}318 319	table tbody {320		padding-top: var(--bw-svt-p-top);321		padding-bottom: var(--bw-svt-p-bottom);322	}323	tbody {324		position: relative;325		box-sizing: border-box;326		border: 0px solid currentColor;327	}328 329	tbody > :global(tr:last-child) {330		border: none;331	}332 333	table :global(td) {334		scroll-snap-align: start;335	}336 337	tbody :global(td.pinned-column) {338		position: sticky;339		z-index: 3;340	}341 342	tbody :global(tr:nth-child(odd)) :global(td.pinned-column) {343		background: var(--table-odd-background-fill);344	}345 346	tbody :global(tr:nth-child(even)) :global(td.pinned-column) {347		background: var(--table-even-background-fill);348	}349 350	tbody :global(td.last-pinned) {351		border-right: 1px solid var(--border-color-primary);352	}353 354	thead {355		position: sticky;356		top: 0;357		left: 0;358		background: var(--background-fill-primary);359		z-index: 7;360	}361 362	thead :global(th) {363		background: var(--table-even-background-fill) !important;364	}365 366	thead :global(th.pinned-column) {367		position: sticky;368		z-index: 7;369		background: var(--table-even-background-fill) !important;370	}371 372	thead :global(th.last-pinned) {373		border-right: 1px solid var(--border-color-primary);374	}375 376	.table.disable-scroll {377		overflow: hidden !important;378	}379</style>380 
gradio/frontend · CoolFace