@svelte-lean/table 0.1.1 → 0.3.0
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 +20 -0
- package/README.md +6 -1
- package/dist/style.css +221 -9
- package/dist/table/data-table.svelte +990 -329
- package/dist/table/navigation.js +20 -2
- package/dist/table.svelte.js +34 -1
- package/dist/types.d.ts +25 -5
- package/package.json +6 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @svelte-lean/table
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- `pageSizeSelect="custom"` draws the page-size control with the Select primitive of `@svelte-lean/primitives`: a trigger button and a listbox popup drawn from the design tokens in every engine, around the same native `<select>`, which keeps the value and works without scripts. The application imports `@svelte-lean/primitives/select/register` and `@svelte-lean/styles/select.css`. `native` (a plain `<select>`) stays the default.
|
|
8
|
+
|
|
9
|
+
The ids that tie the control's label, trigger and listbox come from `$props.id()`, so the peer dependency is now `svelte ^5.20.0`.
|
|
10
|
+
|
|
11
|
+
## 0.2.0
|
|
12
|
+
|
|
13
|
+
### Minor Changes
|
|
14
|
+
|
|
15
|
+
- `layout="grid"` for long pages: the same elements laid out as CSS grid rows with explicit ARIA roles. Body rows stand in sections of 50 under `content-visibility: auto`, so the browser styles, lays out and paints only the sections near the screen; skipped sections assume the measured row height, and complete pages are laid out ahead in idle time. Column widths are measured once from hidden copies of the header and a sample of rows and kept; a declared `width` is a minimum and `maxWidth` (default 480) caps a measured width.
|
|
16
|
+
|
|
17
|
+
`progressive: true` now makes the first batch one screenful, slices are sized from the measured cost of a row, and a table first rendered in the browser mounts progressively too (server output and its hydration stay complete).
|
|
18
|
+
|
|
19
|
+
Rendering cost per row went down in both layouts: row nodes keep their identity across data updates with the same rows, body cells carry no per-cell `data-row` attribute or inline alignment style (alignment is `data-align`), row clicks, keys and drags are delegated to the table, and striping is a `data-even` attribute instead of a structural selector. Selectors that relied on `td[data-row]` should use `tr[data-row-id] > td[data-column]`.
|
|
20
|
+
|
|
21
|
+
A header in a region that scrolls sideways but not down (no `height`, a table wider than its container) is no longer `position: sticky`: it could never stick there, and a sticky box in a scroller made every row painted under it cost compositing work (about a tenth of a fast scroll through 1000 rows).
|
|
22
|
+
|
|
3
23
|
## 0.1.1
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ Svelte Lean Table: a data table for Svelte 5 that renders a native `<table>`, ke
|
|
|
9
9
|
plain object and adds capabilities through separate entry points. Part of the
|
|
10
10
|
[Svelte Lean](https://github.com/say16/svelte-lean) ecosystem.
|
|
11
11
|
|
|
12
|
-
>
|
|
12
|
+
> Pre-1.0: minor versions may change the API; see the changelog.
|
|
13
13
|
|
|
14
14
|
## Install
|
|
15
15
|
|
|
@@ -45,6 +45,11 @@ pinning, resizing, visibility and ordering, inline editing, cell ranges, row reo
|
|
|
45
45
|
empty and error states are props of `<DataTable />`. State is a serializable `TableState` object:
|
|
46
46
|
pass `bind:state` or `onStateChange` to own it.
|
|
47
47
|
|
|
48
|
+
Long pages: `progressive` mounts a large page in slices so the main thread never blocks on it, and
|
|
49
|
+
`layout="grid"` lays rows out as CSS grid rows in sections the browser skips while off screen
|
|
50
|
+
(`content-visibility: auto`), with column widths measured once from the content. Nothing is
|
|
51
|
+
virtualized; every row stays in the document.
|
|
52
|
+
|
|
48
53
|
Keyboard: with `onEdit` or `onCellRangeChange` the body is one Tab stop (roving `tabindex` over
|
|
49
54
|
cells, arrow keys move, Enter/F2 edits, focus returns to the cell after an edit). Text direction
|
|
50
55
|
follows the nearest `dir` attribute unless the `dir` prop is given. `selection: 'single'` needs an
|
package/dist/style.css
CHANGED
|
@@ -342,7 +342,9 @@
|
|
|
342
342
|
) {
|
|
343
343
|
border-start-end-radius: calc(var(--slean-table-radius) - 1px);
|
|
344
344
|
}
|
|
345
|
-
|
|
345
|
+
/* The last body row (`data-last`, set by the renderer) leaves the line to the region's border.
|
|
346
|
+
An attribute, not \`:last-child\`: appending a row then restyles two rows, not every cell. */
|
|
347
|
+
:where(.slean-table-scroll > table > tbody > tr[data-last] > td) {
|
|
346
348
|
border-bottom: 0;
|
|
347
349
|
}
|
|
348
350
|
:where(.slean-table-heading) {
|
|
@@ -426,6 +428,14 @@
|
|
|
426
428
|
min-width: 3rem;
|
|
427
429
|
font-weight: 400;
|
|
428
430
|
}
|
|
431
|
+
/* Alignment is an attribute, not an inline style: an inline style per cell defeats the
|
|
432
|
+
browser's style sharing between cells, which is most of the styling cost of a long table. */
|
|
433
|
+
:where(.slean-table-scroll > table > * > tr > td[data-align='end']) {
|
|
434
|
+
text-align: end;
|
|
435
|
+
}
|
|
436
|
+
:where(.slean-table-scroll > table > * > tr > td[data-align='center']) {
|
|
437
|
+
text-align: center;
|
|
438
|
+
}
|
|
429
439
|
:where(.slean-table-cell) {
|
|
430
440
|
display: flex;
|
|
431
441
|
align-items: center;
|
|
@@ -476,6 +486,28 @@
|
|
|
476
486
|
) {
|
|
477
487
|
color: var(--slean-table-fg);
|
|
478
488
|
}
|
|
489
|
+
/* The expand toggle of a plain grid row: the chevron is drawn by the button itself (two borders
|
|
490
|
+
of a turned square), one element per row instead of an SVG. It points down when open, and the
|
|
491
|
+
other way in right-to-left tables. */
|
|
492
|
+
:where(.slean-table-chevron)::before {
|
|
493
|
+
content: '';
|
|
494
|
+
display: block;
|
|
495
|
+
inline-size: 0.4375rem;
|
|
496
|
+
block-size: 0.4375rem;
|
|
497
|
+
margin: auto;
|
|
498
|
+
border: solid currentColor;
|
|
499
|
+
border-width: 0 1.5px 1.5px 0;
|
|
500
|
+
rotate: -45deg;
|
|
501
|
+
translate: -1px 0;
|
|
502
|
+
}
|
|
503
|
+
:where(.slean-table-chevron[aria-expanded='true'])::before {
|
|
504
|
+
rotate: 45deg;
|
|
505
|
+
translate: 0 -1px;
|
|
506
|
+
}
|
|
507
|
+
:where(.slean-table-chevron[aria-expanded='false']):dir(rtl)::before {
|
|
508
|
+
rotate: 135deg;
|
|
509
|
+
translate: 1px 0;
|
|
510
|
+
}
|
|
479
511
|
/* Keeps the text of rows without a toggle aligned with the rows that have one. */
|
|
480
512
|
:where(.slean-table-expand-spacer) {
|
|
481
513
|
inline-size: 1.5rem;
|
|
@@ -502,14 +534,9 @@
|
|
|
502
534
|
}
|
|
503
535
|
|
|
504
536
|
/* Row states write one variable on the row; cells paint it (pinned cells included). Stripes
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
> .slean-table-scroll
|
|
509
|
-
> table
|
|
510
|
-
> tbody
|
|
511
|
-
> tr:nth-child(even of [data-row-id])
|
|
512
|
-
) {
|
|
537
|
+
follow the row's position on the page (`data-even`, set by the renderer), so an open detail
|
|
538
|
+
row does not shift them and inserting rows restyles nothing else. */
|
|
539
|
+
:where(.slean-table[data-striped='true'] > .slean-table-scroll > table > tbody > tr[data-even]) {
|
|
513
540
|
--slean-table-row-bg: var(--slean-table-row-stripe);
|
|
514
541
|
}
|
|
515
542
|
:where(
|
|
@@ -673,6 +700,13 @@
|
|
|
673
700
|
gap: 0.5rem;
|
|
674
701
|
white-space: nowrap;
|
|
675
702
|
}
|
|
703
|
+
/* The Select primitive as the page-size control (`pageSizeSelect="custom"`): as tall as the
|
|
704
|
+
pager's buttons, as wide as its longest option. */
|
|
705
|
+
:where(.slean-table-page-select) {
|
|
706
|
+
--slean-control-height-sm: var(--slean-table-control-height);
|
|
707
|
+
min-inline-size: 4.75rem;
|
|
708
|
+
font-size: inherit;
|
|
709
|
+
}
|
|
676
710
|
:where(.slean-table-pager, .slean-table-pages) {
|
|
677
711
|
display: flex;
|
|
678
712
|
align-items: center;
|
|
@@ -773,6 +807,184 @@
|
|
|
773
807
|
}
|
|
774
808
|
}
|
|
775
809
|
/* Print: inline sticky offsets and the scroll limit are inline styles, hence !important. */
|
|
810
|
+
/*
|
|
811
|
+
* Grid layout (`layout="grid"`). The native elements stay, with explicit ARIA roles; every row
|
|
812
|
+
* is a grid over the renderer's track list (`--slean-table-tracks`), and a body section off
|
|
813
|
+
* screen is skipped by the browser (`content-visibility: auto`), so a long page costs the layout
|
|
814
|
+
* and paint of what is on screen. `--slean-table-min-width` is the sum of the tracks' minimums:
|
|
815
|
+
* below it the rows scroll sideways. `data-measuring` marks the hidden copy of a few rows the
|
|
816
|
+
* renderer reads the content widths from, and removes before the frame ends.
|
|
817
|
+
*/
|
|
818
|
+
:where(.slean-table[data-layout='grid'] > .slean-table-scroll > table) {
|
|
819
|
+
display: block;
|
|
820
|
+
width: max(100%, var(--slean-table-min-width, 0px));
|
|
821
|
+
min-width: 0;
|
|
822
|
+
}
|
|
823
|
+
:where(
|
|
824
|
+
.slean-table[data-layout='grid']
|
|
825
|
+
> .slean-table-scroll
|
|
826
|
+
> table
|
|
827
|
+
> :is(thead, tbody, tfoot, caption)
|
|
828
|
+
) {
|
|
829
|
+
display: block;
|
|
830
|
+
}
|
|
831
|
+
:where(.slean-table[data-layout='grid'] > .slean-table-scroll > table > * > tr) {
|
|
832
|
+
display: grid;
|
|
833
|
+
grid-template-columns: var(--slean-table-tracks);
|
|
834
|
+
}
|
|
835
|
+
/* Each body row its own stacking context: sticky pinned cells then layerize per row, not per
|
|
836
|
+
cell, which keeps compositing cheap while a long page scrolls. */
|
|
837
|
+
:where(.slean-table[data-layout='grid'] > .slean-table-scroll > table > tbody > tr) {
|
|
838
|
+
isolation: isolate;
|
|
839
|
+
}
|
|
840
|
+
:where(.slean-table[data-layout='grid'] > .slean-table-scroll > table > tbody.slean-table-chunk) {
|
|
841
|
+
content-visibility: auto;
|
|
842
|
+
contain-intrinsic-block-size: auto
|
|
843
|
+
calc(var(--slean-table-chunk-rows, 50) * var(--slean-table-row-estimate, 2.75rem));
|
|
844
|
+
}
|
|
845
|
+
/* A section whose rows are still being mounted holds the height they are expected to stand. */
|
|
846
|
+
:where(
|
|
847
|
+
.slean-table[data-layout='grid']
|
|
848
|
+
> .slean-table-scroll
|
|
849
|
+
> table
|
|
850
|
+
> tbody.slean-table-chunk[data-pending]
|
|
851
|
+
) {
|
|
852
|
+
min-block-size: calc(
|
|
853
|
+
var(--slean-table-chunk-rows, 50) * var(--slean-table-row-estimate, 2.75rem)
|
|
854
|
+
);
|
|
855
|
+
}
|
|
856
|
+
:where(.slean-table[data-layout='grid'] > .slean-table-scroll > table > * > tr > :is(th, td)) {
|
|
857
|
+
display: flex;
|
|
858
|
+
align-items: center;
|
|
859
|
+
min-width: 0;
|
|
860
|
+
}
|
|
861
|
+
:where(
|
|
862
|
+
.slean-table[data-layout='grid'] > .slean-table-scroll > table > * > tr > :is(th, td) > *
|
|
863
|
+
) {
|
|
864
|
+
flex: 1 1 auto;
|
|
865
|
+
min-width: 0;
|
|
866
|
+
}
|
|
867
|
+
:where(
|
|
868
|
+
.slean-table[data-layout='grid'] > .slean-table-scroll > table > * > tr > .slean-table-full
|
|
869
|
+
) {
|
|
870
|
+
grid-column: 1 / -1;
|
|
871
|
+
display: block;
|
|
872
|
+
}
|
|
873
|
+
:where(
|
|
874
|
+
.slean-table[data-layout='grid'] > .slean-table-scroll > table > tbody > tr > td[data-clip]
|
|
875
|
+
) {
|
|
876
|
+
overflow: hidden;
|
|
877
|
+
}
|
|
878
|
+
:where(
|
|
879
|
+
.slean-table[data-layout='grid']
|
|
880
|
+
> .slean-table-scroll
|
|
881
|
+
> table
|
|
882
|
+
> tbody
|
|
883
|
+
> tr
|
|
884
|
+
> td[data-clip]
|
|
885
|
+
> .slean-table-cell
|
|
886
|
+
) {
|
|
887
|
+
overflow: hidden;
|
|
888
|
+
white-space: nowrap;
|
|
889
|
+
text-overflow: ellipsis;
|
|
890
|
+
display: block;
|
|
891
|
+
}
|
|
892
|
+
:where(.slean-table-scroll > table[data-measuring]) {
|
|
893
|
+
display: block;
|
|
894
|
+
width: max-content;
|
|
895
|
+
}
|
|
896
|
+
:where(.slean-table-scroll > table[data-measuring] > *) {
|
|
897
|
+
display: block;
|
|
898
|
+
}
|
|
899
|
+
:where(.slean-table-scroll > table[data-measuring] > * > tr) {
|
|
900
|
+
display: grid;
|
|
901
|
+
content-visibility: visible;
|
|
902
|
+
}
|
|
903
|
+
:where(.slean-table-scroll > table[data-measuring] > * > tr > :is(th, td)) {
|
|
904
|
+
display: flex;
|
|
905
|
+
align-items: center;
|
|
906
|
+
position: static;
|
|
907
|
+
}
|
|
908
|
+
:where(.slean-table-scroll > table[data-measuring] > * > tr) {
|
|
909
|
+
grid-template-columns: none;
|
|
910
|
+
grid-auto-flow: column;
|
|
911
|
+
grid-auto-columns: max-content;
|
|
912
|
+
width: max-content;
|
|
913
|
+
}
|
|
914
|
+
:where(.slean-table-scroll > table[data-measuring] > * > tr > :is(th, td) > *) {
|
|
915
|
+
flex: none;
|
|
916
|
+
}
|
|
917
|
+
:where(.slean-table-scroll > table[data-measuring] > thead > tr) {
|
|
918
|
+
grid-auto-columns: min-content;
|
|
919
|
+
}
|
|
920
|
+
/* A plain grid cell is its own content box: the td carries `slean-table-cell` (no wrapper). */
|
|
921
|
+
:where(
|
|
922
|
+
.slean-table[data-layout='grid']
|
|
923
|
+
> .slean-table-scroll
|
|
924
|
+
> table
|
|
925
|
+
> tbody
|
|
926
|
+
> tr
|
|
927
|
+
> td.slean-table-cell
|
|
928
|
+
) {
|
|
929
|
+
gap: 0.5rem;
|
|
930
|
+
}
|
|
931
|
+
:where(
|
|
932
|
+
.slean-table[data-layout='grid']
|
|
933
|
+
> .slean-table-scroll
|
|
934
|
+
> table
|
|
935
|
+
> tbody
|
|
936
|
+
> tr
|
|
937
|
+
> td.slean-table-cell[data-align='end']
|
|
938
|
+
) {
|
|
939
|
+
justify-content: flex-end;
|
|
940
|
+
}
|
|
941
|
+
:where(
|
|
942
|
+
.slean-table[data-layout='grid']
|
|
943
|
+
> .slean-table-scroll
|
|
944
|
+
> table
|
|
945
|
+
> tbody
|
|
946
|
+
> tr
|
|
947
|
+
> td.slean-table-cell[data-align='center']
|
|
948
|
+
) {
|
|
949
|
+
justify-content: center;
|
|
950
|
+
}
|
|
951
|
+
:where(
|
|
952
|
+
.slean-table[data-layout='grid']
|
|
953
|
+
> .slean-table-scroll
|
|
954
|
+
> table
|
|
955
|
+
> tbody
|
|
956
|
+
> tr
|
|
957
|
+
> td.slean-table-cell:is([data-clip], .slean-table-ellipsis)
|
|
958
|
+
) {
|
|
959
|
+
display: block;
|
|
960
|
+
overflow: hidden;
|
|
961
|
+
white-space: nowrap;
|
|
962
|
+
text-overflow: ellipsis;
|
|
963
|
+
line-height: 1.5rem;
|
|
964
|
+
}
|
|
965
|
+
/* A grid body row paints its own ground once; its cells are transparent, except the sticky
|
|
966
|
+
ones (pinned, selection, expand), which must stay opaque over what scrolls beneath them. */
|
|
967
|
+
:where(.slean-table[data-layout='grid'] > .slean-table-scroll > table > tbody > tr) {
|
|
968
|
+
background: var(--slean-table-row-bg, var(--slean-table-bg));
|
|
969
|
+
}
|
|
970
|
+
:where(.slean-table[data-layout='grid'] > .slean-table-scroll > table > tbody > tr > td) {
|
|
971
|
+
background: none;
|
|
972
|
+
}
|
|
973
|
+
:where(
|
|
974
|
+
.slean-table[data-layout='grid']
|
|
975
|
+
> .slean-table-scroll
|
|
976
|
+
> table
|
|
977
|
+
> tbody
|
|
978
|
+
> tr
|
|
979
|
+
> :is([data-pin], .slean-table-selection, .slean-table-expander)
|
|
980
|
+
) {
|
|
981
|
+
background: var(--slean-table-row-bg, var(--slean-table-bg));
|
|
982
|
+
}
|
|
983
|
+
/* Grid header labels wrap at word boundaries rather than widen their column. */
|
|
984
|
+
:where(.slean-table[data-layout='grid'] > .slean-table-scroll > table > thead > tr > th) {
|
|
985
|
+
white-space: normal;
|
|
986
|
+
overflow-wrap: normal;
|
|
987
|
+
}
|
|
776
988
|
@media print {
|
|
777
989
|
:where(
|
|
778
990
|
.slean-table-toolbar,
|