@nuvui/table 0.0.0-stage → 0.1.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.
Files changed (40) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +57 -2
  3. package/dist/components/data-table/content.d.ts +67 -0
  4. package/dist/components/data-table/content.js +278 -0
  5. package/dist/components/data-table/context.d.ts +23 -0
  6. package/dist/components/data-table/context.js +51 -0
  7. package/dist/components/data-table/create-data-table-hook.d.ts +78 -0
  8. package/dist/components/data-table/create-data-table-hook.js +103 -0
  9. package/dist/components/data-table/data-table.d.ts +76 -0
  10. package/dist/components/data-table/data-table.js +178 -0
  11. package/dist/components/data-table/header.d.ts +23 -0
  12. package/dist/components/data-table/header.js +120 -0
  13. package/dist/components/data-table/index.d.ts +11 -0
  14. package/dist/components/data-table/index.js +10 -0
  15. package/dist/components/data-table/labels.d.ts +50 -0
  16. package/dist/components/data-table/labels.js +35 -0
  17. package/dist/components/data-table/pagination.d.ts +18 -0
  18. package/dist/components/data-table/pagination.js +126 -0
  19. package/dist/components/data-table/selection.d.ts +48 -0
  20. package/dist/components/data-table/selection.js +153 -0
  21. package/dist/components/data-table/toolbar.d.ts +51 -0
  22. package/dist/components/data-table/toolbar.js +123 -0
  23. package/dist/components/data-table/use-data-table-request.d.ts +44 -0
  24. package/dist/components/data-table/use-data-table-request.js +72 -0
  25. package/dist/components/table/index.d.ts +2 -0
  26. package/dist/components/table/index.js +2 -0
  27. package/dist/components/table/table.d.ts +100 -0
  28. package/dist/components/table/table.js +130 -0
  29. package/dist/css/data-table.css +278 -0
  30. package/dist/css/table.css +164 -0
  31. package/dist/full.d.ts +264 -0
  32. package/dist/full.js +44 -0
  33. package/dist/index.d.ts +14 -0
  34. package/dist/index.js +11 -0
  35. package/dist/styles.css +441 -0
  36. package/dist/utils/cx.js +6 -0
  37. package/dist/utils/use-shift-click.js +32 -0
  38. package/dist/virtual.d.ts +42 -0
  39. package/dist/virtual.js +133 -0
  40. package/package.json +101 -3
