Merge branch 'feature/scroll-clientes-proveedores' into feature/filtros-cliente-proveedor
This commit is contained in:
@@ -13,6 +13,8 @@
|
||||
loading: boolean;
|
||||
hasMore: boolean;
|
||||
loadMore: () => void;
|
||||
onRowClick?: (row: TData) => void;
|
||||
selectedId?: number | null;
|
||||
};
|
||||
|
||||
let {
|
||||
@@ -20,7 +22,9 @@
|
||||
columns,
|
||||
loading,
|
||||
hasMore,
|
||||
loadMore
|
||||
loadMore,
|
||||
onRowClick,
|
||||
selectedId = null
|
||||
}: DataTableProps<TData, TValue> = $props();
|
||||
|
||||
const table = createSvelteTable({
|
||||
@@ -33,9 +37,17 @@
|
||||
|
||||
let scrollContainer = $state<HTMLDivElement>();
|
||||
let loadingTrigger = $state<HTMLDivElement>();
|
||||
let tableContainer = $state<HTMLDivElement>();
|
||||
let bottomScrollbar = $state<HTMLDivElement>();
|
||||
let bottomScrollbarInner = $state<HTMLDivElement>();
|
||||
let isSyncingHorizontalScroll = false;
|
||||
|
||||
// Intersection Observer para detectar cuando el usuario llega al final
|
||||
onMount(() => {
|
||||
$effect(() => {
|
||||
const target = loadingTrigger;
|
||||
const root = scrollContainer;
|
||||
if (!target) return;
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
const [entry] = entries;
|
||||
@@ -44,32 +56,77 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
root: scrollContainer,
|
||||
root: root,
|
||||
threshold: 0.1
|
||||
}
|
||||
);
|
||||
|
||||
if (loadingTrigger) {
|
||||
observer.observe(loadingTrigger);
|
||||
}
|
||||
observer.observe(target);
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
});
|
||||
|
||||
function syncBottomScrollbarWidth() {
|
||||
if (!tableContainer || !bottomScrollbarInner) return;
|
||||
const width = Math.max(tableContainer.scrollWidth || 0, tableContainer.clientWidth + 1);
|
||||
bottomScrollbarInner.style.width = `${width}px`;
|
||||
}
|
||||
|
||||
function handleTableHorizontalScroll() {
|
||||
if (!tableContainer || !bottomScrollbar || isSyncingHorizontalScroll) return;
|
||||
isSyncingHorizontalScroll = true;
|
||||
bottomScrollbar.scrollLeft = tableContainer.scrollLeft;
|
||||
isSyncingHorizontalScroll = false;
|
||||
}
|
||||
|
||||
function handleBottomHorizontalScroll() {
|
||||
if (!tableContainer || !bottomScrollbar || isSyncingHorizontalScroll) return;
|
||||
isSyncingHorizontalScroll = true;
|
||||
tableContainer.scrollLeft = bottomScrollbar.scrollLeft;
|
||||
isSyncingHorizontalScroll = false;
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
tableContainer = scrollContainer?.querySelector('[data-slot="table-container"]') ?? undefined;
|
||||
if (!tableContainer) return;
|
||||
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
syncBottomScrollbarWidth();
|
||||
});
|
||||
|
||||
tableContainer.addEventListener('scroll', handleTableHorizontalScroll, { passive: true });
|
||||
resizeObserver.observe(tableContainer);
|
||||
const tableEl = tableContainer.querySelector('[data-slot="table"]');
|
||||
if (tableEl) resizeObserver.observe(tableEl);
|
||||
|
||||
syncBottomScrollbarWidth();
|
||||
|
||||
return () => {
|
||||
tableContainer?.removeEventListener('scroll', handleTableHorizontalScroll);
|
||||
resizeObserver.disconnect();
|
||||
};
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
data;
|
||||
columns;
|
||||
queueMicrotask(() => syncBottomScrollbarWidth());
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
<div
|
||||
class="rounded-md border min-h-[400px] max-h-[calc(100vh-310px)] overflow-y-auto"
|
||||
class="catalog-table-shell min-h-[400px] max-h-[calc(100vh-310px)] overflow-y-auto [&_[data-slot=table-container]]:w-full [&_[data-slot=table-container]]:overflow-x-auto"
|
||||
bind:this={scrollContainer}
|
||||
>
|
||||
<Table.Root>
|
||||
<Table.Header class="bg-background">
|
||||
<Table.Root class="w-max min-w-[1500px]">
|
||||
<Table.Header class="catalog-table-header">
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
<Table.Head class="whitespace-nowrap">
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
@@ -83,9 +140,13 @@
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row data-state={row.getIsSelected() && "selected"}>
|
||||
<Table.Row
|
||||
data-state={row.getIsSelected() && "selected"}
|
||||
class="cursor-pointer transition-colors hover:bg-muted/50 {selectedId === (row.original as any).id ? 'bg-muted' : ''}"
|
||||
onclick={() => onRowClick && onRowClick(row.original)}
|
||||
>
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
<Table.Cell class="whitespace-nowrap">
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
@@ -123,4 +184,11 @@
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</div>
|
||||
<div
|
||||
class="sticky bottom-0 z-30 h-5 overflow-x-scroll overflow-y-hidden border-x border-t bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/80 [scrollbar-gutter:stable]"
|
||||
bind:this={bottomScrollbar}
|
||||
onscroll={handleBottomHorizontalScroll}
|
||||
>
|
||||
<div class="h-full min-w-full" bind:this={bottomScrollbarInner}></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -44,8 +44,11 @@
|
||||
let scrollContainer = $state<HTMLDivElement>();
|
||||
let loadingTrigger = $state<HTMLDivElement>();
|
||||
|
||||
onMount(() => {
|
||||
$effect(() => {
|
||||
if (!loadMore) return;
|
||||
const target = loadingTrigger;
|
||||
const root = scrollContainer;
|
||||
if (!target) return;
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
@@ -55,14 +58,12 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
root: null, // Relative to viewport if scrollContainer is used as max-h div
|
||||
root: root,
|
||||
threshold: 0.1
|
||||
}
|
||||
);
|
||||
|
||||
if (loadingTrigger) {
|
||||
observer.observe(loadingTrigger);
|
||||
}
|
||||
observer.observe(target);
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
|
||||
@@ -72,7 +72,11 @@
|
||||
}
|
||||
|
||||
// Intersection Observer para detectar cuando el usuario llega al final
|
||||
onMount(() => {
|
||||
$effect(() => {
|
||||
const target = loadingTrigger;
|
||||
const root = scrollContainer;
|
||||
if (!target) return;
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
const [entry] = entries;
|
||||
@@ -81,14 +85,12 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
root: scrollContainer,
|
||||
root: root,
|
||||
threshold: 0.1
|
||||
}
|
||||
);
|
||||
|
||||
if (loadingTrigger) {
|
||||
observer.observe(loadingTrigger);
|
||||
}
|
||||
observer.observe(target);
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
|
||||
@@ -35,7 +35,11 @@
|
||||
let loadingTrigger = $state<HTMLDivElement>();
|
||||
|
||||
// Intersection Observer para detectar cuando el usuario llega al final
|
||||
onMount(() => {
|
||||
$effect(() => {
|
||||
const target = loadingTrigger;
|
||||
const root = scrollContainer;
|
||||
if (!target) return;
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
const [entry] = entries;
|
||||
@@ -44,14 +48,12 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
root: scrollContainer,
|
||||
root: root,
|
||||
threshold: 0.1
|
||||
}
|
||||
);
|
||||
|
||||
if (loadingTrigger) {
|
||||
observer.observe(loadingTrigger);
|
||||
}
|
||||
observer.observe(target);
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
|
||||
Reference in New Issue
Block a user