svelte-tably 1.0.0-next.11 → 1.0.0-next.12

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