| 1 |
import { useMemo, CSSProperties } from "react"; |
| 2 |
import classNames from "classnames"; |
| 3 |
import { useSelect } from "@wordpress/data"; |
| 4 |
import { useTableStore } from "../../store"; |
| 5 |
import { Cell } from "../cell"; |
| 6 |
import { SortPreviewBanner } from "../../components/SortPreviewBanner"; |
| 7 |
import { PaginationNavigation } from "../../components/PaginationNavigation"; |
| 8 |
import { SearchInput } from "../../components/SearchInput"; |
| 9 |
import { isProAvailable } from "../../pro-status"; |
| 10 |
import { |
| 11 |
CellKey, |
| 12 |
ResponsiveBreakpoint, |
| 13 |
Span, |
| 14 |
attrDefaults, |
| 15 |
getCellKey, |
| 16 |
} from "../../attributes"; |
| 17 |
import { sortRowsByColumn } from "../../sorting"; |
| 18 |
import { filterRowsBySearch } from "../../search"; |
| 19 |
import { getElementTextContent } from "../../elements"; |
| 20 |
|
| 21 |
const tableConfigDefaults = attrDefaults.table; |
| 22 |
|
| 23 |
type PreviewDevice = "desktop" | "tablet" | "mobile"; |
| 24 |
|
| 25 |
type ResponsivePreviewStore = { |
| 26 |
getDeviceType?: () => string; |
| 27 |
__experimentalGetPreviewDeviceType?: () => string; |
| 28 |
}; |
| 29 |
|
| 30 |
type PreviewCellRef = { |
| 31 |
coords: CellKey; |
| 32 |
span: Span; |
| 33 |
key: string; |
| 34 |
}; |
| 35 |
|
| 36 |
type LegacyResponsiveBreakpoint = Partial<ResponsiveBreakpoint> & { |
| 37 |
direction?: "row" | "col"; |
| 38 |
headerAsCol?: boolean; |
| 39 |
}; |
| 40 |
|
| 41 |
const zeroWidthBorderPattern = /^0(?:\.0+)?(?:[a-z%]+)?$/i; |
| 42 |
|
| 43 |
function hasNonZeroCssValue(value?: string) { |
| 44 |
const trimmed = value?.trim() || ""; |
| 45 |
return !!trimmed && !zeroWidthBorderPattern.test(trimmed); |
| 46 |
} |
| 47 |
|
| 48 |
function hasVisibleBorder(border: string) { |
| 49 |
const trimmedBorder = border.trim(); |
| 50 |
|
| 51 |
if (!trimmedBorder) { |
| 52 |
return false; |
| 53 |
} |
| 54 |
|
| 55 |
const [width = "", style = ""] = trimmedBorder.split(/\s+/, 3); |
| 56 |
|
| 57 |
if (width === "none" || width === "hidden") { |
| 58 |
return false; |
| 59 |
} |
| 60 |
|
| 61 |
if (zeroWidthBorderPattern.test(width)) { |
| 62 |
return false; |
| 63 |
} |
| 64 |
|
| 65 |
if (style === "none" || style === "hidden") { |
| 66 |
return false; |
| 67 |
} |
| 68 |
|
| 69 |
return true; |
| 70 |
} |
| 71 |
|
| 72 |
function normalizeResponsiveBreakpoint( |
| 73 |
breakpoint: LegacyResponsiveBreakpoint | null | undefined, |
| 74 |
fallback: ResponsiveBreakpoint |
| 75 |
): ResponsiveBreakpoint { |
| 76 |
const normalized = { |
| 77 |
...fallback, |
| 78 |
...(breakpoint || {}), |
| 79 |
} as ResponsiveBreakpoint; |
| 80 |
|
| 81 |
normalized.transpose = |
| 82 |
typeof breakpoint?.transpose === "boolean" |
| 83 |
? breakpoint.transpose |
| 84 |
: breakpoint?.direction === "row"; |
| 85 |
|
| 86 |
normalized.repeatFirstCol = |
| 87 |
typeof breakpoint?.repeatFirstCol === "boolean" |
| 88 |
? breakpoint.repeatFirstCol |
| 89 |
: !!breakpoint?.headerAsCol; |
| 90 |
|
| 91 |
return normalized; |
| 92 |
} |
| 93 |
|
| 94 |
function buildResponsivePreviewRows( |
| 95 |
baseRows: PreviewCellRef[][], |
| 96 |
maxItemsPerRow: number, |
| 97 |
transformRowsToCols: boolean, |
| 98 |
repeatFirstColumn: boolean |
| 99 |
): PreviewCellRef[][] { |
| 100 |
if (baseRows.length === 0) { |
| 101 |
return []; |
| 102 |
} |
| 103 |
|
| 104 |
const maxCols = baseRows.reduce((max, row) => Math.max(max, row.length), 0); |
| 105 |
|
| 106 |
if (maxCols === 0) { |
| 107 |
return []; |
| 108 |
} |
| 109 |
|
| 110 |
const matrix: Array<Array<PreviewCellRef | null>> = baseRows.map(row => { |
| 111 |
const padded = row.map(cell => cell as PreviewCellRef | null); |
| 112 |
while (padded.length < maxCols) { |
| 113 |
padded.push(null); |
| 114 |
} |
| 115 |
return padded; |
| 116 |
}); |
| 117 |
|
| 118 |
const sourceRows: Array<Array<PreviewCellRef | null>> = transformRowsToCols |
| 119 |
? Array.from({ length: maxCols }, (_, row) => |
| 120 |
matrix.map(column => column[row] || null) |
| 121 |
) |
| 122 |
: matrix; |
| 123 |
|
| 124 |
const sourceCols = sourceRows[0]?.length || 0; |
| 125 |
if (sourceCols === 0) { |
| 126 |
return []; |
| 127 |
} |
| 128 |
|
| 129 |
const effectiveMax = repeatFirstColumn |
| 130 |
? Math.max(2, maxItemsPerRow) |
| 131 |
: Math.max(1, maxItemsPerRow); |
| 132 |
|
| 133 |
const outputRows: PreviewCellRef[][] = []; |
| 134 |
|
| 135 |
if (!repeatFirstColumn) { |
| 136 |
const groups = Math.ceil(sourceCols / effectiveMax); |
| 137 |
|
| 138 |
for (let group = 0; group < groups; group++) { |
| 139 |
const start = group * effectiveMax; |
| 140 |
const end = Math.min(start + effectiveMax, sourceCols); |
| 141 |
|
| 142 |
for (let row = 0; row < sourceRows.length; row++) { |
| 143 |
const cells = sourceRows[row] |
| 144 |
.slice(start, end) |
| 145 |
.filter((cell): cell is PreviewCellRef => cell !== null); |
| 146 |
|
| 147 |
if (cells.length > 0) { |
| 148 |
outputRows.push(cells); |
| 149 |
} |
| 150 |
} |
| 151 |
} |
| 152 |
|
| 153 |
return outputRows; |
| 154 |
} |
| 155 |
|
| 156 |
const firstColumn = 0; |
| 157 |
const dataChunkSize = Math.max(1, effectiveMax - 1); |
| 158 |
const groups = |
| 159 |
sourceCols <= 1 ? 1 : Math.ceil((sourceCols - 1) / dataChunkSize); |
| 160 |
|
| 161 |
for (let group = 0; group < groups; group++) { |
| 162 |
const start = 1 + group * dataChunkSize; |
| 163 |
const end = Math.min(start + dataChunkSize, sourceCols); |
| 164 |
|
| 165 |
for (let row = 0; row < sourceRows.length; row++) { |
| 166 |
const firstCell = sourceRows[row][firstColumn]; |
| 167 |
const dataCells = sourceRows[row] |
| 168 |
.slice(start, end) |
| 169 |
.filter((cell): cell is PreviewCellRef => cell !== null); |
| 170 |
|
| 171 |
const rowCells: PreviewCellRef[] = []; |
| 172 |
|
| 173 |
if (firstCell) { |
| 174 |
if (group === 0) { |
| 175 |
rowCells.push(firstCell); |
| 176 |
} else { |
| 177 |
rowCells.push({ |
| 178 |
...firstCell, |
| 179 |
key: `${firstCell.key}-repeat-${group}`, |
| 180 |
span: { rowSpan: 1, colSpan: 1 }, |
| 181 |
}); |
| 182 |
} |
| 183 |
} |
| 184 |
|
| 185 |
rowCells.push(...dataCells); |
| 186 |
|
| 187 |
if (rowCells.length > 0) { |
| 188 |
outputRows.push(rowCells); |
| 189 |
} |
| 190 |
} |
| 191 |
} |
| 192 |
|
| 193 |
return outputRows; |
| 194 |
} |
| 195 |
|
| 196 |
function getPreviewDeviceType( |
| 197 |
rawDeviceType: string | undefined |
| 198 |
): PreviewDevice { |
| 199 |
const normalized = (rawDeviceType || "desktop").toLowerCase(); |
| 200 |
|
| 201 |
if (normalized === "tablet") { |
| 202 |
return "tablet"; |
| 203 |
} |
| 204 |
|
| 205 |
if (normalized === "mobile") { |
| 206 |
return "mobile"; |
| 207 |
} |
| 208 |
|
| 209 |
return "desktop"; |
| 210 |
} |
| 211 |
|
| 212 |
export const PrimaryTable = () => { |
| 213 |
const isPro = isProAvailable(); |
| 214 |
const tableConfig = useTableStore(state => state.table); |
| 215 |
const cells = useTableStore(state => state.cells); |
| 216 |
const columns = useTableStore(state => state.columns); |
| 217 |
const getCellSpan = useTableStore(state => state.getCellSpan); |
| 218 |
const tableBorderRadius = useTableStore( |
| 219 |
state => state.cellDefaults.styles.borderRadius |
| 220 |
); |
| 221 |
const sortPreviewMode = useTableStore(state => state.sortPreviewMode); |
| 222 |
const previewSortColumn = useTableStore(state => state.previewSortColumn); |
| 223 |
const previewSortOrder = useTableStore(state => state.previewSortOrder); |
| 224 |
|
| 225 |
const currentPage = useTableStore(state => state.currentPage); |
| 226 |
const setCurrentPage = useTableStore(state => state.setCurrentPage); |
| 227 |
const paginationConfig = useTableStore(state => state.table.pagination!); |
| 228 |
|
| 229 |
const searchTerm = useTableStore(state => state.searchTerm); |
| 230 |
const searchConfig = useTableStore(state => state.table.search); |
| 231 |
|
| 232 |
const previewDevice = useSelect(select => { |
| 233 |
const editorStore = select("core/editor") as ResponsivePreviewStore; |
| 234 |
const siteEditorStore = select( |
| 235 |
"core/edit-site" |
| 236 |
) as ResponsivePreviewStore; |
| 237 |
const postEditorStore = select( |
| 238 |
"core/edit-post" |
| 239 |
) as ResponsivePreviewStore; |
| 240 |
|
| 241 |
const rawDeviceType = |
| 242 |
editorStore?.getDeviceType?.() || |
| 243 |
siteEditorStore?.__experimentalGetPreviewDeviceType?.() || |
| 244 |
postEditorStore?.__experimentalGetPreviewDeviceType?.(); |
| 245 |
|
| 246 |
return getPreviewDeviceType(rawDeviceType); |
| 247 |
}, []); |
| 248 |
|
| 249 |
const { rows: totalRows, cols: totalCols } = tableConfig; |
| 250 |
const paginationEnabled = isPro && paginationConfig.enabled; |
| 251 |
const searchEnabled = isPro && (searchConfig?.enabled || false); |
| 252 |
const tableAlignment = tableConfig.tableAlignment || "left"; |
| 253 |
const tableWidthValue = (tableConfig.tableWidth || "auto").trim(); |
| 254 |
const customTableWidthAllowed = |
| 255 |
tableWidthValue !== "auto" && |
| 256 |
tableWidthValue !== "wide" && |
| 257 |
tableWidthValue !== "full"; |
| 258 |
const tableWidth = customTableWidthAllowed ? tableWidthValue : "100%"; |
| 259 |
const wrapperAlignmentClass = customTableWidthAllowed |
| 260 |
? `justify-table-${tableAlignment}` |
| 261 |
: ""; |
| 262 |
const defaultCellSpacing = tableConfigDefaults.cellSpacing!!; |
| 263 |
const cellSpacing = tableConfig.cellSpacing || defaultCellSpacing; |
| 264 |
const defaultTableBorder = tableConfigDefaults.tableBorder!!; |
| 265 |
const tableBorder = tableConfig.tableBorder || defaultTableBorder; |
| 266 |
const hasTableBorderTop = hasVisibleBorder(tableBorder.top); |
| 267 |
const hasTableBorderRight = hasVisibleBorder(tableBorder.right); |
| 268 |
const hasTableBorderBottom = hasVisibleBorder(tableBorder.bottom); |
| 269 |
const hasTableBorderLeft = hasVisibleBorder(tableBorder.left); |
| 270 |
const horizontalCellSpacing = |
| 271 |
cellSpacing.horizontal || defaultCellSpacing.horizontal; |
| 272 |
const verticalCellSpacing = |
| 273 |
cellSpacing.vertical || defaultCellSpacing.vertical; |
| 274 |
const isHorizontalSpacingZero = horizontalCellSpacing === "0"; |
| 275 |
const isVerticalSpacingZero = verticalCellSpacing === "0"; |
| 276 |
const hasCellSpacing = !isHorizontalSpacingZero || !isVerticalSpacingZero; |
| 277 |
const tableClassName = [ |
| 278 |
"wp-block-tableberg", |
| 279 |
hasTableBorderTop ? "tableberg-has-table-border-top" : "", |
| 280 |
hasTableBorderRight ? "tableberg-has-table-border-right" : "", |
| 281 |
hasTableBorderBottom ? "tableberg-has-table-border-bottom" : "", |
| 282 |
hasTableBorderLeft ? "tableberg-has-table-border-left" : "", |
| 283 |
hasCellSpacing ? "tableberg-has-cell-spacing" : "", |
| 284 |
hasCellSpacing && isHorizontalSpacingZero |
| 285 |
? "tableberg-cell-spacing-horizontal-zero" |
| 286 |
: "", |
| 287 |
hasCellSpacing && isVerticalSpacingZero |
| 288 |
? "tableberg-cell-spacing-vertical-zero" |
| 289 |
: "", |
| 290 |
] |
| 291 |
.filter(Boolean) |
| 292 |
.join(" "); |
| 293 |
|
| 294 |
const activeResponsiveBreakpoint: ResponsiveBreakpoint | null = |
| 295 |
previewDevice === "desktop" |
| 296 |
? null |
| 297 |
: normalizeResponsiveBreakpoint( |
| 298 |
tableConfig.responsive?.[ |
| 299 |
previewDevice |
| 300 |
] as LegacyResponsiveBreakpoint, |
| 301 |
tableConfigDefaults.responsive![previewDevice] |
| 302 |
); |
| 303 |
|
| 304 |
const hasResponsivePreview = |
| 305 |
!!activeResponsiveBreakpoint?.enabled && |
| 306 |
!!activeResponsiveBreakpoint?.mode; |
| 307 |
|
| 308 |
const responsivePreviewClassName = !hasResponsivePreview |
| 309 |
? "" |
| 310 |
: activeResponsiveBreakpoint?.mode === "scroll" |
| 311 |
? "tableberg-scroll-x" |
| 312 |
: ""; |
| 313 |
|
| 314 |
const editorResponsiveStackCount = hasResponsivePreview |
| 315 |
? Math.max(1, activeResponsiveBreakpoint?.stackCount || 1) |
| 316 |
: 1; |
| 317 |
|
| 318 |
const wrapperClassName = classNames( |
| 319 |
"tableberg-table-wrapper", |
| 320 |
wrapperAlignmentClass, |
| 321 |
responsivePreviewClassName |
| 322 |
); |
| 323 |
|
| 324 |
const filteredRowIndices = useMemo(() => { |
| 325 |
if (!searchEnabled || !searchTerm.trim()) { |
| 326 |
return Array.from({ length: totalRows }, (_, i) => i); |
| 327 |
} |
| 328 |
|
| 329 |
return filterRowsBySearch( |
| 330 |
cells, |
| 331 |
totalRows, |
| 332 |
totalCols, |
| 333 |
tableConfig, |
| 334 |
searchTerm |
| 335 |
); |
| 336 |
}, [searchEnabled, searchTerm, cells, totalRows, totalCols, tableConfig]); |
| 337 |
|
| 338 |
const sortedRowIndices = useMemo(() => { |
| 339 |
if (!isPro || !sortPreviewMode || previewSortColumn === null) { |
| 340 |
return filteredRowIndices; |
| 341 |
} |
| 342 |
|
| 343 |
const sortType = columns[previewSortColumn]?.sortable || "text"; |
| 344 |
|
| 345 |
if (searchEnabled && searchTerm.trim()) { |
| 346 |
const { headerEnabled, footerEnabled } = tableConfig; |
| 347 |
const headerRow = headerEnabled ? 0 : -1; |
| 348 |
const footerRow = footerEnabled ? totalRows - 1 : -1; |
| 349 |
|
| 350 |
const dataRows = filteredRowIndices.filter( |
| 351 |
idx => idx !== headerRow && idx !== footerRow |
| 352 |
); |
| 353 |
|
| 354 |
const rowsWithValues = dataRows.map(rowIdx => { |
| 355 |
const elements = |
| 356 |
cells[`${rowIdx},${previewSortColumn}`]?.elements || []; |
| 357 |
const rawValue = |
| 358 |
elements.length > 0 |
| 359 |
? getElementTextContent(elements[0]) |
| 360 |
: ""; |
| 361 |
|
| 362 |
let parsedValue: string | number | Date; |
| 363 |
switch (sortType) { |
| 364 |
case "number": { |
| 365 |
const cleaned = rawValue.replace(/[^0-9.-]/g, ""); |
| 366 |
const num = parseFloat(cleaned); |
| 367 |
parsedValue = isNaN(num) ? 0 : num; |
| 368 |
break; |
| 369 |
} |
| 370 |
case "date": { |
| 371 |
const date = new Date(rawValue); |
| 372 |
parsedValue = isNaN(date.getTime()) |
| 373 |
? new Date(0) |
| 374 |
: date; |
| 375 |
break; |
| 376 |
} |
| 377 |
case "text": |
| 378 |
default: |
| 379 |
parsedValue = rawValue.toLowerCase(); |
| 380 |
} |
| 381 |
|
| 382 |
return { index: rowIdx, value: parsedValue }; |
| 383 |
}); |
| 384 |
|
| 385 |
rowsWithValues.sort(({ value: a }, { value: b }) => { |
| 386 |
let result: number; |
| 387 |
if (typeof a === "number" && typeof b === "number") { |
| 388 |
result = a - b; |
| 389 |
} else if (a instanceof Date && b instanceof Date) { |
| 390 |
result = a.getTime() - b.getTime(); |
| 391 |
} else { |
| 392 |
result = String(a).localeCompare(String(b)); |
| 393 |
} |
| 394 |
return previewSortOrder === "asc" ? result : -result; |
| 395 |
}); |
| 396 |
|
| 397 |
const result: number[] = []; |
| 398 |
if (headerEnabled && filteredRowIndices.includes(0)) { |
| 399 |
result.push(0); |
| 400 |
} |
| 401 |
for (const { index } of rowsWithValues) { |
| 402 |
result.push(index); |
| 403 |
} |
| 404 |
if (footerEnabled && filteredRowIndices.includes(totalRows - 1)) { |
| 405 |
result.push(totalRows - 1); |
| 406 |
} |
| 407 |
|
| 408 |
return result; |
| 409 |
} |
| 410 |
|
| 411 |
return sortRowsByColumn( |
| 412 |
cells, |
| 413 |
totalRows, |
| 414 |
tableConfig, |
| 415 |
previewSortColumn, |
| 416 |
sortType, |
| 417 |
previewSortOrder |
| 418 |
); |
| 419 |
}, [ |
| 420 |
sortPreviewMode, |
| 421 |
previewSortColumn, |
| 422 |
previewSortOrder, |
| 423 |
filteredRowIndices, |
| 424 |
cells, |
| 425 |
columns, |
| 426 |
tableConfig, |
| 427 |
totalRows, |
| 428 |
searchEnabled, |
| 429 |
searchTerm, |
| 430 |
isPro, |
| 431 |
]); |
| 432 |
|
| 433 |
const rowOrder = useMemo(() => { |
| 434 |
let rows = sortedRowIndices; |
| 435 |
|
| 436 |
if (paginationEnabled) { |
| 437 |
const { headerEnabled, footerEnabled } = tableConfig; |
| 438 |
|
| 439 |
const headerRow = headerEnabled ? 0 : -1; |
| 440 |
const footerRow = footerEnabled ? totalRows - 1 : -1; |
| 441 |
|
| 442 |
const dataRowsInOrder = rows.filter( |
| 443 |
idx => idx !== headerRow && idx !== footerRow |
| 444 |
); |
| 445 |
|
| 446 |
const { pageSize } = paginationConfig; |
| 447 |
const startIdx = currentPage * pageSize; |
| 448 |
const endIdx = Math.min( |
| 449 |
startIdx + pageSize, |
| 450 |
dataRowsInOrder.length |
| 451 |
); |
| 452 |
|
| 453 |
const maxPage = Math.max( |
| 454 |
0, |
| 455 |
Math.ceil(dataRowsInOrder.length / pageSize) - 1 |
| 456 |
); |
| 457 |
if (currentPage > maxPage && maxPage >= 0) { |
| 458 |
setCurrentPage(maxPage); |
| 459 |
} |
| 460 |
|
| 461 |
const pagedRows: number[] = []; |
| 462 |
|
| 463 |
if (headerEnabled && rows.includes(0)) { |
| 464 |
pagedRows.push(0); |
| 465 |
} |
| 466 |
|
| 467 |
for (let i = startIdx; i < endIdx; i++) { |
| 468 |
pagedRows.push(dataRowsInOrder[i]); |
| 469 |
} |
| 470 |
|
| 471 |
if (footerEnabled && rows.includes(totalRows - 1)) { |
| 472 |
pagedRows.push(totalRows - 1); |
| 473 |
} |
| 474 |
|
| 475 |
rows = pagedRows; |
| 476 |
} |
| 477 |
|
| 478 |
return rows; |
| 479 |
}, [ |
| 480 |
sortedRowIndices, |
| 481 |
paginationEnabled, |
| 482 |
currentPage, |
| 483 |
paginationConfig.pageSize, |
| 484 |
tableConfig, |
| 485 |
totalRows, |
| 486 |
setCurrentPage, |
| 487 |
]); |
| 488 |
|
| 489 |
const getOccupiedCellsForRow = (actualRow: number) => { |
| 490 |
if (sortPreviewMode || paginationEnabled) { |
| 491 |
return new Set<number>(); |
| 492 |
} |
| 493 |
|
| 494 |
const occupied = new Set<number>(); |
| 495 |
for (const [key, cell] of Object.entries(cells)) { |
| 496 |
const span = cell.span; |
| 497 |
if (!span) { |
| 498 |
continue; |
| 499 |
} |
| 500 |
|
| 501 |
const [cellRow, cellCol] = key.split(",").map(Number); |
| 502 |
if (cellRow <= actualRow && cellRow + span.rowSpan > actualRow) { |
| 503 |
for (let cs = 0; cs < span.colSpan; cs++) { |
| 504 |
if (cellRow !== actualRow) { |
| 505 |
occupied.add(cellCol + cs); |
| 506 |
} |
| 507 |
} |
| 508 |
} |
| 509 |
} |
| 510 |
return occupied; |
| 511 |
}; |
| 512 |
|
| 513 |
const tableRows: JSX.Element[] = []; |
| 514 |
const baseRowsForResponsivePreview: PreviewCellRef[][] = []; |
| 515 |
for (const actualRow of rowOrder) { |
| 516 |
const rowCells: JSX.Element[] = []; |
| 517 |
const rowCellsForPreview: PreviewCellRef[] = []; |
| 518 |
const occupiedCols = getOccupiedCellsForRow(actualRow); |
| 519 |
|
| 520 |
for (let col = 0; col < totalCols; col++) { |
| 521 |
if (occupiedCols.has(col)) { |
| 522 |
continue; |
| 523 |
} |
| 524 |
|
| 525 |
const cellKey = `${actualRow},${col}`; |
| 526 |
const span = getCellSpan(cellKey as CellKey); |
| 527 |
|
| 528 |
if (!sortPreviewMode && !paginationEnabled) { |
| 529 |
for (let cs = 1; cs < span.colSpan; cs++) { |
| 530 |
occupiedCols.add(col + cs); |
| 531 |
} |
| 532 |
} |
| 533 |
|
| 534 |
rowCellsForPreview.push({ |
| 535 |
coords: getCellKey(actualRow, col), |
| 536 |
span, |
| 537 |
key: cellKey, |
| 538 |
}); |
| 539 |
|
| 540 |
rowCells.push( |
| 541 |
<Cell |
| 542 |
key={cellKey} |
| 543 |
span={span} |
| 544 |
cellCoords={cellKey as CellKey} |
| 545 |
/> |
| 546 |
); |
| 547 |
} |
| 548 |
|
| 549 |
baseRowsForResponsivePreview.push(rowCellsForPreview); |
| 550 |
tableRows.push(<tr key={actualRow}>{rowCells}</tr>); |
| 551 |
} |
| 552 |
|
| 553 |
let renderedRows = tableRows; |
| 554 |
|
| 555 |
if (hasResponsivePreview && activeResponsiveBreakpoint?.mode === "stack") { |
| 556 |
const previewRows = buildResponsivePreviewRows( |
| 557 |
baseRowsForResponsivePreview, |
| 558 |
editorResponsiveStackCount, |
| 559 |
activeResponsiveBreakpoint.transpose, |
| 560 |
activeResponsiveBreakpoint.repeatFirstCol |
| 561 |
); |
| 562 |
|
| 563 |
renderedRows = previewRows.map((previewRow, rowIndex) => ( |
| 564 |
<tr key={`responsive-preview-row-${rowIndex}`}> |
| 565 |
{previewRow.map((cell, cellIndex) => ( |
| 566 |
<Cell |
| 567 |
key={`${cell.key}-${rowIndex}-${cellIndex}`} |
| 568 |
span={{ rowSpan: 1, colSpan: 1 }} |
| 569 |
cellCoords={cell.coords} |
| 570 |
/> |
| 571 |
))} |
| 572 |
</tr> |
| 573 |
)); |
| 574 |
} |
| 575 |
|
| 576 |
// The rounded wrapper owns only the explicit table border. Cell borders |
| 577 |
// stay on cells so they do not create a second, unintended outer border. |
| 578 |
const hasTableRadius = !!( |
| 579 |
hasNonZeroCssValue(tableBorderRadius?.topLeft) || |
| 580 |
hasNonZeroCssValue(tableBorderRadius?.topRight) || |
| 581 |
hasNonZeroCssValue(tableBorderRadius?.bottomRight) || |
| 582 |
hasNonZeroCssValue(tableBorderRadius?.bottomLeft) |
| 583 |
); |
| 584 |
const tableRadiusWrapperStyle: CSSProperties | undefined = hasTableRadius |
| 585 |
? { |
| 586 |
borderTopLeftRadius: tableBorderRadius?.topLeft, |
| 587 |
borderTopRightRadius: tableBorderRadius?.topRight, |
| 588 |
borderBottomRightRadius: tableBorderRadius?.bottomRight, |
| 589 |
borderBottomLeftRadius: tableBorderRadius?.bottomLeft, |
| 590 |
borderTop: tableBorder.top || undefined, |
| 591 |
borderRight: tableBorder.right || undefined, |
| 592 |
borderBottom: tableBorder.bottom || undefined, |
| 593 |
borderLeft: tableBorder.left || undefined, |
| 594 |
boxSizing: "border-box", |
| 595 |
overflow: "hidden", |
| 596 |
} |
| 597 |
: undefined; |
| 598 |
|
| 599 |
return ( |
| 600 |
<div className={wrapperClassName} style={tableRadiusWrapperStyle}> |
| 601 |
<SearchInput /> |
| 602 |
<SortPreviewBanner /> |
| 603 |
<table |
| 604 |
className={tableClassName} |
| 605 |
data-tableberg-editor-preview={ |
| 606 |
hasResponsivePreview ? previewDevice : undefined |
| 607 |
} |
| 608 |
style={{ |
| 609 |
borderCollapse: hasCellSpacing ? "separate" : "collapse", |
| 610 |
borderSpacing: hasCellSpacing |
| 611 |
? `${horizontalCellSpacing} ${verticalCellSpacing}` |
| 612 |
: undefined, |
| 613 |
width: tableWidth, |
| 614 |
...(customTableWidthAllowed |
| 615 |
? { |
| 616 |
maxWidth: tableWidth, |
| 617 |
} |
| 618 |
: {}), |
| 619 |
borderTop: hasTableRadius |
| 620 |
? undefined |
| 621 |
: tableBorder.top || undefined, |
| 622 |
borderRight: hasTableRadius |
| 623 |
? undefined |
| 624 |
: tableBorder.right || undefined, |
| 625 |
borderBottom: hasTableRadius |
| 626 |
? undefined |
| 627 |
: tableBorder.bottom || undefined, |
| 628 |
borderLeft: hasTableRadius |
| 629 |
? undefined |
| 630 |
: tableBorder.left || undefined, |
| 631 |
}} |
| 632 |
> |
| 633 |
<tbody>{renderedRows}</tbody> |
| 634 |
</table> |
| 635 |
<PaginationNavigation filteredRowCount={sortedRowIndices.length} /> |
| 636 |
</div> |
| 637 |
); |
| 638 |
}; |
| 639 |
|