svelte-tably 1.0.0-next.9 → 1.0.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/dist/Table.svelte DELETED
@@ -1,906 +0,0 @@
1
- <!-- @component
2
-
3
- This is a description, \
4
- on how to use this.
5
-
6
- @example
7
- <Component />
8
-
9
- -->
10
-
11
- <script module lang="ts">
12
- export interface TableState<
13
- T extends Record<PropertyKey, any> = Record<PropertyKey, any>
14
- > {
15
- columns: Record<string, ColumnState<T>>
16
- panels: Record<string, TPanel<T>>
17
- selected: T[] | null
18
- sortby?: string
19
- sortReverse: boolean
20
- positions: {
21
- sticky: string[]
22
- scroll: string[]
23
- hidden: string[]
24
- toggle(key: string): void
25
- }
26
- readonly resizeable: boolean
27
- readonly data: T[]
28
- /** Rows become anchors */
29
- readonly href?: (item: T) => string
30
- addColumn(key: string, options: ColumnState<T>): void
31
- removeColumn(key: string): void
32
- }
33
-
34
- export function getTableState<T extends Record<PropertyKey, any> = Record<PropertyKey, any>>() {
35
- return getContext<TableState<T>>('svelte5-table')
36
- }
37
-
38
- export type HeaderSelectCtx<T = any> = {
39
- isSelected: boolean
40
- /** The list of selected items */
41
- readonly selected: T[]
42
- /**
43
- * See [MDN :indeterminate](https://developer.mozilla.org/en-US/docs/Web/CSS/:indeterminate)
44
- */
45
- readonly indeterminate: boolean
46
- }
47
-
48
- export type RowSelectCtx<T = any> = {
49
- readonly item: T
50
- readonly row: RowCtx<unknown>
51
- data: T[]
52
- isSelected: boolean
53
- }
54
- </script>
55
-
56
- <script lang="ts">
57
- import { getContext, onMount, setContext, tick, untrack, type Snippet } from 'svelte'
58
- import Column, { type ColumnProps, type RowCtx, type ColumnState } from './Column.svelte'
59
- import Panel, { PanelTween, type Panel as TPanel } from './Panel.svelte'
60
- import { fly } from 'svelte/transition'
61
- import { sineInOut } from 'svelte/easing'
62
- import { on } from 'svelte/events'
63
-
64
- type T = $$Generic<Record<PropertyKey, unknown>>
65
-
66
- type ConstructorReturnType<T extends new (...args: any[]) => any> = T extends new (...args: any[]) => infer K ? K : never
67
- type ConstructorParams<T extends new (...args: any[]) => any> = T extends new (...args: infer K) => any ? K : never
68
-
69
- type ContentCtx<T extends Record<PropertyKey, unknown>> = {
70
- Column: {
71
- new <V>(...args: ConstructorParams<typeof Column<T, V>>): ConstructorReturnType<typeof Column<T, V>>
72
- <V>(...args: Parameters<typeof Column<T, V>>): ReturnType<typeof Column<T, V>>
73
- }
74
- Panel: typeof Panel
75
- readonly table: TableState<T>
76
- readonly data: T[]
77
- }
78
-
79
- interface Props {
80
- content: Snippet<[context: ContentCtx<T>]>
81
-
82
- panel?: string
83
- data?: T[]
84
- id?: string
85
- href?: (item: T) => string
86
- /**
87
- * Can you change the width of the columns?
88
- * @default true
89
- */
90
- resizeable?: boolean
91
-
92
- selected?: T[]
93
- select?:
94
- | boolean
95
- | {
96
- /**
97
- * The style, in which the selection is shown
98
- *
99
- * NOTE: If using `edge` | 'side', "show" will always be `hover`. This is due to
100
- * an inconsistency/limitation of matching the scroll between the selection div and the rows.
101
- *
102
- * @default 'column'
103
- */
104
- style?: 'column'
105
- /**
106
- * When to show the row-select, when not selected?
107
- * @default 'hover'
108
- */
109
- show?: 'hover' | 'always' | 'never'
110
- /**
111
- * Custom snippet
112
- */
113
- headerSnippet?: Snippet<[context: HeaderSelectCtx]>
114
- rowSnippet?: Snippet<[context: RowSelectCtx<T>]>
115
- }
116
- // | {
117
- // /**
118
- // * The style, in which the selection is shown
119
- // *
120
- // * NOTE: If using `edge` | 'side', "show" will always be `hover`. This is due to
121
- // * an inconsistency/limitation of matching the scroll between the selection div and the rows.
122
- // *
123
- // * @default 'column'
124
- // */
125
- // style?: 'edge' | 'side'
126
- // /**
127
- // * When to show the row-select, when not selected?
128
- // * @default 'hover'
129
- // */
130
- // show?: 'hover'
131
- // /**
132
- // * Custom snippet
133
- // */
134
- // snippet?: Snippet<[context: { item: T, data: T[], selected: boolean }]>
135
- // }
136
-
137
- /*
138
- ordered?: {
139
- style?: 'column' | 'side' // combine with select if both use 'column'
140
- show?: 'hover' | 'always'
141
- // snippet?: Snippet<[context: { item: T, data: T[], selected: boolean }]>
142
- }
143
- */
144
- }
145
-
146
- let {
147
- content,
148
- selected = $bindable([]),
149
- panel = $bindable(),
150
- data: _data = [],
151
- id = Array.from({ length: 12 }, () => String.fromCharCode(Math.floor(Math.random() * 26) + 97)).join(''),
152
- href,
153
- resizeable = true,
154
- select
155
- }: Props = $props()
156
-
157
- let mounted = $state(false)
158
- onMount(() => (mounted = true))
159
-
160
- const data = $derived([..._data])
161
-
162
- const elements = $state({}) as Record<
163
- 'headers' | 'statusbar' | 'rows' | 'virtualTop' | 'virtualBottom' | 'selects',
164
- HTMLElement
165
- >
166
-
167
- let cols: TableState<T>['columns'] = $state({})
168
- let positions: TableState<T>['positions'] = $state({
169
- fixed: [],
170
- sticky: [],
171
- scroll: [],
172
- hidden: [],
173
- toggle(key) {
174
- if (table.positions.hidden.includes(key))
175
- table.positions.hidden = table.positions.hidden.filter((column) => column !== key)
176
- else table.positions.hidden.push(key)
177
- }
178
- })
179
-
180
- const table: TableState<T> = $state({
181
- columns: cols,
182
- selected,
183
- panels: {},
184
- positions,
185
- sortReverse: false,
186
- get href() {
187
- return href
188
- },
189
- get data() {
190
- return data
191
- },
192
- get resizeable() {
193
- return resizeable
194
- },
195
- addColumn(key, column) {
196
- table.columns[key] = column
197
-
198
- if (column.defaults.sort) sortBy(key)
199
-
200
- if (column.fixed) {
201
- // @ts-expect-error
202
- table.positions.fixed.push(key)
203
- return
204
- }
205
-
206
- if (!column.defaults.show) table.positions.hidden.push(key)
207
-
208
- if (column.defaults.sticky) table.positions.sticky.push(key)
209
- else table.positions.scroll.push(key)
210
- },
211
- removeColumn(key) {
212
- delete table.columns[key]
213
- // @ts-expect-error fixed is not typed
214
- table.positions.fixed = table.positions.fixed.filter((column) => column !== key)
215
- table.positions.sticky = table.positions.sticky.filter((column) => column !== key)
216
- table.positions.scroll = table.positions.scroll.filter((column) => column !== key)
217
- table.positions.hidden = table.positions.hidden.filter((column) => column !== key)
218
- }
219
- })
220
-
221
- setContext('svelte5-table', table)
222
-
223
- // * --- *
224
-
225
- // * --- Virtualization --- *
226
- // #region Virtualization
227
- let scrollTop = $state(0)
228
- let viewportHeight = $state(0)
229
-
230
- let heightPerItem = $state(8)
231
-
232
- const spacing = () => viewportHeight / 2
233
-
234
- let virtualTop = $derived.by(() => {
235
- let result = Math.max(scrollTop - spacing(), 0)
236
- result -= result % heightPerItem
237
- return result
238
- })
239
- let virtualBottom = $derived.by(() => {
240
- let result = heightPerItem * data.length - virtualTop - spacing() * 4
241
- result = Math.max(result, 0)
242
- return result
243
- })
244
-
245
- let renderItemLength = $derived(Math.ceil(Math.max(30, (viewportHeight / heightPerItem) * 2)))
246
-
247
- /** The area of data being rendered */
248
- let area = $derived.by(() => {
249
- table.sortReverse
250
- table.sortby
251
- const index = virtualTop / heightPerItem || 0
252
- const end = index + renderItemLength
253
- const result = data.slice(index, end)
254
- return result
255
- })
256
-
257
- function calculateHeightPerItem() {
258
- if (!elements.rows) {
259
- heightPerItem = 8
260
- return
261
- }
262
- tick().then(() => {
263
- const firstRow = elements.rows.children[0].getBoundingClientRect().top
264
- const lastRow =
265
- elements.rows.children[elements.rows.children.length - 1].getBoundingClientRect().bottom
266
- heightPerItem = (lastRow - firstRow) / area.length
267
- })
268
- }
269
-
270
- $effect(() => {
271
- data
272
- untrack(calculateHeightPerItem)
273
- })
274
- // #endregion
275
- // * --- Virtualization --- *
276
-
277
-
278
-
279
- function sortBy(column: string) {
280
- const { sorting, value } = table.columns[column]!.options
281
- if(!sorting || !value) return
282
-
283
- if (table.sortby === column) {
284
- table.sortReverse = !table.sortReverse
285
- }
286
- else {
287
- table.sortReverse = false
288
- table.sortby = column
289
- }
290
- }
291
- function sortAction(node: HTMLElement, column: string) {
292
- $effect(() => on(node, 'click', () => sortBy(column)))
293
- }
294
-
295
- function sortTable() {
296
- if (!table.sortby) return
297
- const column = table.columns[table.sortby]
298
- let { sorting, value } = column.options
299
- if(!sorting || !value) return
300
- if(sorting === true) {
301
- sorting = (a, b) => String(a).localeCompare(String(b))
302
- }
303
- if(table.sortReverse) {
304
- data.sort((a, b) => sorting(value(b), value(a)))
305
- } else {
306
- data.sort((a, b) => sorting(value(a), value(b)))
307
- }
308
- }
309
-
310
- $effect.pre(() => {
311
- data
312
- table.sortby
313
- table.sortReverse
314
- untrack(sortTable)
315
- })
316
-
317
- const panelTween = new PanelTween(() => panel, 24)
318
-
319
- let hoveredRow: T | null = $state(null)
320
-
321
- /** Order of columns */
322
- const fixed = $derived(
323
- // @ts-expect-error
324
- positions.fixed
325
- ) as string[]
326
- const hidden = $derived(positions.hidden)
327
- const notHidden = (key: string) => !positions.hidden.includes(key)
328
- const sticky = $derived(positions.sticky.filter(notHidden))
329
- const scrolled = $derived(positions.scroll.filter(notHidden))
330
- const columns = $derived([...fixed, ...sticky, ...scrolled])
331
-
332
- /** Width of each column */
333
- const columnWidths = $state({}) as Record<string, number>
334
-
335
- const getWidth = (key: string, def: number = 150) =>
336
- columnWidths[key] || table.columns[key]?.defaults.width || def
337
-
338
- /** grid-template-columns for widths */
339
- const style = $derived.by(() => {
340
- if (!mounted) return ''
341
- const templateColumns = `
342
- #${id} > .headers,
343
- #${id} > tbody > .row,
344
- #${id} > tfoot > tr,
345
- #${id} > .content > .virtual.bottom {
346
- grid-template-columns: ${columns
347
- .map((key, i, arr) => {
348
- const width = getWidth(key)
349
- if (i === arr.length - 1) return `minmax(${width}px, 1fr)`
350
- return `${width}px`
351
- })
352
- .join(' ')};
353
- }
354
- `
355
-
356
- let sum = 0
357
- const stickyLeft = [...fixed, ...sticky]
358
- .map((key, i, arr) => {
359
- sum += getWidth(arr[i - 1], i === 0 ? 0 : undefined)
360
- return `
361
- #${id} .column.sticky[data-column='${key}'] {
362
- left: ${sum}px;
363
- }
364
- `
365
- })
366
- .join('')
367
-
368
- return templateColumns + stickyLeft
369
- })
370
-
371
- function observeColumnWidth(node: HTMLDivElement, isHeader = false) {
372
- if (!isHeader) return
373
-
374
- const key = node.getAttribute('data-column')!
375
- node.style.width = getWidth(key) + 'px'
376
-
377
- const observer = new MutationObserver(() => {
378
- columnWidths[key] = parseFloat(node.style.width)
379
- })
380
-
381
- observer.observe(node, { attributes: true })
382
- return { destroy: () => observer.disconnect() }
383
- }
384
-
385
- async function onscroll() {
386
- const target = elements.rows
387
- if (target.scrollTop !== scrollTop) {
388
- scrollTop = target?.scrollTop ?? scrollTop
389
- }
390
-
391
- if (elements.selects) {
392
- elements.selects.scrollTop = target?.scrollTop
393
- }
394
-
395
- if (!elements.headers) return
396
- elements.headers.scrollLeft = target.scrollLeft
397
- elements.statusbar.scrollLeft = target.scrollLeft
398
- }
399
-
400
- export { selected, positions, data, href, cols as columns }
401
- </script>
402
-
403
- <!---------------------------------------------------->
404
-
405
- <svelte:head>
406
- {@html `<style>${style}</style>`}
407
- </svelte:head>
408
-
409
- {#snippet chevronSnippet(reversed: boolean)}
410
- <svg
411
- class='sorting-icon'
412
- class:reversed
413
- xmlns="http://www.w3.org/2000/svg"
414
- width="16"
415
- height="16"
416
- viewBox="0 0 16 16"
417
- style='margin: auto; margin-right: var(--tably-padding-x, 1rem);'
418
- >
419
- <path
420
- fill="currentColor"
421
- d="M3.2 5.74a.75.75 0 0 1 1.06-.04L8 9.227L11.74 5.7a.75.75 0 1 1 1.02 1.1l-4.25 4a.75.75 0 0 1-1.02 0l-4.25-4a.75.75 0 0 1-.04-1.06"
422
- />
423
- </svg>
424
- {/snippet}
425
-
426
- {#snippet columnsSnippet(
427
- renderable: (column: string) => Snippet<[arg0?: any, arg1?: any]> | undefined,
428
- arg: null | ((column: string) => any[]) = null,
429
- isHeader = false
430
- )}
431
- {#each fixed as column, i (column)}
432
- {#if !hidden.includes(column)}
433
- {@const args = arg ? arg(column) : []}
434
- {@const sortable = isHeader && table.columns[column]!.options.sorting}
435
- {@const sortClick = isHeader ? sortAction : ()=>{}}
436
- <svelte:element
437
- this={isHeader ? 'th' : 'td'}
438
- class="column sticky fixed"
439
- data-column={column}
440
- class:header={isHeader}
441
- class:sortable={sortable}
442
- use:sortClick={column}
443
- >
444
- {@render renderable(column)?.(args[0], args[1])}
445
- {#if isHeader && table.sortby === column && sortable}
446
- {@render chevronSnippet(table.sortReverse)}
447
- {/if}
448
- </svelte:element>
449
- {/if}
450
- {/each}
451
- {#each sticky as column, i (column)}
452
- {#if !hidden.includes(column)}
453
- {@const args = arg ? arg(column) : []}
454
- {@const sortable = isHeader && table.columns[column]!.options.sorting}
455
- {@const sortClick = isHeader ? sortAction : ()=>{}}
456
- <svelte:element
457
- this={isHeader ? 'th' : 'td'}
458
- class="column sticky"
459
- use:observeColumnWidth={isHeader}
460
- data-column={column}
461
- class:header={isHeader}
462
- class:resizeable={isHeader && table.columns[column].options.resizeable && table.resizeable}
463
- class:border={i == sticky.length - 1}
464
- class:sortable={sortable}
465
- use:sortClick={column}
466
- >
467
- {@render renderable(column)?.(args[0], args[1])}
468
- {#if isHeader && table.sortby === column && sortable}
469
- {@render chevronSnippet(table.sortReverse)}
470
- {/if}
471
- </svelte:element>
472
- {/if}
473
- {/each}
474
- {#each scrolled as column, i (column)}
475
- {#if !hidden.includes(column)}
476
- {@const args = arg ? arg(column) : []}
477
- {@const sortable = isHeader && table.columns[column]!.options.sorting}
478
- {@const sortClick = isHeader ? sortAction : ()=>{}}
479
- <svelte:element
480
- this={isHeader ? 'th' : 'td'}
481
- class="column"
482
- data-column={column}
483
- use:observeColumnWidth={isHeader}
484
- class:resizeable={isHeader && table.columns[column].options.resizeable && table.resizeable}
485
- class:sortable={sortable}
486
- use:sortClick={column}
487
- >
488
- {@render renderable(column)?.(args[0], args[1])}
489
- {#if isHeader && table.sortby === column && sortable}
490
- {@render chevronSnippet(table.sortReverse)}
491
- {/if}
492
- </svelte:element>
493
- {/if}
494
- {/each}
495
- {/snippet}
496
-
497
- <table
498
- {id}
499
- class="table svelte-tably"
500
- style="--t: {virtualTop}px; --b: {virtualBottom}px;"
501
- aria-rowcount={data.length}
502
- >
503
- <thead class="headers" bind:this={elements.headers}>
504
- {@render columnsSnippet(
505
- (column) => table.columns[column]?.header,
506
- () => [true],
507
- true
508
- )}
509
- </thead>
510
-
511
- <tbody class="content" bind:this={elements.rows} onscrollcapture={onscroll} bind:clientHeight={viewportHeight}>
512
- {#each area as item, i (item)}
513
- {@const props = table.href ? { href: table.href(item) } : {}}
514
- {@const index = data.indexOf(item) + 1}
515
- <svelte:element
516
- this={table.href ? 'a' : 'tr'}
517
- class="row"
518
- class:hover={hoveredRow === item}
519
- class:selected={table.selected?.includes(item)}
520
- class:first={i === 0}
521
- class:last={i === area.length - 1}
522
- {...props}
523
- aria-rowindex={index}
524
- onpointerenter={() => (hoveredRow = item)}
525
- onpointerleave={() => (hoveredRow = null)}
526
- >
527
- {@render columnsSnippet(
528
- (column) => table.columns[column]!.row,
529
- (column) => {
530
- const col = table.columns[column]!
531
- return [
532
- item,
533
- {
534
- get index() {
535
- return index - 1
536
- },
537
- get value() {
538
- return col.options.value ? col.options.value(item) : undefined
539
- },
540
- get isHovered() {
541
- return hoveredRow === item
542
- },
543
- get selected() {
544
- return table.selected?.includes(item)
545
- },
546
- set selected(value) {
547
- value ?
548
- table.selected!.push(item)
549
- : table.selected!.splice(table.selected!.indexOf(item), 1)
550
- }
551
- }
552
- ]
553
- }
554
- )}
555
- </svelte:element>
556
- {/each}
557
- </tbody>
558
-
559
- <tfoot class="statusbar" bind:this={elements.statusbar}>
560
- <tr>
561
- {@render columnsSnippet((column) => table.columns[column]?.statusbar)}
562
- </tr>
563
- </tfoot>
564
-
565
- <caption
566
- class="panel"
567
- style="width: {panelTween.current}px;"
568
- style:overflow={panelTween.transitioning ? 'hidden' : 'auto'}
569
- >
570
- {#if panel && panel in table.panels}
571
- <div
572
- class="panel-content"
573
- bind:clientWidth={panelTween.width}
574
- in:fly={{ x: 100, easing: sineInOut, duration: 300 }}
575
- out:fly={{ x: 100, duration: 200, easing: sineInOut }}
576
- >
577
- {@render table.panels[panel].content({
578
- get table() {
579
- return table
580
- },
581
- get data() {
582
- return data
583
- }
584
- })}
585
- </div>
586
- {/if}
587
- </caption>
588
- <caption class="backdrop" aria-hidden={panel && table.panels[panel]?.backdrop ? false : true}>
589
- <button aria-label="Panel backdrop" tabindex="-1" onclick={() => (panel = undefined)}></button>
590
- </caption>
591
- </table>
592
-
593
- {#snippet headerSelected(ctx: HeaderSelectCtx<T>)}
594
- <input type="checkbox" indeterminate={ctx.indeterminate} bind:checked={ctx.isSelected} />
595
- {/snippet}
596
-
597
- {#snippet rowSelected(ctx: RowSelectCtx<T>)}
598
- <input type="checkbox" bind:checked={ctx.isSelected} />
599
- {/snippet}
600
-
601
- {#if select}
602
- {@const {
603
- show = 'hover',
604
- style = 'column',
605
- rowSnippet = rowSelected,
606
- headerSnippet = headerSelected
607
- } = typeof select === 'boolean' ? {} : select}
608
- {#if show !== 'never'}
609
- <Column id="__fixed" {table} fixed width={56} resizeable={false}>
610
- {#snippet header()}
611
- <div class="__fixed">
612
- {@render headerSnippet({
613
- get isSelected() {
614
- return table.data.length === table.selected?.length
615
- },
616
- set isSelected(value) {
617
- if (value) {
618
- table.selected = table.data
619
- } else {
620
- table.selected = []
621
- }
622
- },
623
- get selected() {
624
- return table.selected!
625
- },
626
- get indeterminate() {
627
- return (table.selected?.length || 0) > 0 && table.data.length !== table.selected?.length
628
- }
629
- })}
630
- </div>
631
- {/snippet}
632
- {#snippet row(item, row)}
633
- <div class="__fixed">
634
- {#if row.selected || show === 'always' || (row.isHovered && show === 'hover')}
635
- {@render rowSnippet({
636
- get isSelected() {
637
- return row.selected
638
- },
639
- set isSelected(value) {
640
- row.selected = value
641
- },
642
- get row() {
643
- return row
644
- },
645
- get item() {
646
- return item
647
- },
648
- get data() {
649
- return table.data
650
- }
651
- })}
652
- {/if}
653
- </div>
654
- {/snippet}
655
- </Column>
656
- {/if}
657
- {/if}
658
-
659
- {@render content?.({
660
- Column,
661
- Panel,
662
- get table() {
663
- return table
664
- },
665
- get data() {
666
- return data
667
- }
668
- })}
669
-
670
- <!---------------------------------------------------->
671
- <style>
672
- .svelte-tably *,
673
- .svelte-tably {
674
- box-sizing: border-box;
675
- background-color: inherit;
676
- }
677
-
678
- .svelte-tably {
679
- position: relative;
680
- overflow: visible;
681
- }
682
-
683
- input[type='checkbox'] {
684
- width: 18px;
685
- height: 18px;
686
- cursor: pointer;
687
- }
688
-
689
- .sorting-icon {
690
- transition: transform .15s ease;
691
- transform: rotateZ(0deg);
692
- &.reversed {
693
- transform: rotateZ(-180deg);
694
- }
695
- }
696
-
697
- .__fixed {
698
- display: flex;
699
- align-items: center;
700
- justify-content: center;
701
- gap: 0.5rem;
702
- position: absolute;
703
- top: 0;
704
- left: 0;
705
- right: 0;
706
- bottom: 0;
707
- width: 100%;
708
- }
709
-
710
- .first .__fixed {
711
- top: var(--tably-padding-y, 0.5rem);
712
- }
713
- .last .__fixed {
714
- bottom: var(--tably-padding-y, 0.5rem);
715
- }
716
-
717
- tbody::before,
718
- tbody::after,
719
- selects::before,
720
- selects::after {
721
- content: '';
722
- display: grid;
723
- min-height: 100%;
724
- }
725
-
726
- tbody::before,
727
- selects::before {
728
- height: var(--t);
729
- }
730
- tbody::after,
731
- selects::after {
732
- height: var(--b);
733
- }
734
-
735
- a.row {
736
- color: inherit;
737
- text-decoration: inherit;
738
- }
739
-
740
- .backdrop {
741
- position: absolute;
742
- left: 0px;
743
- top: 0px;
744
- bottom: 0px;
745
- right: 0px;
746
- background-color: hsla(0, 0%, 0%, 0.3);
747
- z-index: 3;
748
- opacity: 1;
749
- transition: 0.15s ease;
750
- border: none;
751
- outline: none;
752
- cursor: pointer;
753
-
754
- > button {
755
- position: absolute;
756
- left: 0px;
757
- top: 0px;
758
- bottom: 0px;
759
- right: 0px;
760
- }
761
-
762
- &[aria-hidden='true'] {
763
- opacity: 0;
764
- pointer-events: none;
765
- }
766
- }
767
-
768
- .headers,
769
- .statusbar {
770
- /* So that the scrollbar doesn't cause the headers/statusbar to shift */
771
- padding-right: 11px;
772
- }
773
-
774
- .table {
775
- color: var(--tably-color, hsl(0, 0%, 0%));
776
- background-color: var(--tably-bg, hsl(0, 0%, 100%));
777
- }
778
-
779
- .sticky {
780
- position: sticky;
781
- /* right: 100px; */
782
- z-index: 1;
783
- }
784
-
785
- .sticky.border {
786
- border-right: 1px solid var(--tably-border, hsl(0, 0%, 90%));
787
- }
788
-
789
- .headers > .column {
790
- border-right: 1px solid var(--tably-border, hsl(0, 0%, 90%));
791
- overflow: hidden;
792
- padding: var(--tably-padding-y, 0.5rem) 0;
793
- cursor: default;
794
- user-select: none;
795
-
796
- &.sortable {
797
- cursor: pointer;
798
- }
799
-
800
- &.resizeable {
801
- resize: horizontal;
802
- }
803
- }
804
-
805
- .table {
806
- display: grid;
807
- height: 100%;
808
- position: relative;
809
-
810
- grid-template-areas:
811
- 'headers panel'
812
- 'rows panel'
813
- 'statusbar panel';
814
-
815
- grid-template-columns: auto min-content;
816
- grid-template-rows: auto 1fr auto;
817
-
818
- border: 1px solid var(--tably-border, hsl(0, 0%, 90%));
819
- border-radius: var(--tably-radius, 0.25rem);
820
-
821
- max-height: 100%;
822
- }
823
-
824
- .headers {
825
- grid-area: headers;
826
- z-index: 2;
827
- overflow: hidden;
828
- }
829
-
830
- .headers > .column {
831
- width: auto !important;
832
- border-bottom: 1px solid var(--tably-border, hsl(0, 0%, 90%));
833
- }
834
-
835
- .content {
836
- display: grid;
837
- grid-auto-rows: max-content;
838
-
839
- grid-area: rows;
840
- scrollbar-width: thin;
841
- overflow: auto;
842
- /* height: 100%; */
843
- }
844
-
845
- .statusbar {
846
- grid-area: statusbar;
847
- overflow: hidden;
848
- background-color: var(--tably-statusbar, hsl(0, 0%, 98%));
849
- }
850
-
851
- .statusbar > tr > .column {
852
- border-top: 1px solid var(--tably-border, hsl(0, 0%, 90%));
853
- padding: calc(var(--tably-padding-y, 0.5rem) / 2) 0;
854
- }
855
-
856
- .headers,
857
- .row,
858
- .statusbar > tr {
859
- position: relative;
860
- display: grid;
861
- width: 100%;
862
- height: 100%;
863
-
864
- & > .column {
865
- display: flex;
866
- padding-left: var(--tably-padding-x, 1rem);
867
- overflow: hidden;
868
- }
869
-
870
- & > *:last-child {
871
- width: 100%;
872
- padding-right: var(--tably-padding-x, 1rem);
873
- }
874
- }
875
-
876
- .row:first-child > * {
877
- padding-top: calc(var(--tably-padding-y, 0.5rem) + calc(var(--tably-padding-y, 0.5rem) / 2));
878
- }
879
- .row:last-child > * {
880
- padding-bottom: calc(var(--tably-padding-y, 0.5rem) + calc(var(--tably-padding-y, 0.5rem) / 2));
881
- }
882
-
883
- .row > * {
884
- padding: calc(var(--tably-padding-y, 0.5rem) / 2) 0;
885
- }
886
-
887
- .panel {
888
- position: relative;
889
- grid-area: panel;
890
- height: 100%;
891
-
892
- border-left: 1px solid var(--tably-border, hsl(0, 0%, 90%));
893
- scrollbar-gutter: stable both-edges;
894
- scrollbar-width: thin;
895
- z-index: 4;
896
-
897
- > .panel-content {
898
- position: absolute;
899
- top: 0;
900
- right: 0;
901
- width: min-content;
902
- overflow: auto;
903
- padding: var(--tably-padding-y, 0.5rem) 0;
904
- }
905
- }
906
- </style>