svelte-tably 1.1.1 → 1.2.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/README.md +8 -1
- package/dist/column/Column.svelte +1 -1
- package/dist/column/Column.svelte.d.ts +5 -5
- package/dist/column/column-state.svelte.d.ts +2 -1
- package/dist/column/column-state.svelte.js +2 -1
- package/dist/expandable/Expandable.svelte +1 -2
- package/dist/expandable/Expandable.svelte.d.ts +4 -5
- package/dist/index.d.ts +4 -0
- package/dist/panel/Panel.svelte +1 -1
- package/dist/panel/Panel.svelte.d.ts +4 -4
- package/dist/row/Row.svelte +1 -2
- package/dist/row/Row.svelte.d.ts +4 -5
- package/dist/row/row-state.svelte.d.ts +4 -1
- package/dist/size-tween.svelte.js +9 -1
- package/dist/table/Table.svelte +344 -116
- package/dist/table/Table.svelte.d.ts +24 -95
- package/dist/table/data.svelte.js +21 -8
- package/dist/table/table-state.svelte.d.ts +1 -1
- package/dist/table/virtualization.svelte.d.ts +2 -2
- package/dist/table/virtualization.svelte.js +67 -23
- package/dist/utility.svelte.d.ts +1 -1
- package/dist/utility.svelte.js +2 -2
- package/package.json +1 -1
package/dist/table/Table.svelte
CHANGED
|
@@ -8,12 +8,8 @@
|
|
|
8
8
|
|
|
9
9
|
-->
|
|
10
10
|
|
|
11
|
-
<script lang="ts">
|
|
12
|
-
import {
|
|
13
|
-
import { fly } from 'svelte/transition'
|
|
14
|
-
import { sineInOut } from 'svelte/easing'
|
|
15
|
-
import reorder, { type ItemState } from 'runic-reorder'
|
|
16
|
-
import { Virtualization } from './virtualization.svelte.js'
|
|
11
|
+
<script module lang="ts">
|
|
12
|
+
import type { Snippet } from 'svelte'
|
|
17
13
|
import {
|
|
18
14
|
TableState,
|
|
19
15
|
type HeaderSelectCtx,
|
|
@@ -21,36 +17,43 @@
|
|
|
21
17
|
type RowSelectCtx,
|
|
22
18
|
type TableProps
|
|
23
19
|
} from './table-state.svelte.js'
|
|
24
|
-
import Panel from '../panel/Panel.svelte'
|
|
25
20
|
import Column from '../column/Column.svelte'
|
|
26
|
-
import
|
|
27
|
-
import { conditional } from '../conditional.svelte.js'
|
|
28
|
-
import { ColumnState, type RowColumnCtx } from '../column/column-state.svelte.js'
|
|
21
|
+
import Panel from '../panel/Panel.svelte'
|
|
29
22
|
import Expandable from '../expandable/Expandable.svelte'
|
|
30
|
-
import { SizeTween } from '../size-tween.svelte.js'
|
|
31
|
-
import { on } from 'svelte/events'
|
|
32
23
|
import Row from '../row/Row.svelte'
|
|
33
|
-
import type { CSVOptions } from './csv.js'
|
|
34
24
|
|
|
35
|
-
type
|
|
25
|
+
type ConstructorReturnType<C extends new (...args: any[]) => any> =
|
|
26
|
+
C extends new (...args: any[]) => infer K ? K : never
|
|
27
|
+
type ConstructorParams<C extends new (...args: any[]) => any> =
|
|
28
|
+
C extends new (...args: infer K) => any ? K : never
|
|
36
29
|
|
|
37
|
-
type
|
|
38
|
-
T extends new (...args: any[]) => infer K ? K : never
|
|
39
|
-
type ConstructorParams<T extends new (...args: any[]) => any> =
|
|
40
|
-
T extends new (...args: infer K) => any ? K : never
|
|
41
|
-
|
|
42
|
-
type ContentCtx<T extends Record<PropertyKey, unknown>> = {
|
|
30
|
+
export type ContentCtx<Item = any> = {
|
|
43
31
|
Column: {
|
|
44
|
-
new <V>(...args: ConstructorParams<typeof Column<
|
|
45
|
-
<V>(...args: Parameters<typeof Column<
|
|
32
|
+
new <V>(...args: ConstructorParams<typeof Column<Item, V>>): ConstructorReturnType<typeof Column<Item, V>>
|
|
33
|
+
<V>(...args: Parameters<typeof Column<Item, V>>): ReturnType<typeof Column<Item, V>>
|
|
46
34
|
}
|
|
47
|
-
Panel: typeof Panel<
|
|
48
|
-
Expandable: typeof Expandable<
|
|
49
|
-
Row: typeof Row<
|
|
50
|
-
readonly table: TableState<
|
|
35
|
+
Panel: typeof Panel<Item>
|
|
36
|
+
Expandable: typeof Expandable<Item>
|
|
37
|
+
Row: typeof Row<Item>
|
|
38
|
+
readonly table: TableState<Item>
|
|
51
39
|
}
|
|
52
40
|
|
|
53
|
-
type ContentSnippet = Snippet<[context: ContentCtx<
|
|
41
|
+
export type ContentSnippet<Item = any> = Snippet<[context: ContentCtx<Item>]>
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<script lang="ts">
|
|
45
|
+
import { fly } from 'svelte/transition'
|
|
46
|
+
import { sineInOut } from 'svelte/easing'
|
|
47
|
+
import reorder, { type ItemState } from 'runic-reorder'
|
|
48
|
+
import { Virtualization } from './virtualization.svelte.js'
|
|
49
|
+
import { assignDescriptors, capitalize, fromProps, mounted, segmentize } from '../utility.svelte.js'
|
|
50
|
+
import { conditional } from '../conditional.svelte.js'
|
|
51
|
+
import { ColumnState, type RowColumnCtx } from '../column/column-state.svelte.js'
|
|
52
|
+
import { SizeTween } from '../size-tween.svelte.js'
|
|
53
|
+
import { on } from 'svelte/events'
|
|
54
|
+
import type { CSVOptions } from './csv.js'
|
|
55
|
+
|
|
56
|
+
type T = $$Generic
|
|
54
57
|
|
|
55
58
|
let {
|
|
56
59
|
content,
|
|
@@ -59,7 +62,7 @@
|
|
|
59
62
|
data: _data = $bindable([]),
|
|
60
63
|
table: _table = $bindable(),
|
|
61
64
|
...restProps
|
|
62
|
-
}: TableProps<T> & { content?: ContentSnippet } = $props()
|
|
65
|
+
}: TableProps<T> & { content?: ContentSnippet<T> } = $props()
|
|
63
66
|
|
|
64
67
|
const properties = fromProps(restProps, {
|
|
65
68
|
selected: [() => _selected, (v) => (_selected = v)],
|
|
@@ -69,14 +72,39 @@
|
|
|
69
72
|
|
|
70
73
|
const mount = mounted()
|
|
71
74
|
|
|
75
|
+
|
|
76
|
+
const getRowLabel = (item: T, index: number) => {
|
|
77
|
+
const labelColumn = columns.find((c) => c.id !== '__fixed')
|
|
78
|
+
const raw = labelColumn?.options.value?.(item)
|
|
79
|
+
|
|
80
|
+
if (raw === null || raw === undefined) return `Row ${index + 1}`
|
|
81
|
+
if (typeof raw === 'string' || typeof raw === 'number' || typeof raw === 'boolean') {
|
|
82
|
+
const text = String(raw).trim()
|
|
83
|
+
return text ? text : `Row ${index + 1}`
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return `Row ${index + 1}`
|
|
87
|
+
}
|
|
88
|
+
|
|
72
89
|
const reorderArea = reorder(rowSnippet)
|
|
73
90
|
|
|
74
|
-
const elements = $state({}) as Record<
|
|
75
|
-
'headers' | 'statusbar' | 'rows' | 'virtualTop' | 'virtualBottom' | 'selects',
|
|
76
|
-
HTMLElement
|
|
77
|
-
>
|
|
91
|
+
const elements = $state({}) as Record<'headers' | 'statusbar', HTMLElement>
|
|
78
92
|
|
|
79
93
|
const table = new TableState<T>(properties) as TableState<T>
|
|
94
|
+
const uid = table.cssId
|
|
95
|
+
let expandIdCounter = 0
|
|
96
|
+
const expandIds = new WeakMap<object, string>()
|
|
97
|
+
const getExpandId = (item: T) => {
|
|
98
|
+
if (item && typeof item === 'object') {
|
|
99
|
+
let id = expandIds.get(item)
|
|
100
|
+
if (!id) {
|
|
101
|
+
id = `${uid}-expand-${++expandIdCounter}`
|
|
102
|
+
expandIds.set(item, id)
|
|
103
|
+
}
|
|
104
|
+
return id
|
|
105
|
+
}
|
|
106
|
+
return `${uid}-expand-${String(item)}`
|
|
107
|
+
}
|
|
80
108
|
|
|
81
109
|
const virtualization = new Virtualization(table)
|
|
82
110
|
|
|
@@ -86,13 +114,38 @@
|
|
|
86
114
|
let hoveredColumn: ColumnState | null = $state(null)
|
|
87
115
|
|
|
88
116
|
/** Order of columns */
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
const
|
|
117
|
+
const isColumn = (value: unknown): value is ColumnState =>
|
|
118
|
+
value instanceof ColumnState
|
|
119
|
+
const fixed = $derived(table.positions.fixed.filter(isColumn))
|
|
120
|
+
const hidden = $derived(table.positions.hidden.filter(isColumn))
|
|
121
|
+
const notHidden = (column: ColumnState | undefined) =>
|
|
122
|
+
!!column && !table.positions.hidden.includes(column)
|
|
92
123
|
const sticky = $derived(table.positions.sticky.filter(notHidden))
|
|
93
124
|
const scrolled = $derived(table.positions.scroll.filter(notHidden))
|
|
94
125
|
const columns = $derived([...fixed, ...sticky, ...scrolled])
|
|
95
126
|
|
|
127
|
+
const autoSchema = $derived.by(() => {
|
|
128
|
+
const rows = table.dataState.origin as any[]
|
|
129
|
+
const keys = [] as string[]
|
|
130
|
+
const seen = new Set<string>()
|
|
131
|
+
const sample = {} as Record<string, unknown>
|
|
132
|
+
|
|
133
|
+
for (const row of rows.slice(0, 50)) {
|
|
134
|
+
if (!row || typeof row !== 'object') continue
|
|
135
|
+
for (const key of Object.keys(row)) {
|
|
136
|
+
if (!seen.has(key)) {
|
|
137
|
+
seen.add(key)
|
|
138
|
+
keys.push(key)
|
|
139
|
+
}
|
|
140
|
+
if (sample[key] === undefined && row[key] !== undefined) {
|
|
141
|
+
sample[key] = row[key]
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return { keys, sample }
|
|
147
|
+
})
|
|
148
|
+
|
|
96
149
|
const getWidth = (key: string, def: number = 150) =>
|
|
97
150
|
table.columnWidths[key] ??= table.columns[key]?.defaults.width ?? def
|
|
98
151
|
|
|
@@ -104,7 +157,7 @@
|
|
|
104
157
|
return
|
|
105
158
|
}
|
|
106
159
|
|
|
107
|
-
const context = table.row?.snippets.context ? table.row?.options.context.width : ''
|
|
160
|
+
const context = table.row?.snippets.context ? ` ${table.row?.options.context.width}` : ''
|
|
108
161
|
|
|
109
162
|
const templateColumns =
|
|
110
163
|
columns
|
|
@@ -115,7 +168,7 @@
|
|
|
115
168
|
})
|
|
116
169
|
.join(' ') + context
|
|
117
170
|
|
|
118
|
-
const
|
|
171
|
+
const theadTemplateColumns = `
|
|
119
172
|
[data-svelte-tably="${table.cssId}"] > thead > tr,
|
|
120
173
|
[data-svelte-tably="${table.cssId}"] > tfoot > tr {
|
|
121
174
|
grid-template-columns: ${templateColumns};
|
|
@@ -124,6 +177,7 @@
|
|
|
124
177
|
|
|
125
178
|
const tbodyTemplateColumns = `
|
|
126
179
|
[data-area-class='${table.cssId}'] tr.row,
|
|
180
|
+
[data-area-class='${table.cssId}'] tr.filler,
|
|
127
181
|
[data-svelte-tably="${table.cssId}"] > tbody::after {
|
|
128
182
|
grid-template-columns: ${templateColumns};
|
|
129
183
|
}
|
|
@@ -154,7 +208,7 @@
|
|
|
154
208
|
)
|
|
155
209
|
.join('')
|
|
156
210
|
|
|
157
|
-
style =
|
|
211
|
+
style = theadTemplateColumns + tbodyTemplateColumns + stickyLeft + columnStyling
|
|
158
212
|
})
|
|
159
213
|
|
|
160
214
|
function observeColumnWidth(node: HTMLDivElement, isHeader = false) {
|
|
@@ -188,21 +242,41 @@
|
|
|
188
242
|
}
|
|
189
243
|
|
|
190
244
|
let tbody = $state({
|
|
191
|
-
|
|
245
|
+
scrollbar: 0
|
|
192
246
|
})
|
|
193
|
-
|
|
194
|
-
|
|
247
|
+
|
|
248
|
+
function observeScrollbar(node: HTMLElement) {
|
|
249
|
+
if (typeof ResizeObserver === 'undefined') return
|
|
250
|
+
|
|
251
|
+
const update = () => {
|
|
252
|
+
// Reserve the same gutter in header/footer as the scrollable body
|
|
253
|
+
tbody.scrollbar = Math.max(0, node.offsetWidth - node.clientWidth)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
update()
|
|
257
|
+
const observer = new ResizeObserver(update)
|
|
258
|
+
observer.observe(node)
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
destroy() {
|
|
262
|
+
observer.disconnect()
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
function onscroll() {
|
|
267
|
+
const target = virtualization.viewport.element
|
|
268
|
+
if (!target) return
|
|
195
269
|
if (target.scrollTop !== virtualization.scrollTop) {
|
|
196
270
|
virtualization.scrollTop = target?.scrollTop ?? virtualization.scrollTop
|
|
197
271
|
}
|
|
198
272
|
|
|
199
|
-
if (elements.
|
|
200
|
-
elements.
|
|
273
|
+
if (elements.headers) {
|
|
274
|
+
elements.headers.scrollLeft = target.scrollLeft
|
|
201
275
|
}
|
|
202
276
|
|
|
203
|
-
if (
|
|
204
|
-
|
|
205
|
-
|
|
277
|
+
if (elements.statusbar) {
|
|
278
|
+
elements.statusbar.scrollLeft = target.scrollLeft
|
|
279
|
+
}
|
|
206
280
|
}
|
|
207
281
|
|
|
208
282
|
// * --- CSV --- *
|
|
@@ -411,7 +485,11 @@
|
|
|
411
485
|
class:fixed={true}
|
|
412
486
|
use:addRowColumnEvents={[where, column, () => args[1]]}
|
|
413
487
|
data-column={column.id}
|
|
414
|
-
class:pad={
|
|
488
|
+
class:pad={
|
|
489
|
+
(where === 'header' && column.options.padHeader) ||
|
|
490
|
+
(where === 'row' && column.options.padRow) ||
|
|
491
|
+
(where === 'statusbar' && column.options.padStatusbar)
|
|
492
|
+
}
|
|
415
493
|
class:header={isHeader}
|
|
416
494
|
class:sortable
|
|
417
495
|
use:conditional={[isHeader, (node) => table.dataState.sortAction(node, column.id)]}
|
|
@@ -439,7 +517,11 @@
|
|
|
439
517
|
use:addRowColumnEvents={[where, column, () => args[1]]}
|
|
440
518
|
use:observeColumnWidth={isHeader}
|
|
441
519
|
data-column={column.id}
|
|
442
|
-
class:pad={
|
|
520
|
+
class:pad={
|
|
521
|
+
(where === 'header' && column.options.padHeader) ||
|
|
522
|
+
(where === 'row' && column.options.padRow) ||
|
|
523
|
+
(where === 'statusbar' && column.options.padStatusbar)
|
|
524
|
+
}
|
|
443
525
|
class:header={isHeader}
|
|
444
526
|
class:resizeable={isHeader && column.options.resizeable && table.options.resizeable}
|
|
445
527
|
class:border={i == sticky.length - 1}
|
|
@@ -466,7 +548,11 @@
|
|
|
466
548
|
class={column.options.class ?? ''}
|
|
467
549
|
class:column={true}
|
|
468
550
|
data-column={column.id}
|
|
469
|
-
class:pad={
|
|
551
|
+
class:pad={
|
|
552
|
+
(where === 'header' && column.options.padHeader) ||
|
|
553
|
+
(where === 'row' && column.options.padRow) ||
|
|
554
|
+
(where === 'statusbar' && column.options.padStatusbar)
|
|
555
|
+
}
|
|
470
556
|
use:addRowColumnEvents={[where, column, () => args[1]]}
|
|
471
557
|
use:observeColumnWidth={isHeader}
|
|
472
558
|
class:resizeable={isHeader && column.options.resizeable && table.options.resizeable}
|
|
@@ -486,7 +572,7 @@
|
|
|
486
572
|
{/each}
|
|
487
573
|
{/snippet}
|
|
488
574
|
|
|
489
|
-
{#snippet defaultRow(item:
|
|
575
|
+
{#snippet defaultRow(item: any, ctx: RowColumnCtx<any, any>)}
|
|
490
576
|
{ctx.value}
|
|
491
577
|
{/snippet}
|
|
492
578
|
|
|
@@ -562,30 +648,46 @@
|
|
|
562
648
|
'row'
|
|
563
649
|
)}
|
|
564
650
|
{#if table.row?.snippets.context}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
hoveredRow !== item}
|
|
572
|
-
>
|
|
573
|
-
{@render table.row?.snippets.context?.(item, ctx)}
|
|
574
|
-
</td>
|
|
575
|
-
{/if}
|
|
651
|
+
<td
|
|
652
|
+
class="context-col"
|
|
653
|
+
class:hidden={table.row?.options.context.hover && hoveredRow !== item}
|
|
654
|
+
>
|
|
655
|
+
{@render table.row?.snippets.context?.(item, ctx)}
|
|
656
|
+
</td>
|
|
576
657
|
{/if}
|
|
577
658
|
</tr>
|
|
578
659
|
|
|
579
|
-
{@const expandableTween = new SizeTween(() => table.expandable && expandedRow.includes(item), {
|
|
580
|
-
min:
|
|
660
|
+
{@const expandableTween = new SizeTween(() => !!table.expandable && expandedRow.includes(item), {
|
|
661
|
+
min: 0,
|
|
581
662
|
duration: table.expandable?.options.slide.duration,
|
|
582
663
|
easing: table.expandable?.options.slide.easing
|
|
583
664
|
})}
|
|
584
|
-
{
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
665
|
+
{@const expanded = !!table.expandable && expandedRow.includes(item)}
|
|
666
|
+
{#if table.expandable && (expanded || expandableTween.current > 0 || expandableTween.transitioning)}
|
|
667
|
+
{@const expandId = getExpandId(item)}
|
|
668
|
+
{@const expandLabelId = `${expandId}-label`}
|
|
669
|
+
<tr class="expandable">
|
|
670
|
+
<td
|
|
671
|
+
colspan={columns.length + (table.row?.snippets.context ? 1 : 0)}
|
|
672
|
+
style="padding: 0"
|
|
673
|
+
>
|
|
674
|
+
<div
|
|
675
|
+
class="expandable-clip"
|
|
676
|
+
style="height: {Math.round(expandableTween.current)}px"
|
|
677
|
+
id={expandId}
|
|
678
|
+
role="region"
|
|
679
|
+
aria-labelledby={expandLabelId}
|
|
680
|
+
aria-hidden={!expanded}
|
|
681
|
+
>
|
|
682
|
+
<span class="sr-only" id={expandLabelId}>
|
|
683
|
+
Expanded content for {getRowLabel(item, index)}
|
|
684
|
+
</span>
|
|
685
|
+
<div
|
|
686
|
+
class="expandable-content"
|
|
687
|
+
bind:offsetHeight={expandableTween.size}
|
|
688
|
+
>
|
|
689
|
+
{@render table.expandable?.snippets.content?.(item, ctx)}
|
|
690
|
+
</div>
|
|
589
691
|
</div>
|
|
590
692
|
</td>
|
|
591
693
|
</tr>
|
|
@@ -596,7 +698,7 @@
|
|
|
596
698
|
id={table.id}
|
|
597
699
|
data-svelte-tably={table.cssId}
|
|
598
700
|
class="table svelte-tably"
|
|
599
|
-
style="--t: {virtualization.virtualTop}px; --b: {virtualization.virtualBottom}px;"
|
|
701
|
+
style="--t: {virtualization.virtualTop}px; --b: {virtualization.virtualBottom}px; --scrollbar: {tbody.scrollbar}px;"
|
|
600
702
|
aria-rowcount={table.data.length}
|
|
601
703
|
>
|
|
602
704
|
{#if columns.some((v) => v.snippets.header)}
|
|
@@ -616,23 +718,28 @@
|
|
|
616
718
|
],
|
|
617
719
|
'header'
|
|
618
720
|
)}
|
|
619
|
-
{#if table.row?.snippets.
|
|
620
|
-
<th
|
|
621
|
-
|
|
721
|
+
{#if table.row?.snippets.context}
|
|
722
|
+
<th
|
|
723
|
+
class="context-col"
|
|
724
|
+
aria-hidden={table.row?.snippets.contextHeader ? undefined : true}
|
|
725
|
+
role={table.row?.snippets.contextHeader ? undefined : 'presentation'}
|
|
726
|
+
>
|
|
727
|
+
{#if table.row?.snippets.contextHeader}
|
|
728
|
+
{@render table.row?.snippets.contextHeader()}
|
|
729
|
+
{/if}
|
|
622
730
|
</th>
|
|
623
731
|
{/if}
|
|
624
732
|
</tr>
|
|
625
|
-
<tr style="width:400px;background:none;pointer-events:none;"></tr>
|
|
626
733
|
</thead>
|
|
627
734
|
{/if}
|
|
628
735
|
|
|
629
736
|
<tbody
|
|
630
737
|
class="content"
|
|
631
738
|
use:reorderArea={{ axis: 'y', class: table.cssId }}
|
|
739
|
+
use:observeScrollbar
|
|
632
740
|
bind:this={virtualization.viewport.element}
|
|
633
741
|
onscrollcapture={onscroll}
|
|
634
742
|
bind:clientHeight={virtualization.viewport.height}
|
|
635
|
-
bind:clientWidth={tbody.width}
|
|
636
743
|
>
|
|
637
744
|
{#if table.options.reorderable}
|
|
638
745
|
{@render reorderArea({
|
|
@@ -651,6 +758,39 @@
|
|
|
651
758
|
{@render rowSnippet(item, { index: i + virtualization.topIndex } as ItemState)}
|
|
652
759
|
{/each}
|
|
653
760
|
{/if}
|
|
761
|
+
|
|
762
|
+
{#if columns.length > 0 && virtualization.virtualTop === 0 && virtualization.virtualBottom === 0}
|
|
763
|
+
<tr class="filler" aria-hidden="true">
|
|
764
|
+
{#each fixed as column (column)}
|
|
765
|
+
{#if !hidden.includes(column)}
|
|
766
|
+
<td
|
|
767
|
+
class={`column sticky fixed ${column.options.class ?? ''}`}
|
|
768
|
+
data-column={column.id}
|
|
769
|
+
></td>
|
|
770
|
+
{/if}
|
|
771
|
+
{/each}
|
|
772
|
+
{#each sticky as column, i (column)}
|
|
773
|
+
{#if !hidden.includes(column)}
|
|
774
|
+
<td
|
|
775
|
+
class={`column sticky ${column.options.class ?? ''}`}
|
|
776
|
+
class:border={i == sticky.length - 1}
|
|
777
|
+
data-column={column.id}
|
|
778
|
+
></td>
|
|
779
|
+
{/if}
|
|
780
|
+
{/each}
|
|
781
|
+
{#each scrolled as column (column)}
|
|
782
|
+
{#if !hidden.includes(column)}
|
|
783
|
+
<td
|
|
784
|
+
class={`column ${column.options.class ?? ''}`}
|
|
785
|
+
data-column={column.id}
|
|
786
|
+
></td>
|
|
787
|
+
{/if}
|
|
788
|
+
{/each}
|
|
789
|
+
{#if table.row?.snippets.context}
|
|
790
|
+
<td class="context-col" aria-hidden="true"></td>
|
|
791
|
+
{/if}
|
|
792
|
+
</tr>
|
|
793
|
+
{/if}
|
|
654
794
|
</tbody>
|
|
655
795
|
|
|
656
796
|
{#if columns.some((v) => v.snippets.statusbar)}
|
|
@@ -667,8 +807,10 @@
|
|
|
667
807
|
],
|
|
668
808
|
'statusbar'
|
|
669
809
|
)}
|
|
810
|
+
{#if table.row?.snippets.context}
|
|
811
|
+
<td class="context-col" aria-hidden="true"></td>
|
|
812
|
+
{/if}
|
|
670
813
|
</tr>
|
|
671
|
-
<tr style="width:400px;background:none;pointer-events:none;"></tr>
|
|
672
814
|
</tfoot>
|
|
673
815
|
{/if}
|
|
674
816
|
|
|
@@ -704,11 +846,11 @@
|
|
|
704
846
|
</caption>
|
|
705
847
|
</table>
|
|
706
848
|
|
|
707
|
-
{#snippet headerSelected(ctx: HeaderSelectCtx<
|
|
849
|
+
{#snippet headerSelected(ctx: HeaderSelectCtx<any>)}
|
|
708
850
|
<input type="checkbox" indeterminate={ctx.indeterminate} bind:checked={ctx.isSelected} />
|
|
709
851
|
{/snippet}
|
|
710
852
|
|
|
711
|
-
{#snippet rowSelected(ctx: RowSelectCtx<
|
|
853
|
+
{#snippet rowSelected(ctx: RowSelectCtx<any>)}
|
|
712
854
|
<input type="checkbox" bind:checked={ctx.isSelected} tabindex="-1" />
|
|
713
855
|
{/snippet}
|
|
714
856
|
|
|
@@ -794,9 +936,20 @@
|
|
|
794
936
|
})}
|
|
795
937
|
{/if}
|
|
796
938
|
{#if expandable && expandable?.options.chevron !== 'never'}
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
939
|
+
{@const expandId = getExpandId(item)}
|
|
940
|
+
{@const expanded = row.expanded}
|
|
941
|
+
{@const label = expanded ? 'Collapse row' : 'Expand row'}
|
|
942
|
+
<button
|
|
943
|
+
class="expand-row"
|
|
944
|
+
tabindex="-1"
|
|
945
|
+
type="button"
|
|
946
|
+
aria-label={label}
|
|
947
|
+
aria-expanded={expanded}
|
|
948
|
+
aria-controls={expandId}
|
|
949
|
+
onclick={() => (row.expanded = !row.expanded)}
|
|
950
|
+
>
|
|
951
|
+
{#if expanded || expandable.options.chevron === 'always' || (row.rowHovered && expandable.options.chevron === 'hover')}
|
|
952
|
+
{@render chevronSnippet(expanded ? 180 : 90)}
|
|
800
953
|
{/if}
|
|
801
954
|
</button>
|
|
802
955
|
{/if}
|
|
@@ -807,12 +960,12 @@
|
|
|
807
960
|
{/if}
|
|
808
961
|
|
|
809
962
|
{#if table.options.auto}
|
|
810
|
-
{#each
|
|
963
|
+
{#each autoSchema.keys as key}
|
|
811
964
|
<Column
|
|
812
965
|
id={key}
|
|
813
|
-
|
|
966
|
+
value={(r) => (r as any)?.[key]}
|
|
814
967
|
header={capitalize(segmentize(key))}
|
|
815
|
-
sort={typeof
|
|
968
|
+
sort={typeof autoSchema.sample?.[key] === 'number' ?
|
|
816
969
|
(a, b) => a - b
|
|
817
970
|
: (a, b) => String(a).localeCompare(String(b))}
|
|
818
971
|
/>
|
|
@@ -846,14 +999,9 @@
|
|
|
846
999
|
height: 100%;
|
|
847
1000
|
z-index: 3;
|
|
848
1001
|
padding: 0;
|
|
849
|
-
|
|
850
|
-
&.hover {
|
|
851
|
-
position: absolute;
|
|
852
|
-
}
|
|
853
1002
|
&.hidden {
|
|
854
1003
|
pointer-events: none;
|
|
855
1004
|
user-select: none;
|
|
856
|
-
border-left: none;
|
|
857
1005
|
background: none;
|
|
858
1006
|
> :global(*) {
|
|
859
1007
|
opacity: 0;
|
|
@@ -861,6 +1009,30 @@
|
|
|
861
1009
|
}
|
|
862
1010
|
}
|
|
863
1011
|
|
|
1012
|
+
.table::before {
|
|
1013
|
+
content: '';
|
|
1014
|
+
grid-area: headers;
|
|
1015
|
+
justify-self: end;
|
|
1016
|
+
align-self: stretch;
|
|
1017
|
+
width: var(--scrollbar, 0px);
|
|
1018
|
+
background-color: var(--tably-bg);
|
|
1019
|
+
pointer-events: none;
|
|
1020
|
+
position: relative;
|
|
1021
|
+
z-index: 4;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
.table::after {
|
|
1025
|
+
content: '';
|
|
1026
|
+
grid-area: statusbar;
|
|
1027
|
+
justify-self: end;
|
|
1028
|
+
align-self: stretch;
|
|
1029
|
+
width: var(--scrollbar, 0px);
|
|
1030
|
+
background-color: var(--tably-statusbar);
|
|
1031
|
+
pointer-events: none;
|
|
1032
|
+
position: relative;
|
|
1033
|
+
z-index: 4;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
864
1036
|
:global(:root) {
|
|
865
1037
|
--tably-color: hsl(0, 0%, 0%);
|
|
866
1038
|
--tably-bg: hsl(0, 0%, 100%);
|
|
@@ -877,24 +1049,32 @@
|
|
|
877
1049
|
|
|
878
1050
|
.svelte-tably {
|
|
879
1051
|
position: relative;
|
|
880
|
-
overflow:
|
|
1052
|
+
overflow: hidden;
|
|
1053
|
+
border-collapse: collapse;
|
|
1054
|
+
border-spacing: 0;
|
|
881
1055
|
}
|
|
882
1056
|
|
|
883
1057
|
.expandable {
|
|
884
|
-
position: relative;
|
|
885
|
-
|
|
886
1058
|
& > td {
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
> div {
|
|
890
|
-
position: absolute;
|
|
891
|
-
overflow: auto;
|
|
892
|
-
top: -1.5px;
|
|
893
|
-
left: 0;
|
|
894
|
-
}
|
|
1059
|
+
padding: 0;
|
|
1060
|
+
border: none;
|
|
895
1061
|
}
|
|
896
1062
|
}
|
|
897
1063
|
|
|
1064
|
+
.expandable-clip {
|
|
1065
|
+
overflow: hidden;
|
|
1066
|
+
width: 100%;
|
|
1067
|
+
background-color: var(--tably-bg);
|
|
1068
|
+
box-shadow: inset 0 -1px 0 var(--tably-border-grid);
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
.expandable-content {
|
|
1072
|
+
overflow: auto;
|
|
1073
|
+
width: 100%;
|
|
1074
|
+
background-color: var(--tably-bg);
|
|
1075
|
+
box-sizing: border-box;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
898
1078
|
.expand-row {
|
|
899
1079
|
display: flex;
|
|
900
1080
|
justify-content: center;
|
|
@@ -961,20 +1141,16 @@
|
|
|
961
1141
|
}
|
|
962
1142
|
|
|
963
1143
|
tbody::before,
|
|
964
|
-
tbody::after
|
|
965
|
-
selects::before,
|
|
966
|
-
selects::after {
|
|
1144
|
+
tbody::after {
|
|
967
1145
|
content: '';
|
|
968
|
-
display:
|
|
969
|
-
|
|
1146
|
+
display: block;
|
|
1147
|
+
flex: 0 0 auto;
|
|
970
1148
|
}
|
|
971
1149
|
|
|
972
|
-
tbody::before
|
|
973
|
-
selects::before {
|
|
1150
|
+
tbody::before {
|
|
974
1151
|
height: var(--t);
|
|
975
1152
|
}
|
|
976
|
-
tbody::after
|
|
977
|
-
selects::after {
|
|
1153
|
+
tbody::after {
|
|
978
1154
|
height: var(--b);
|
|
979
1155
|
}
|
|
980
1156
|
|
|
@@ -1042,7 +1218,10 @@
|
|
|
1042
1218
|
|
|
1043
1219
|
.table {
|
|
1044
1220
|
display: grid;
|
|
1045
|
-
|
|
1221
|
+
width: 100%;
|
|
1222
|
+
min-width: 0;
|
|
1223
|
+
min-height: 0;
|
|
1224
|
+
height: 100%;
|
|
1046
1225
|
max-height: 100%;
|
|
1047
1226
|
position: relative;
|
|
1048
1227
|
|
|
@@ -1054,8 +1233,8 @@
|
|
|
1054
1233
|
'rows panel'
|
|
1055
1234
|
'statusbar panel';
|
|
1056
1235
|
|
|
1057
|
-
grid-template-columns:
|
|
1058
|
-
grid-template-rows: auto 1fr auto;
|
|
1236
|
+
grid-template-columns: 1fr min-content;
|
|
1237
|
+
grid-template-rows: auto minmax(0, 1fr) auto;
|
|
1059
1238
|
|
|
1060
1239
|
border: 1px solid var(--tably-border);
|
|
1061
1240
|
border-radius: var(--tably-radius);
|
|
@@ -1066,6 +1245,8 @@
|
|
|
1066
1245
|
grid-area: headers;
|
|
1067
1246
|
z-index: 2;
|
|
1068
1247
|
overflow: hidden;
|
|
1248
|
+
min-width: 0;
|
|
1249
|
+
padding-right: var(--scrollbar, 0px);
|
|
1069
1250
|
}
|
|
1070
1251
|
|
|
1071
1252
|
.headers > tr > .column {
|
|
@@ -1078,13 +1259,30 @@
|
|
|
1078
1259
|
border-left: 1px solid var(--tably-border-grid);
|
|
1079
1260
|
}
|
|
1080
1261
|
|
|
1262
|
+
.headers > tr > .context-col {
|
|
1263
|
+
background-color: var(--tably-bg);
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1081
1266
|
.content {
|
|
1082
|
-
display:
|
|
1083
|
-
|
|
1267
|
+
display: flex;
|
|
1268
|
+
flex-direction: column;
|
|
1269
|
+
min-width: 0;
|
|
1270
|
+
min-height: 0;
|
|
1084
1271
|
|
|
1085
1272
|
grid-area: rows;
|
|
1086
1273
|
scrollbar-width: thin;
|
|
1087
|
-
overflow: auto;
|
|
1274
|
+
overflow-x: auto;
|
|
1275
|
+
overflow-y: scroll;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
.content > tr.row,
|
|
1279
|
+
.content > tr.expandable {
|
|
1280
|
+
flex: 0 0 auto;
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
.content > tr.filler {
|
|
1284
|
+
flex: 1 0 0px;
|
|
1285
|
+
min-height: 0;
|
|
1088
1286
|
}
|
|
1089
1287
|
|
|
1090
1288
|
.statusbar {
|
|
@@ -1092,19 +1290,30 @@
|
|
|
1092
1290
|
grid-area: statusbar;
|
|
1093
1291
|
overflow: hidden;
|
|
1094
1292
|
background-color: var(--tably-statusbar);
|
|
1293
|
+
min-width: 0;
|
|
1294
|
+
padding-right: var(--scrollbar, 0px);
|
|
1095
1295
|
}
|
|
1096
1296
|
|
|
1097
1297
|
.statusbar > tr > .column {
|
|
1098
1298
|
border-top: 1px solid var(--tably-border);
|
|
1099
1299
|
padding: calc(var(--tably-padding-y) / 2) 0;
|
|
1100
1300
|
}
|
|
1301
|
+
.statusbar > tr > .context-col {
|
|
1302
|
+
border-top: 1px solid var(--tably-border);
|
|
1303
|
+
border-left: 1px solid var(--tably-border-grid);
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
.statusbar > tr > .context-col {
|
|
1307
|
+
background-color: var(--tably-statusbar);
|
|
1308
|
+
}
|
|
1101
1309
|
|
|
1102
1310
|
.headers > tr,
|
|
1103
1311
|
.row,
|
|
1312
|
+
.expandable,
|
|
1313
|
+
.filler,
|
|
1104
1314
|
.statusbar > tr {
|
|
1105
1315
|
display: grid;
|
|
1106
1316
|
width: 100%;
|
|
1107
|
-
height: 100%;
|
|
1108
1317
|
min-width: max-content;
|
|
1109
1318
|
|
|
1110
1319
|
& > .column {
|
|
@@ -1141,11 +1350,30 @@
|
|
|
1141
1350
|
border-top: 2px solid var(--tably-border-grid);
|
|
1142
1351
|
}
|
|
1143
1352
|
|
|
1144
|
-
.row >
|
|
1353
|
+
.row > *,
|
|
1354
|
+
.filler > * {
|
|
1145
1355
|
border-left: 1px solid var(--tably-border-grid);
|
|
1146
1356
|
border-bottom: 1px solid var(--tably-border-grid);
|
|
1147
1357
|
}
|
|
1148
1358
|
|
|
1359
|
+
.filler {
|
|
1360
|
+
pointer-events: none;
|
|
1361
|
+
user-select: none;
|
|
1362
|
+
background: none;
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
.sr-only {
|
|
1366
|
+
position: absolute;
|
|
1367
|
+
width: 1px;
|
|
1368
|
+
height: 1px;
|
|
1369
|
+
padding: 0;
|
|
1370
|
+
margin: -1px;
|
|
1371
|
+
overflow: hidden;
|
|
1372
|
+
clip: rect(0, 0, 0, 0);
|
|
1373
|
+
white-space: nowrap;
|
|
1374
|
+
border: 0;
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1149
1377
|
.panel {
|
|
1150
1378
|
position: relative;
|
|
1151
1379
|
grid-area: panel;
|