mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-25 11:52:10 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c873736da6 |
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 0,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "gitbook",
|
||||
@@ -135,6 +134,7 @@
|
||||
"@radix-ui/react-tooltip": "^1.1.8",
|
||||
"@sindresorhus/fnv1a": "^3.1.0",
|
||||
"@tailwindcss/container-queries": "^0.1.1",
|
||||
"@tanstack/react-virtual": "^3.13.12",
|
||||
"@tusbar/cache-control": "^1.0.2",
|
||||
"ai": "^4.2.2",
|
||||
"assert-never": "catalog:",
|
||||
@@ -1478,7 +1478,9 @@
|
||||
|
||||
"@tailwindcss/postcss": ["@tailwindcss/postcss@4.1.11", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.1.11", "@tailwindcss/oxide": "4.1.11", "postcss": "^8.4.41", "tailwindcss": "4.1.11" } }, "sha512-q/EAIIpF6WpLhKEuQSEVMZNMIY8KhWoAemZ9eylNAih9jxMGAYPPWBn3I9QL/2jZ+e7OEz/tZkX5HwbBR4HohA=="],
|
||||
|
||||
"@tanstack/virtual-core": ["@tanstack/virtual-core@3.10.8", "", {}, "sha512-PBu00mtt95jbKFi6Llk9aik8bnR3tR/oQP1o3TSi+iG//+Q2RTIzCEgKkHG8BB86kxMNW6O8wku+Lmi+QFR6jA=="],
|
||||
"@tanstack/react-virtual": ["@tanstack/react-virtual@3.13.18", "", { "dependencies": { "@tanstack/virtual-core": "3.13.18" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-dZkhyfahpvlaV0rIKnvQiVoWPyURppl6w4m9IwMDpuIjcJ1sD9YGWrt0wISvgU7ewACXx2Ct46WPgI6qAD4v6A=="],
|
||||
|
||||
"@tanstack/virtual-core": ["@tanstack/virtual-core@3.13.18", "", {}, "sha512-Mx86Hqu1k39icq2Zusq+Ey2J6dDWTjDvEv43PJtRCoEYTLyfaPnxIQ6iy7YAOK0NV/qOEmZQ/uCufrppZxTgcg=="],
|
||||
|
||||
"@tanstack/vue-virtual": ["@tanstack/vue-virtual@3.10.8", "", { "dependencies": { "@tanstack/virtual-core": "3.10.8" }, "peerDependencies": { "vue": "^2.7.0 || ^3.0.0" } }, "sha512-DB5QA8c/LfqOqIUCpSs3RdOTVroRRdqeHMqBkYrcashSZtOzIv8xbiqHgg7RYxDfkH5F3Y+e0MkuuyGNDVB0BQ=="],
|
||||
|
||||
@@ -4034,6 +4036,8 @@
|
||||
|
||||
"@tailwindcss/postcss/postcss": ["postcss@8.5.3", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A=="],
|
||||
|
||||
"@tanstack/vue-virtual/@tanstack/virtual-core": ["@tanstack/virtual-core@3.10.8", "", {}, "sha512-PBu00mtt95jbKFi6Llk9aik8bnR3tR/oQP1o3TSi+iG//+Q2RTIzCEgKkHG8BB86kxMNW6O8wku+Lmi+QFR6jA=="],
|
||||
|
||||
"@ts-morph/common/fast-glob": ["fast-glob@3.3.2", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow=="],
|
||||
|
||||
"@ts-morph/common/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
"@sindresorhus/fnv1a": "^3.1.0",
|
||||
"@tailwindcss/container-queries": "^0.1.1",
|
||||
"@tusbar/cache-control": "^1.0.2",
|
||||
"@tanstack/react-virtual": "^3.13.12",
|
||||
"ai": "^4.2.2",
|
||||
"assert-never": "catalog:",
|
||||
"bidc": "catalog:",
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { type ClassValue, tcls } from '@/lib/tailwind';
|
||||
import type { DocumentTableViewGrid } from '@gitbook/api';
|
||||
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { RecordColumnValue } from './RecordColumnValue';
|
||||
import type { TableRecordKV, TableViewProps } from './Table';
|
||||
import { getColumnWidth } from './ViewGrid';
|
||||
@@ -13,12 +11,13 @@ export function RecordRow(
|
||||
record: TableRecordKV;
|
||||
autoSizedColumns: string[];
|
||||
fixedColumns: string[];
|
||||
className?: ClassValue;
|
||||
}
|
||||
) {
|
||||
const { view, autoSizedColumns, fixedColumns, block, context } = props;
|
||||
const { view, autoSizedColumns, fixedColumns, block, context, className } = props;
|
||||
|
||||
return (
|
||||
<div className={styles.row} role="row">
|
||||
<div className={tcls(styles.row, className)} role="row">
|
||||
{view.columns.map((column) => {
|
||||
const columnWidth = getColumnWidth({
|
||||
column,
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
import type { DocumentTableViewGrid } from '@gitbook/api';
|
||||
import { useWindowVirtualizer } from '@tanstack/react-virtual';
|
||||
import React from 'react';
|
||||
import { RecordRow } from './RecordRow';
|
||||
import type { TableViewProps } from './Table';
|
||||
|
||||
/**
|
||||
* Virtualized group of rows for tables with many records.
|
||||
*/
|
||||
export const VIRTUALIZATION_THRESHOLD = 200;
|
||||
const ROW_ESTIMATE_PX = 40;
|
||||
|
||||
export function RowGroupVirtualized(
|
||||
props: TableViewProps<DocumentTableViewGrid> & {
|
||||
autoSizedColumns: string[];
|
||||
fixedColumns: string[];
|
||||
tableWidth: string;
|
||||
}
|
||||
) {
|
||||
const { records, tableWidth, ...rest } = props;
|
||||
const [isHydrated, setIsHydrated] = React.useState(false);
|
||||
const [scrollMargin, setScrollMargin] = React.useState(0);
|
||||
const parentRef = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
setIsHydrated(true);
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!isHydrated) {
|
||||
return;
|
||||
}
|
||||
|
||||
const updateScrollMargin = () => {
|
||||
if (!parentRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rect = parentRef.current.getBoundingClientRect();
|
||||
setScrollMargin(rect.top + window.scrollY);
|
||||
};
|
||||
|
||||
updateScrollMargin();
|
||||
window.addEventListener('resize', updateScrollMargin);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', updateScrollMargin);
|
||||
};
|
||||
}, [isHydrated]);
|
||||
|
||||
const rowVirtualizer = useWindowVirtualizer({
|
||||
count: records.length,
|
||||
estimateSize: () => ROW_ESTIMATE_PX,
|
||||
overscan: 8,
|
||||
scrollMargin,
|
||||
});
|
||||
|
||||
if (!isHydrated) {
|
||||
return (
|
||||
<div role="rowgroup" className={tcls('flex', 'flex-col', tableWidth)}>
|
||||
{records.map((record) => (
|
||||
<RecordRow key={record[0]} record={record} records={records} {...rest} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={parentRef}
|
||||
role="rowgroup"
|
||||
className={tcls('relative', 'flex', 'flex-col', tableWidth)}
|
||||
style={{ height: rowVirtualizer.getTotalSize() }}
|
||||
>
|
||||
{rowVirtualizer.getVirtualItems().map((virtualRow) => {
|
||||
const record = records[virtualRow.index];
|
||||
|
||||
if (!record) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={record[0]}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
transform: `translateY(${virtualRow.start - scrollMargin}px)`,
|
||||
}}
|
||||
>
|
||||
<RecordRow
|
||||
records={records}
|
||||
record={record}
|
||||
className={virtualRow.index === 0 ? undefined : 'border-t'}
|
||||
{...rest}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import type { DocumentTableViewGrid } from '@gitbook/api';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { RecordRow } from './RecordRow';
|
||||
import { RowGroupVirtualized, VIRTUALIZATION_THRESHOLD } from './RowGroupVirtualized';
|
||||
import type { TableViewProps } from './Table';
|
||||
import styles from './table.module.css';
|
||||
import { getColumnAlignment } from './utils';
|
||||
@@ -13,7 +14,7 @@ import { getColumnAlignment } from './utils';
|
||||
3. Auto-size is turned off without setting a width, we then default to a fixed width of 100px
|
||||
*/
|
||||
export function ViewGrid(props: TableViewProps<DocumentTableViewGrid>) {
|
||||
const { block, view, records, style, context } = props;
|
||||
const { block, view, records, style, context, isOffscreen } = props;
|
||||
|
||||
/* Calculate how many columns are auto-sized vs fixed width */
|
||||
const columnWidths = context.mode === 'print' ? undefined : view.columnWidths;
|
||||
@@ -29,6 +30,9 @@ export function ViewGrid(props: TableViewProps<DocumentTableViewGrid>) {
|
||||
(columnId) => (block.data.definition[columnId]?.title.trim().length ?? 0) > 0
|
||||
);
|
||||
|
||||
const shouldVirtualize =
|
||||
context.mode !== 'print' && !isOffscreen && records.length >= VIRTUALIZATION_THRESHOLD;
|
||||
|
||||
return (
|
||||
<div className={tcls(style, styles.tableWrapper)}>
|
||||
{/* Table */}
|
||||
@@ -73,20 +77,29 @@ export function ViewGrid(props: TableViewProps<DocumentTableViewGrid>) {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
role="rowgroup"
|
||||
className={tcls('flex', 'flex-col', tableWidth, '[&>*+*]:border-t')}
|
||||
>
|
||||
{records.map((record) => (
|
||||
<RecordRow
|
||||
key={record[0]}
|
||||
record={record}
|
||||
autoSizedColumns={autoSizedColumns}
|
||||
fixedColumns={fixedColumns}
|
||||
{...props}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{shouldVirtualize ? (
|
||||
<RowGroupVirtualized
|
||||
autoSizedColumns={autoSizedColumns}
|
||||
fixedColumns={fixedColumns}
|
||||
tableWidth={tableWidth}
|
||||
{...props}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
role="rowgroup"
|
||||
className={tcls('flex', 'flex-col', tableWidth, '[&>*+*]:border-t')}
|
||||
>
|
||||
{records.map((record) => (
|
||||
<RecordRow
|
||||
key={record[0]}
|
||||
record={record}
|
||||
autoSizedColumns={autoSizedColumns}
|
||||
fixedColumns={fixedColumns}
|
||||
{...props}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user