|
1 | 1 | <script lang="ts"> |
2 | | - import { createEventDispatcher } from 'svelte'; |
| 2 | + import { createEventDispatcher, onMount } from 'svelte'; |
3 | 3 | import { readable, writable } from 'svelte/store'; |
4 | 4 |
|
5 | 5 | import Fa from 'svelte-fa'; |
|
33 | 33 | fixedWidth, |
34 | 34 | normalizeFilters, |
35 | 35 | resetResize, |
36 | | - convertServerColumns |
| 36 | + convertServerColumns, |
| 37 | + minWidth |
37 | 38 | } from './shared'; |
38 | 39 | import { Receive, Send } from '$models/Models'; |
39 | 40 | import type { TableConfig } from '$models/Models'; |
|
61 | 62 |
|
62 | 63 | let searchValue = ''; |
63 | 64 | let isFetching = false; |
| 65 | + let tableRef: HTMLTableElement; |
| 66 | +
|
64 | 67 | const serverSide = server !== undefined; |
65 | 68 | const { baseUrl, entityId, versionId, sendModel = new Send() } = server ?? {}; |
66 | 69 |
|
|
75 | 78 | const dispatch = createEventDispatcher(); |
76 | 79 | const actionDispatcher = (obj) => dispatch('action', obj); |
77 | 80 |
|
| 81 | + // Stores to hold the width and height information for resizing |
| 82 | + const rowHeights = writable< |
| 83 | + { |
| 84 | + max: number; |
| 85 | + min: number; |
| 86 | + }[] |
| 87 | + >([]); |
| 88 | + const colWidths = writable<number[]>([]); |
| 89 | +
|
| 90 | + // Server-side variables |
78 | 91 | const serverItems = serverSide ? writable<Number>(0) : undefined; |
79 | 92 | const serverItemCount = serverSide |
80 | 93 | ? readable<Number>(0, (set) => { |
|
375 | 388 | updateTable(); |
376 | 389 | }; |
377 | 390 |
|
| 391 | + const getMaxCellHeightInRow = () => { |
| 392 | + if (!tableRef || resizable === 'columns' || resizable === 'none') return; |
| 393 | +
|
| 394 | + // Initialize the rowHeights array if it is empty |
| 395 | + if ($rowHeights.length === 0) { |
| 396 | + $rowHeights = Array.from({ length: $pageRows.length }, () => ({ max: 44, min: 20 })); |
| 397 | + } |
| 398 | +
|
| 399 | + tableRef.querySelectorAll('tbody tr').forEach((row, index) => { |
| 400 | + const cells = row.querySelectorAll('td'); |
| 401 | +
|
| 402 | + let maxHeight = optionsComponent ? 56 : 44; |
| 403 | + let minHeight = optionsComponent ? 56 : 44; |
| 404 | +
|
| 405 | + cells.forEach((cell) => { |
| 406 | + const cellHeight = cell.getBoundingClientRect().height; |
| 407 | + // + 2 pixels for rendering borders correctly |
| 408 | + if (cellHeight > maxHeight) { |
| 409 | + maxHeight = cellHeight + 2; |
| 410 | + } |
| 411 | + if (cellHeight < minHeight) { |
| 412 | + minHeight = cellHeight + 2; |
| 413 | + } |
| 414 | + }); |
| 415 | +
|
| 416 | + rowHeights.update((rh) => { |
| 417 | + rh[index].max = maxHeight - 24; |
| 418 | + rh[index].min = Math.max(minHeight - 24, rowHeight ?? 20); |
| 419 | + return rh; |
| 420 | + }); |
| 421 | + }); |
| 422 | + }; |
| 423 | +
|
| 424 | + const getMinCellWidthInColumn = () => { |
| 425 | + if (!tableRef || resizable === 'rows' || resizable === 'none') return; |
| 426 | +
|
| 427 | + // Initialize the colWidths array if it is empty |
| 428 | + if ($colWidths.length === 0) { |
| 429 | + $colWidths = Array.from({ length: $headerRows[0].cells.length }, () => 100); |
| 430 | + } |
| 431 | +
|
| 432 | + colWidths.update((cw) => { |
| 433 | + tableRef.querySelectorAll('thead tr th span').forEach((cell, index) => { |
| 434 | + // + 12 pixels for padding and + 32 pixels for filter icon |
| 435 | + // If the column width is 100, which means it has not been initialized, then calculate the width |
| 436 | + cw[index] = cw[index] === 100 ? cell.getBoundingClientRect().width + 12 + 32 : cw[index]; |
| 437 | + }); |
| 438 | + return cw; |
| 439 | + }); |
| 440 | + }; |
| 441 | +
|
| 442 | + const resizeObserver = new ResizeObserver(() => { |
| 443 | + getMaxCellHeightInRow(); |
| 444 | + getMinCellWidthInColumn(); |
| 445 | + }); |
| 446 | +
|
| 447 | + const observeFirstCells = () => { |
| 448 | + if (!tableRef) return; |
| 449 | +
|
| 450 | + tableRef.querySelectorAll('tbody tr td:first-child').forEach((cell) => { |
| 451 | + resizeObserver.observe(cell); |
| 452 | + }); |
| 453 | + }; |
| 454 | +
|
| 455 | + const observeHeaderColumns = () => { |
| 456 | + if (!tableRef) return; |
| 457 | +
|
| 458 | + tableRef.querySelectorAll('thead tr th').forEach((cell) => { |
| 459 | + resizeObserver.observe(cell); |
| 460 | + }); |
| 461 | + }; |
| 462 | +
|
| 463 | + const getDimensions = () => { |
| 464 | + if (resizable === 'none') return; |
| 465 | + else if (resizable === 'columns') { |
| 466 | + observeHeaderColumns(); |
| 467 | + } else if (resizable === 'rows') { |
| 468 | + observeFirstCells(); |
| 469 | + } else { |
| 470 | + observeHeaderColumns(); |
| 471 | + observeFirstCells(); |
| 472 | + } |
| 473 | + }; |
| 474 | +
|
378 | 475 | $: sortKeys = pluginStates.sort.sortKeys; |
379 | 476 | $: serverSide && updateTable(); |
380 | 477 | $: serverSide && sortServer($sortKeys[0]?.order, $sortKeys[0]?.id); |
381 | 478 | $: $hiddenColumnIds = shownColumns.filter((col) => !col.visible).map((col) => col.id); |
| 479 | + $: tableRef && getDimensions(); |
| 480 | + $: $headerRows.length > 0 && getMinCellWidthInColumn(); |
| 481 | + $: $pageRows.length > 0 && getMaxCellHeightInRow(); |
382 | 482 | </script> |
383 | 483 |
|
384 | 484 | <div class="grid gap-2 overflow-auto" class:w-fit={!fitToScreen} class:w-full={fitToScreen}> |
|
492 | 592 |
|
493 | 593 | <div class="overflow-auto" style="height: {height}px"> |
494 | 594 | <table |
| 595 | + bind:this={tableRef} |
495 | 596 | {...$tableAttrs} |
496 | 597 | class="table table-auto table-compact bg-tertiary-500/30 dark:bg-tertiary-900/10 overflow-clip" |
497 | 598 | id="{tableId}-table" |
|
507 | 608 | let:rowProps |
508 | 609 | > |
509 | 610 | <tr {...rowAttrs} class="bg-primary-300 dark:bg-primary-800"> |
510 | | - {#each headerRow.cells as cell (cell.id)} |
| 611 | + {#each headerRow.cells as cell, index (cell.id)} |
511 | 612 | <Subscribe attrs={cell.attrs()} props={cell.props()} let:props let:attrs> |
512 | | - <th scope="col" class="!p-2" {...attrs} style={cellStyle(cell.id, columns)}> |
| 613 | + <th |
| 614 | + scope="col" |
| 615 | + class="!p-2" |
| 616 | + {...attrs} |
| 617 | + style={` |
| 618 | + width: ${cell.isData() ? 'auto' : '0'}; |
| 619 | + ${cellStyle(cell.id, columns)} |
| 620 | + `} |
| 621 | + > |
513 | 622 | <div |
514 | 623 | class="overflow-auto" |
515 | 624 | class:resize-x={(resizable === 'columns' || resizable === 'both') && |
516 | 625 | !fixedWidth(cell.id, columns)} |
517 | 626 | id="th-{tableId}-{cell.id}" |
| 627 | + style={` |
| 628 | + min-width: ${minWidth(cell.id, columns) ? minWidth(cell.id, columns) : $colWidths[index]}px; |
| 629 | + `} |
518 | 630 | > |
519 | 631 | <div class="flex justify-between items-center"> |
520 | 632 | <div class="flex gap-1 whitespace-pre-wrap"> |
|
563 | 675 | <tr {...rowAttrs} id="{tableId}-row-{row.id}" class=""> |
564 | 676 | {#each row.cells as cell, index (cell?.id)} |
565 | 677 | <Subscribe attrs={cell.attrs()} let:attrs> |
566 | | - <td {...attrs} class="!p-2"> |
| 678 | + <td {...attrs} class=""> |
567 | 679 | <div |
568 | | - class=" overflow-auto h-max {index === 0 && |
| 680 | + class=" h-full {index === 0 && |
569 | 681 | (resizable === 'rows' || resizable === 'both') |
570 | | - ? 'resize-y' |
| 682 | + ? 'resize-y overflow-auto' |
571 | 683 | : ''}" |
572 | 684 | id="{tableId}-{cell.id}-{row.id}" |
| 685 | + style={` |
| 686 | + min-height: ${$rowHeights && $rowHeights[row.id] ? `${$rowHeights[row.id].min}px` : 'auto'}; |
| 687 | + max-height: ${ |
| 688 | + index !== 0 && $rowHeights && $rowHeights[row.id] |
| 689 | + ? `${$rowHeights[row.id].max}px` |
| 690 | + : 'auto' |
| 691 | + }; |
| 692 | + height: ${$rowHeights && $rowHeights[+row.id] ? `${$rowHeights[+row.id].min}px` : 'auto'}; |
| 693 | + `} |
573 | 694 | > |
574 | 695 | <!-- Adding config for initial rowHeight, if provided --> |
575 | 696 | <div |
576 | | - class="flex items-center overflow-auto" |
577 | | - style="height: {rowHeight ? `${rowHeight}px` : 'auto'};" |
| 697 | + class="flex items-start overflow-auto" |
| 698 | + style={` |
| 699 | + max-height: ${$rowHeights && $rowHeights[row.id] ? `${$rowHeights[row.id].max}px` : 'auto'}; |
| 700 | + `} |
578 | 701 | > |
579 | | - <div class="grow h-full"><Render of={cell.render()} /></div> |
| 702 | + <div |
| 703 | + class="grow overflow-auto" |
| 704 | + style={cell.isData() |
| 705 | + ? `width: ${ |
| 706 | + minWidth(cell.id, columns) |
| 707 | + ? minWidth(cell.id, columns) |
| 708 | + : $colWidths[index] |
| 709 | + }px;` |
| 710 | + : 'max-width: min-content;'} |
| 711 | + > |
| 712 | + <Render of={cell.render()} /> |
| 713 | + </div> |
580 | 714 | </div> |
581 | 715 | </div> |
582 | 716 | </td> |
|
0 commit comments