@svelte-lean/table 0.13.2 → 0.13.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/style.css +30 -0
- package/dist/table/data-table.svelte +21 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @svelte-lean/table
|
|
2
2
|
|
|
3
|
+
## 0.13.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- c97ea03: A grid-layout table with grouped columns draws its header rows straight again: a header row kept the cells that span down to it after the grouped ones, and grid auto-placement, which never moves back along a row, pushed the grouped leaves onto a second line. The cells are now in column order, and the room a spanning cell leaves above it takes the header's surface.
|
|
8
|
+
|
|
9
|
+
## 0.13.3
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- d30fe0c: A table wider than its region keeps its header in view: without `height`, a region that scrolls sideways used to leave the header behind as the page scrolled. The header now follows the page (a scroll-driven animation on the region's view timeline, run by the compositor), from `--slean-table-sticky-top` down to the table's last row. Browsers without scroll-driven animations keep the old behaviour.
|
|
14
|
+
|
|
3
15
|
## 0.13.2
|
|
4
16
|
|
|
5
17
|
No changes in this release.
|
package/dist/style.css
CHANGED
|
@@ -327,6 +327,32 @@
|
|
|
327
327
|
:where(.slean-table-scroll[data-page-sticky] > .slean-table-sticky > thead) {
|
|
328
328
|
top: var(--slean-table-sticky-top, 0px);
|
|
329
329
|
}
|
|
330
|
+
/* Sideways-only region: the header follows the page (data-table.svelte, `data-head-follows`). */
|
|
331
|
+
@supports (animation-timeline: view()) {
|
|
332
|
+
:where(.slean-table-scroll[data-head-follows]) {
|
|
333
|
+
view-timeline-name: --slean-table-region;
|
|
334
|
+
view-timeline-axis: block;
|
|
335
|
+
view-timeline-inset: var(--slean-table-sticky-top, 0px) 0px;
|
|
336
|
+
}
|
|
337
|
+
:where(.slean-table-scroll[data-head-follows] > table > thead) {
|
|
338
|
+
position: relative;
|
|
339
|
+
z-index: 5;
|
|
340
|
+
animation-name: slean-table-head-follow;
|
|
341
|
+
animation-duration: auto;
|
|
342
|
+
animation-timing-function: linear;
|
|
343
|
+
animation-fill-mode: both;
|
|
344
|
+
animation-timeline: --slean-table-region;
|
|
345
|
+
animation-range: exit-crossing 0px exit-crossing var(--slean-table-travel, 0px);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
@keyframes slean-table-head-follow {
|
|
349
|
+
from {
|
|
350
|
+
translate: 0 0;
|
|
351
|
+
}
|
|
352
|
+
to {
|
|
353
|
+
translate: 0 var(--slean-table-travel, 0px);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
330
356
|
:where(
|
|
331
357
|
.slean-table
|
|
332
358
|
> .slean-table-scroll[data-page-sticky]:first-child
|
|
@@ -910,6 +936,10 @@
|
|
|
910
936
|
}
|
|
911
937
|
/* Each body row its own stacking context: sticky pinned cells then layerize per row, not per
|
|
912
938
|
cell, which keeps compositing cheap while a long page scrolls. */
|
|
939
|
+
/* A header row is a grid too: the room a spanning cell leaves above it is the head's surface. */
|
|
940
|
+
:where(.slean-table[data-layout='grid'] > .slean-table-scroll > table > thead > tr) {
|
|
941
|
+
background: var(--slean-table-header-bg);
|
|
942
|
+
}
|
|
913
943
|
:where(.slean-table[data-layout='grid'] > .slean-table-scroll > table > tbody > tr) {
|
|
914
944
|
isolation: isolate;
|
|
915
945
|
}
|
|
@@ -776,7 +776,8 @@
|
|
|
776
776
|
/**
|
|
777
777
|
* Header rows. In grid layout every row is a grid of its own, so a cell spanning rows sits in
|
|
778
778
|
* the lowest row it spans, placed at its first column; the leading control headers sit there
|
|
779
|
-
* too.
|
|
779
|
+
* too. A row's cells are kept in column order: auto-placement never moves back along a row, so
|
|
780
|
+
* a cell placed before one to its left would push that one onto a second grid row.
|
|
780
781
|
*/
|
|
781
782
|
const leafIndex = $derived(new Map(cols.map((col, i) => [col.id, i])));
|
|
782
783
|
function firstLeaf(column: Column<T>): number {
|
|
@@ -787,15 +788,19 @@
|
|
|
787
788
|
const levels = table.headers;
|
|
788
789
|
if (!grid) return levels.map((row) => row.map((cell) => ({ cell })));
|
|
789
790
|
const lead = leads.length;
|
|
790
|
-
const result: { cell: HeaderCell<T>; place?: string }[][] = levels.map(() => []);
|
|
791
|
+
const result: { cell: HeaderCell<T>; place?: string; start: number }[][] = levels.map(() => []);
|
|
791
792
|
levels.forEach((row, level) =>
|
|
792
793
|
row.forEach((cell) => {
|
|
793
794
|
const target = Math.min(levels.length - 1, level + cell.rowSpan - 1);
|
|
794
795
|
const start = lead + firstLeaf(cell.column) + 1;
|
|
795
|
-
result[target]!.push({
|
|
796
|
+
result[target]!.push({
|
|
797
|
+
cell,
|
|
798
|
+
start,
|
|
799
|
+
place: `grid-column:${start} / span ${cell.colSpan};`
|
|
800
|
+
});
|
|
796
801
|
})
|
|
797
802
|
);
|
|
798
|
-
return result;
|
|
803
|
+
return result.map((row) => row.sort((a, b) => a.start - b.start));
|
|
799
804
|
});
|
|
800
805
|
const leadLevel = $derived(grid ? table.headers.length - 1 : 0);
|
|
801
806
|
/**
|
|
@@ -825,19 +830,26 @@
|
|
|
825
830
|
* header without ever letting it stick; the header is then not sticky at all (`scrollsY`), since
|
|
826
831
|
* a sticky box in a scroller is composited, and every row painted under it is weighed against it
|
|
827
832
|
* (at 1000 rows about a tenth of a fast scroll's main-thread work).
|
|
833
|
+
*
|
|
834
|
+
* Such a header follows the page instead (`data-head-follows`, style.css): a scroll-driven
|
|
835
|
+
* animation on the region's view timeline moves it down by as much as the region has passed
|
|
836
|
+
* under the page's top, never past its last row (`--slean-table-travel`). The compositor runs
|
|
837
|
+
* it; script only measures the travel when a size changes.
|
|
828
838
|
*/
|
|
829
839
|
let fits = $state(false);
|
|
830
840
|
let scrollsY = $state(true);
|
|
841
|
+
let travel = $state(0);
|
|
831
842
|
$effect(() => {
|
|
832
843
|
if (!stickyHeader || height || typeof ResizeObserver === 'undefined') {
|
|
833
844
|
fits = false;
|
|
834
845
|
scrollsY = true;
|
|
835
846
|
return;
|
|
836
847
|
}
|
|
837
|
-
const table = viewport.firstElementChild as
|
|
848
|
+
const table = viewport.firstElementChild as HTMLTableElement;
|
|
838
849
|
const observer = new ResizeObserver(() => {
|
|
839
850
|
fits = table.offsetWidth <= viewport.clientWidth + 1;
|
|
840
851
|
scrollsY = viewport.scrollHeight > viewport.clientHeight + 1;
|
|
852
|
+
travel = Math.max(0, table.offsetHeight - (table.tHead?.offsetHeight ?? 0));
|
|
841
853
|
});
|
|
842
854
|
observer.observe(viewport);
|
|
843
855
|
observer.observe(table);
|
|
@@ -1683,6 +1695,10 @@
|
|
|
1683
1695
|
bind:this={viewport}
|
|
1684
1696
|
class="slean-table-scroll"
|
|
1685
1697
|
data-page-sticky={fits || undefined}
|
|
1698
|
+
data-head-follows={(stickyHeader && !height && !fits && !scrollsY) || undefined}
|
|
1699
|
+
style:--slean-table-travel={stickyHeader && !height && !fits && !scrollsY
|
|
1700
|
+
? `${travel}px`
|
|
1701
|
+
: undefined}
|
|
1686
1702
|
style:max-height={height}
|
|
1687
1703
|
tabindex={height ? 0 : undefined}
|
|
1688
1704
|
role="region"
|