@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 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({ cell, place: `grid-column:${start} / span ${cell.colSpan};` });
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 HTMLElement;
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"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svelte-lean/table",
3
- "version": "0.13.2",
3
+ "version": "0.13.4",
4
4
  "description": "A Svelte-native table and data-grid engine that ships only the features you use.",
5
5
  "license": "MIT",
6
6
  "author": "Ömer Say",