@@ -0,0 +1,441 @@
1
+ @layer tokens, base, components;
2
+ @layer components {
3
+ .nuv-table-container {
4
+ position: relative;
5
+ max-inline-size: 100%;
6
+ max-block-size: var(--nuv-table-container-max-height, none);
7
+ overflow: auto;
8
+ border: var(--nuv-table-container-border-width, var(--nuv-border-width)) solid var(--nuv-table-border, var(--color-border));
9
+ border-radius: var(--nuv-table-container-radius, var(--radius-lg));
10
+ background-color: var(--nuv-table-bg, var(--color-surface));
11
+ }
12
+ .nuv-table-container:focus-visible {
13
+ outline: var(--nuv-focus-ring-width, 2px) solid var(--nuv-focus-ring-color, var(--color-ring));
14
+ outline-offset: var(--nuv-focus-ring-offset, -2px);
15
+ }
16
+ .nuv-table {
17
+ inline-size: 100%;
18
+ border-spacing: 0;
19
+ border-collapse: separate;
20
+ color: var(--nuv-table-fg, var(--color-surface-foreground));
21
+ font-size: var(--nuv-table-font-size, var(--text-sm));
22
+ line-height: var(--text-sm--line-height);
23
+ font-variant-numeric: tabular-nums;
24
+ text-align: start;
25
+ }
26
+ .nuv-table--fixed {
27
+ table-layout: fixed;
28
+ }
29
+ .nuv-table--fixed .nuv-table__head {
30
+ overflow: hidden;
31
+ text-overflow: ellipsis;
32
+ }
33
+ .nuv-table--fixed .nuv-table__cell {
34
+ overflow-wrap: anywhere;
35
+ }
36
+ .nuv-table__caption {
37
+ padding: var(--nuv-table-cell-padding-y, calc(var(--spacing) * 1.5)) var(--nuv-table-cell-padding-x, calc(var(--spacing) * 3));
38
+ color: var(--nuv-table-caption-fg, var(--color-muted-foreground));
39
+ text-align: start;
40
+ caption-side: var(--nuv-table-caption-side, bottom);
41
+ }
42
+ .nuv-table__caption--hidden {
43
+ position: absolute;
44
+ inline-size: 1px;
45
+ block-size: 1px;
46
+ margin: -1px;
47
+ padding: 0;
48
+ overflow: hidden;
49
+ clip-path: inset(50%);
50
+ white-space: nowrap;
51
+ }
52
+ .nuv-table__row {
53
+ background-color: var(--nuv-table-row-bg, var(--nuv-table-bg, var(--color-surface)));
54
+ }
55
+ .nuv-table__head,
56
+ .nuv-table__cell {
57
+ block-size: var(--nuv-table-row-height, var(--nuv-touch-size));
58
+ }
59
+ @media (pointer: fine) {
60
+ .nuv-table__head,
61
+ .nuv-table__cell {
62
+ block-size: var(--nuv-table-row-height, var(--nuv-control-height-md));
63
+ }
64
+ }
65
+ .nuv-table__head,
66
+ .nuv-table__cell {
67
+ padding: var(--nuv-table-cell-padding-y, calc(var(--spacing) * 1.5)) var(--nuv-table-cell-padding-x, calc(var(--spacing) * 3));
68
+ border-block-end: var(--nuv-border-width) solid var(--nuv-table-border, var(--color-border));
69
+ background-color: inherit;
70
+ vertical-align: middle;
71
+ }
72
+ .nuv-table__head {
73
+ background-color: var(--nuv-table-header-bg, var(--color-muted));
74
+ color: var(--nuv-table-header-fg, var(--color-muted-foreground));
75
+ font-weight: var(--font-weight-medium);
76
+ text-align: start;
77
+ white-space: nowrap;
78
+ }
79
+ .nuv-table__cell--row-header {
80
+ font-weight: var(--font-weight-medium);
81
+ text-align: start;
82
+ }
83
+ .nuv-table__head--align-center {
84
+ text-align: center;
85
+ }
86
+ .nuv-table__head--align-end {
87
+ text-align: end;
88
+ }
89
+ .nuv-table__cell--align-center {
90
+ text-align: center;
91
+ }
92
+ .nuv-table__cell--align-end {
93
+ text-align: end;
94
+ }
95
+ .nuv-table__head--pinned-start,
96
+ .nuv-table__head--pinned-end {
97
+ position: sticky;
98
+ z-index: 1;
99
+ }
100
+ .nuv-table__head--pinned-start {
101
+ inset-inline-start: 0;
102
+ }
103
+ .nuv-table__head--pinned-end {
104
+ inset-inline-end: 0;
105
+ }
106
+ .nuv-table__head--pinned-start.nuv-table__head--pinned-edge {
107
+ border-inline-end: var(--nuv-border-width) solid var(--nuv-table-border, var(--color-border));
108
+ }
109
+ .nuv-table__head--pinned-end.nuv-table__head--pinned-edge {
110
+ border-inline-start: var(--nuv-border-width) solid var(--nuv-table-border, var(--color-border));
111
+ }
112
+ .nuv-table__cell--pinned-start,
113
+ .nuv-table__cell--pinned-end {
114
+ position: sticky;
115
+ z-index: 1;
116
+ }
117
+ .nuv-table__cell--pinned-start {
118
+ inset-inline-start: 0;
119
+ }
120
+ .nuv-table__cell--pinned-end {
121
+ inset-inline-end: 0;
122
+ }
123
+ .nuv-table__cell--pinned-start.nuv-table__cell--pinned-edge {
124
+ border-inline-end: var(--nuv-border-width) solid var(--nuv-table-border, var(--color-border));
125
+ }
126
+ .nuv-table__cell--pinned-end.nuv-table__cell--pinned-edge {
127
+ border-inline-start: var(--nuv-border-width) solid var(--nuv-table-border, var(--color-border));
128
+ }
129
+ .nuv-table__row:last-child > .nuv-table__cell {
130
+ border-block-end-width: var(--nuv-table-last-row-border-width, 0);
131
+ }
132
+ .nuv-table__footer > .nuv-table__row {
133
+ background-color: var(--nuv-table-footer-bg, var(--color-muted));
134
+ font-weight: var(--font-weight-medium);
135
+ }
136
+ .nuv-table__footer .nuv-table__cell {
137
+ border-block-start: var(--nuv-border-width) solid var(--nuv-table-border, var(--color-border));
138
+ }
139
+ .nuv-table__body--striped > .nuv-table__row:nth-child(even) {
140
+ background-color: var(--nuv-table-stripe-bg, var(--color-muted));
141
+ }
142
+ @media (hover: hover) {
143
+ .nuv-table__body > .nuv-table__row:hover {
144
+ background-color: var(--nuv-table-row-hover-bg, var(--color-muted));
145
+ }
146
+ }
147
+ .nuv-table__body > .nuv-table__row[data-state=selected] {
148
+ background-color: var(--nuv-table-row-selected-bg, color-mix(in oklab, var(--color-primary) 12%, var(--color-surface)));
149
+ }
150
+ .nuv-table--sticky-header .nuv-table__head {
151
+ position: sticky;
152
+ inset-block-start: 0;
153
+ z-index: 2;
154
+ }
155
+ .nuv-table--sticky-header .nuv-table__head--pinned-start,
156
+ .nuv-table--sticky-header .nuv-table__head--pinned-end {
157
+ z-index: 3;
158
+ }
159
+ @media (forced-colors: active) {
160
+ .nuv-table__row[data-state=selected] > :first-child {
161
+ border-inline-start: calc(var(--spacing) * 1) solid Highlight;
162
+ }
163
+ }
164
+ }
165
+ @layer components {
166
+ .nuv-data-table {
167
+ display: flex;
168
+ flex-direction: column;
169
+ gap: var(--nuv-data-table-gap, calc(var(--spacing) * 3));
170
+ min-inline-size: 0;
171
+ color: var(--nuv-data-table-fg, var(--color-foreground));
172
+ font-size: var(--text-sm);
173
+ line-height: var(--text-sm--line-height);
174
+ }
175
+ .nuv-data-table__status {
176
+ position: absolute;
177
+ inline-size: 1px;
178
+ block-size: 1px;
179
+ margin: -1px;
180
+ overflow: hidden;
181
+ clip-path: inset(50%);
182
+ white-space: nowrap;
183
+ }
184
+ .nuv-data-table__toolbar {
185
+ display: flex;
186
+ flex-wrap: wrap;
187
+ gap: calc(var(--spacing) * 2);
188
+ align-items: center;
189
+ }
190
+ .nuv-data-table__toolbar-gap {
191
+ flex: 1 1 0;
192
+ }
193
+ .nuv-data-table__filter {
194
+ flex: 1 1 100%;
195
+ }
196
+ @media (width >= 40rem) {
197
+ .nuv-data-table__filter {
198
+ flex: 0 1 var(--nuv-data-table-filter-width, 16rem);
199
+ }
200
+ }
201
+ .nuv-data-table__column-filter {
202
+ inline-size: 100%;
203
+ min-inline-size: var(--nuv-data-table-column-filter-min-width, 6rem);
204
+ }
205
+ .nuv-data-table__bulk {
206
+ display: flex;
207
+ flex-wrap: wrap;
208
+ gap: calc(var(--spacing) * 2);
209
+ align-items: center;
210
+ padding: calc(var(--spacing) * 2) calc(var(--spacing) * 3);
211
+ border: var(--nuv-border-width) solid var(--nuv-data-table-bulk-border, var(--color-border));
212
+ border-radius: var(--nuv-data-table-bulk-radius, var(--radius-lg));
213
+ background-color: var(--nuv-data-table-bulk-bg, var(--color-muted));
214
+ }
215
+ .nuv-data-table__bulk[hidden] {
216
+ display: none;
217
+ }
218
+ .nuv-data-table__bulk-count {
219
+ font-weight: var(--font-weight-medium);
220
+ }
221
+ .nuv-data-table__bulk-actions {
222
+ display: flex;
223
+ flex: 1 1 auto;
224
+ flex-wrap: wrap;
225
+ gap: calc(var(--spacing) * 2);
226
+ align-items: center;
227
+ }
228
+ .nuv-data-table__heading {
229
+ min-inline-size: 0;
230
+ overflow: hidden;
231
+ text-overflow: ellipsis;
232
+ }
233
+ .nuv-data-table__sort {
234
+ display: inline-flex;
235
+ max-inline-size: 100%;
236
+ gap: calc(var(--spacing) * 1);
237
+ align-items: center;
238
+ min-block-size: var(--nuv-touch-size);
239
+ margin-block: calc(var(--spacing) * -1);
240
+ padding: 0;
241
+ border: 0;
242
+ border-radius: var(--radius-sm);
243
+ background: none;
244
+ color: inherit;
245
+ font: inherit;
246
+ text-align: inherit;
247
+ cursor: pointer;
248
+ -webkit-tap-highlight-color: transparent;
249
+ }
250
+ .nuv-data-table__sort:focus-visible {
251
+ outline: var(--nuv-focus-ring-width, 2px) solid var(--nuv-focus-ring-color, var(--color-ring));
252
+ outline-offset: var(--nuv-focus-ring-offset, 2px);
253
+ }
254
+ @media (pointer: fine) {
255
+ .nuv-data-table__sort {
256
+ min-block-size: var(--nuv-control-height-sm);
257
+ }
258
+ }
259
+ @media (hover: hover) {
260
+ .nuv-data-table__sort:hover {
261
+ color: var(--nuv-data-table-sort-hover-fg, var(--color-foreground));
262
+ }
263
+ }
264
+ .nuv-data-table__sort[data-sorted] {
265
+ color: var(--nuv-data-table-sorted-fg, var(--color-foreground));
266
+ }
267
+ .nuv-data-table__sort-icon {
268
+ flex: none;
269
+ opacity: 0.6;
270
+ }
271
+ .nuv-data-table__sort-icon[data-direction] {
272
+ opacity: 1;
273
+ }
274
+ .nuv-data-table__sort-index {
275
+ font-size: var(--text-xs);
276
+ }
277
+ .nuv-data-table__head--resizable {
278
+ position: relative;
279
+ }
280
+ .nuv-data-table__resize {
281
+ position: absolute;
282
+ inset-block: 0;
283
+ inset-inline-end: 0;
284
+ inline-size: calc(var(--spacing) * 4);
285
+ cursor: col-resize;
286
+ touch-action: none;
287
+ user-select: none;
288
+ }
289
+ .nuv-data-table__resize:focus-visible {
290
+ outline: var(--nuv-focus-ring-width, 2px) solid var(--nuv-focus-ring-color, var(--color-ring));
291
+ outline-offset: var(--nuv-focus-ring-offset, -2px);
292
+ }
293
+ @media (pointer: fine) {
294
+ .nuv-data-table__resize {
295
+ inline-size: calc(var(--spacing) * 2);
296
+ }
297
+ }
298
+ .nuv-data-table__resize::after {
299
+ position: absolute;
300
+ inset-block: 25%;
301
+ inset-inline-end: 0;
302
+ inline-size: 2px;
303
+ background-color: var(--nuv-data-table-resize-color, var(--color-border-strong));
304
+ content: "";
305
+ }
306
+ .nuv-data-table__resize[data-resizing]::after {
307
+ inset-block: 0;
308
+ background-color: var(--nuv-data-table-resize-active-color, var(--color-primary));
309
+ }
310
+ @media (hover: hover) {
311
+ .nuv-data-table__resize:hover::after {
312
+ inset-block: 0;
313
+ background-color: var(--nuv-data-table-resize-active-color, var(--color-primary));
314
+ }
315
+ }
316
+ .nuv-data-table__cell--control {
317
+ inline-size: 1%;
318
+ white-space: nowrap;
319
+ }
320
+ .nuv-data-table__expand,
321
+ .nuv-data-table__expand-gap {
322
+ display: inline-flex;
323
+ flex: none;
324
+ inline-size: var(--nuv-data-table-expand-size, 1.5rem);
325
+ block-size: var(--nuv-data-table-expand-size, 1.5rem);
326
+ margin-inline-start: calc(var(--nuv-data-table-indent, 1.25rem) * var(--nuv-data-table-depth, 0));
327
+ vertical-align: middle;
328
+ }
329
+ .nuv-data-table__expand {
330
+ position: relative;
331
+ align-items: center;
332
+ justify-content: center;
333
+ padding: 0;
334
+ border: 0;
335
+ border-radius: var(--radius-sm);
336
+ background: none;
337
+ color: var(--nuv-data-table-expand-fg, var(--color-muted-foreground));
338
+ cursor: pointer;
339
+ -webkit-tap-highlight-color: transparent;
340
+ }
341
+ .nuv-data-table__expand::after {
342
+ position: absolute;
343
+ top: 50%;
344
+ left: 50%;
345
+ inline-size: max(100%, var(--nuv-touch-size, 2.75rem));
346
+ block-size: max(100%, var(--nuv-touch-size, 2.75rem));
347
+ transform: translate(-50%, -50%);
348
+ content: "";
349
+ }
350
+ @media (pointer: fine) {
351
+ .nuv-data-table__expand::after {
352
+ content: none;
353
+ }
354
+ }
355
+ .nuv-data-table__expand:focus-visible {
356
+ outline: var(--nuv-focus-ring-width, 2px) solid var(--nuv-focus-ring-color, var(--color-ring));
357
+ outline-offset: var(--nuv-focus-ring-offset, 2px);
358
+ }
359
+ @media (hover: hover) {
360
+ .nuv-data-table__expand:hover {
361
+ color: var(--color-foreground);
362
+ }
363
+ }
364
+ @media (prefers-reduced-motion: no-preference) {
365
+ .nuv-data-table__expand-icon {
366
+ transition: transform var(--nuv-duration-fast) var(--ease-out);
367
+ }
368
+ }
369
+ .nuv-data-table__expand-icon:dir(rtl) {
370
+ transform: scaleX(-1);
371
+ }
372
+ .nuv-data-table__expand[aria-expanded=true] > .nuv-data-table__expand-icon {
373
+ transform: rotate(90deg);
374
+ }
375
+ .nuv-data-table__detail-row {
376
+ --nuv-table-row-bg: var(--nuv-data-table-detail-bg, var(--color-background));
377
+ --nuv-table-row-hover-bg: var(--nuv-data-table-detail-bg, var(--color-background));
378
+ }
379
+ .nuv-data-table__message-row,
380
+ .nuv-data-table__loading-row {
381
+ --nuv-table-row-hover-bg: var(--nuv-table-bg, var(--color-surface));
382
+ }
383
+ .nuv-data-table__detail {
384
+ --nuv-table-cell-padding-x: var(--nuv-data-table-detail-padding, calc(var(--spacing) * 4));
385
+ --nuv-table-cell-padding-y: var(--nuv-data-table-detail-padding, calc(var(--spacing) * 4));
386
+ }
387
+ .nuv-data-table__message {
388
+ --nuv-table-cell-padding-y: var(--nuv-data-table-message-padding, calc(var(--spacing) * 10));
389
+ color: var(--nuv-data-table-message-fg, var(--color-muted-foreground));
390
+ text-align: center;
391
+ }
392
+ .nuv-data-table__message > * + * {
393
+ margin-inline-start: calc(var(--spacing) * 3);
394
+ }
395
+ .nuv-data-table__pagination {
396
+ display: flex;
397
+ flex-wrap: wrap;
398
+ gap: calc(var(--spacing) * 2) calc(var(--spacing) * 4);
399
+ align-items: center;
400
+ justify-content: space-between;
401
+ color: var(--nuv-data-table-pagination-fg, var(--color-muted-foreground));
402
+ }
403
+ @media (width >= 40rem) {
404
+ .nuv-data-table__pagination {
405
+ justify-content: flex-end;
406
+ }
407
+ }
408
+ .nuv-data-table__page-size {
409
+ display: flex;
410
+ white-space: nowrap;
411
+ gap: calc(var(--spacing) * 2);
412
+ align-items: center;
413
+ }
414
+ .nuv-data-table__page-range,
415
+ .nuv-data-table__page {
416
+ white-space: nowrap;
417
+ }
418
+ .nuv-data-table__pages {
419
+ display: flex;
420
+ flex: 1 1 100%;
421
+ gap: calc(var(--spacing) * 1);
422
+ align-items: center;
423
+ justify-content: space-between;
424
+ }
425
+ @media (width >= 40rem) {
426
+ .nuv-data-table__pages {
427
+ flex: none;
428
+ justify-content: flex-start;
429
+ }
430
+ }
431
+ .nuv-data-table__page-button {
432
+ --nuv-button-padding-inline: 0;
433
+ }
434
+ .nuv-data-table__page-button[aria-disabled=true] {
435
+ opacity: var(--nuv-disabled-opacity);
436
+ cursor: not-allowed;
437
+ }
438
+ .nuv-data-table__page-icon:dir(rtl) {
439
+ transform: scaleX(-1);
440
+ }
441
+ }
@@ -0,0 +1,6 @@
1
+ //#region src/utils/cx.ts
2
+ function cx(...classes) {
3
+ return classes.filter(Boolean).join(" ");
4
+ }
5
+ //#endregion
6
+ export { cx };
@@ -0,0 +1,32 @@
1
+ import { useRef } from "react";
2
+ //#region src/utils/use-shift-click.ts
3
+ /**
4
+ * Whether Shift was held for a click, including a click made with the
5
+ * keyboard.
6
+ *
7
+ * Space or Enter on a button gives a click event. Chromium and WebKit mark
8
+ * that event with the Shift that was held. Firefox doesn't, so Shift and
9
+ * Space would never select a range there. The key that's about to make the
10
+ * click is watched, and its Shift is used for the click that follows.
11
+ */
12
+ function useShiftClick() {
13
+ const held = useRef(false);
14
+ return {
15
+ props: {
16
+ onKeyDown(event) {
17
+ if (event.key === " " || event.key === "Enter") held.current = event.shiftKey;
18
+ },
19
+ onBlur() {
20
+ held.current = false;
21
+ }
22
+ },
23
+ /** Call it once, in the click handler. */
24
+ shift(event) {
25
+ const shift = event.shiftKey || held.current;
26
+ held.current = false;
27
+ return shift;
28
+ }
29
+ };
30
+ }
31
+ //#endregion
32
+ export { useShiftClick };
@@ -0,0 +1,42 @@
1
+ import { DataTableContentOwnProps, DataTableName } from "./components/data-table/content.js";
2
+ import { DataTableProps } from "./components/data-table/data-table.js";
3
+ import { RowData, TableFeatures } from "@tanstack/react-table";
4
+ //#region src/virtual.d.ts
5
+ export interface DataTableVirtualOwnProps {
6
+ /**
7
+ * How tall the table may get before its rows scroll. A number is pixels.
8
+ * The window of rows is cut to this, so it has to be set.
9
+ * @default 480
10
+ */
11
+ height?: number | string;
12
+ /**
13
+ * A guess at a row's height in pixels, used for rows that haven't been
14
+ * drawn yet. Each row is measured when it is.
15
+ * @default 40
16
+ */
17
+ estimateRowHeight?: number;
18
+ /**
19
+ * How many rows to draw beyond each edge of what's in view.
20
+ * @default 10
21
+ */
22
+ overscan?: number;
23
+ }
24
+ export type DataTableVirtualContentProps<TFeatures extends TableFeatures = any, TData extends RowData = any> = DataTableContentOwnProps<TFeatures, TData> & DataTableVirtualOwnProps & DataTableName;
25
+ /**
26
+ * The table with only the rows in view drawn, for thousands of rows at
27
+ * once. It takes what `DataTableContent` takes, and a height to scroll in.
28
+ * The header stays in view unless `stickyHeader` is `false`.
29
+ *
30
+ * A screen reader is told how many rows there are and where each one is,
31
+ * since it can't count the ones that aren't in the page.
32
+ */
33
+ export declare function DataTableVirtualContent<TFeatures extends TableFeatures = any, TData extends RowData = any>({ height, estimateRowHeight, overscan, stickyHeader, ...props }: DataTableVirtualContentProps<TFeatures, TData>): import("react").JSX.Element;
34
+ export type DataTableVirtualProps<TFeatures extends TableFeatures, TData extends RowData> = DataTableProps<TFeatures, TData> & DataTableVirtualOwnProps;
35
+ /**
36
+ * `DataTable` with only the rows in view drawn. Use it for a table of
37
+ * thousands of rows that are all loaded. It usually goes without
38
+ * pagination: leave `rowPaginationFeature` out, or pass
39
+ * `pagination={false}`.
40
+ */
41
+ export declare function DataTableVirtual<TFeatures extends TableFeatures, TData extends RowData>(props: DataTableVirtualProps<TFeatures, TData>): import("react").JSX.Element;
42
+ //#endregion
@@ -0,0 +1,133 @@
1
+ "use client";
2
+ import { useDataTableView } from "./components/data-table/context.js";
3
+ import { ContentFrame } from "./components/data-table/content.js";
4
+ import { DataTableLayout } from "./components/data-table/data-table.js";
5
+ import { useCallback, useEffect, useLayoutEffect, useState } from "react";
6
+ import { jsx } from "react/jsx-runtime";
7
+ import { defaultRangeExtractor, useVirtualizer } from "@tanstack/react-virtual";
8
+ //#region src/virtual.tsx
9
+ const useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
10
+ function Spacer({ height, columns }) {
11
+ return /* @__PURE__ */ jsx("tr", {
12
+ "aria-hidden": "true",
13
+ className: "nuv-data-table__spacer",
14
+ children: /* @__PURE__ */ jsx("td", {
15
+ colSpan: columns,
16
+ style: {
17
+ blockSize: height,
18
+ padding: 0,
19
+ border: 0
20
+ }
21
+ })
22
+ });
23
+ }
24
+ function VirtualRows({ rows, renderRow, headerRows, container, estimateRowHeight, overscan }) {
25
+ const { table } = useDataTableView("DataTableVirtualContent");
26
+ const [headerHeight, setHeaderHeight] = useState(0);
27
+ const rangeExtractor = useCallback((range) => {
28
+ const indexes = defaultRangeExtractor(range);
29
+ const active = typeof document === "undefined" ? null : document.activeElement;
30
+ if (!active || !container?.contains(active)) return indexes;
31
+ const row = active.closest("tr[data-index]");
32
+ const focused = row ? Number(row.getAttribute("data-index")) : -1;
33
+ if (focused < 0 || focused >= range.count || indexes.includes(focused)) return indexes;
34
+ return [...indexes, focused].sort((a, b) => a - b);
35
+ }, [container]);
36
+ const virtualizer = useVirtualizer({
37
+ count: rows.length,
38
+ getScrollElement: () => container,
39
+ estimateSize: () => estimateRowHeight,
40
+ getItemKey: (index) => rows[index]?.id ?? index,
41
+ overscan,
42
+ rangeExtractor,
43
+ scrollMargin: headerHeight,
44
+ measureElement: (element) => {
45
+ const panel = element.nextElementSibling;
46
+ const extra = panel?.classList.contains("nuv-data-table__detail-row") ? panel.offsetHeight : 0;
47
+ return element.offsetHeight + extra;
48
+ }
49
+ });
50
+ useIsomorphicLayoutEffect(() => {
51
+ const head = container?.querySelector("thead");
52
+ if (!head) return;
53
+ const measure = () => setHeaderHeight(head.getBoundingClientRect().height);
54
+ measure();
55
+ if (typeof ResizeObserver === "undefined") return;
56
+ const observer = new ResizeObserver(measure);
57
+ observer.observe(head);
58
+ return () => observer.disconnect();
59
+ }, [container]);
60
+ const open = table.atoms.expanded?.get();
61
+ useEffect(() => {
62
+ const box = container;
63
+ if (!box) return;
64
+ for (const row of box.querySelectorAll("tr[data-index]")) virtualizer.measureElement(row);
65
+ }, [open]);
66
+ const items = virtualizer.getVirtualItems();
67
+ const span = 1e3;
68
+ const total = virtualizer.getTotalSize();
69
+ const parts = [];
70
+ let end = headerHeight;
71
+ for (const item of items) {
72
+ const row = rows[item.index];
73
+ if (!row) continue;
74
+ if (item.start > end) parts.push(/* @__PURE__ */ jsx(Spacer, {
75
+ height: item.start - end,
76
+ columns: span
77
+ }, `gap-${item.index}`));
78
+ parts.push(renderRow(row, { rowProps: {
79
+ ref: virtualizer.measureElement,
80
+ "data-index": item.index,
81
+ "aria-rowindex": headerRows + item.index + 1
82
+ } }));
83
+ end = item.end;
84
+ }
85
+ const rest = total + headerHeight - end;
86
+ if (rest > 0) parts.push(/* @__PURE__ */ jsx(Spacer, {
87
+ height: rest,
88
+ columns: span
89
+ }, "gap-end"));
90
+ return parts;
91
+ }
92
+ /**
93
+ * The table with only the rows in view drawn, for thousands of rows at
94
+ * once. It takes what `DataTableContent` takes, and a height to scroll in.
95
+ * The header stays in view unless `stickyHeader` is `false`.
96
+ *
97
+ * A screen reader is told how many rows there are and where each one is,
98
+ * since it can't count the ones that aren't in the page.
99
+ */
100
+ function DataTableVirtualContent({ height = 480, estimateRowHeight = 40, overscan = 10, stickyHeader = true, ...props }) {
101
+ const { table } = useDataTableView("DataTableVirtualContent");
102
+ const [container, setContainer] = useState(null);
103
+ const headerRows = table.getHeaderGroups().length;
104
+ return /* @__PURE__ */ jsx(ContentFrame, {
105
+ ...props,
106
+ stickyHeader,
107
+ containerRef: setContainer,
108
+ containerProps: { style: { maxBlockSize: height } },
109
+ tableProps: { "aria-rowcount": headerRows + table.getRowModel().rows.length },
110
+ children: (rows, renderRow) => /* @__PURE__ */ jsx(VirtualRows, {
111
+ rows,
112
+ renderRow,
113
+ headerRows,
114
+ container,
115
+ estimateRowHeight,
116
+ overscan
117
+ })
118
+ });
119
+ }
120
+ /**
121
+ * `DataTable` with only the rows in view drawn. Use it for a table of
122
+ * thousands of rows that are all loaded. It usually goes without
123
+ * pagination: leave `rowPaginationFeature` out, or pass
124
+ * `pagination={false}`.
125
+ */
126
+ function DataTableVirtual(props) {
127
+ return /* @__PURE__ */ jsx(DataTableLayout, {
128
+ ...props,
129
+ content: (content) => /* @__PURE__ */ jsx(DataTableVirtualContent, { ...content })
130
+ });
131
+ }
132
+ //#endregion
133
+ export { DataTableVirtual, DataTableVirtualContent };