svelte-tably 1.1.2 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -2
- 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 +14 -2
- package/dist/row/row-state.svelte.js +3 -2
- package/dist/size-tween.svelte.js +9 -1
- package/dist/table/Table.svelte +483 -128
- 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/table-state.svelte.js +67 -8
- 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,16 +114,100 @@
|
|
|
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
|
|
|
152
|
+
const measureContextCellWidth = (cell: HTMLElement | null) => {
|
|
153
|
+
if (!cell) return 0
|
|
154
|
+
const inner = cell.querySelector(':scope > .context-inner') as HTMLElement | null
|
|
155
|
+
const content = inner?.firstElementChild as HTMLElement | null
|
|
156
|
+
const candidates = [cell, inner, content].filter(Boolean) as HTMLElement[]
|
|
157
|
+
let width = 0
|
|
158
|
+
for (const el of candidates) {
|
|
159
|
+
width = Math.max(
|
|
160
|
+
width,
|
|
161
|
+
Math.ceil(el.getBoundingClientRect().width),
|
|
162
|
+
Math.ceil(el.scrollWidth)
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
return width
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
let contextWidth = $state(0)
|
|
169
|
+
let contextWidthRaf = 0
|
|
170
|
+
$effect(() => {
|
|
171
|
+
if (!mount.isMounted) {
|
|
172
|
+
contextWidth = 0
|
|
173
|
+
if (contextWidthRaf) cancelAnimationFrame(contextWidthRaf)
|
|
174
|
+
return
|
|
175
|
+
}
|
|
176
|
+
if (!table.row?.snippets.context) {
|
|
177
|
+
contextWidth = 0
|
|
178
|
+
if (contextWidthRaf) cancelAnimationFrame(contextWidthRaf)
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
if (!table.row?.options.context.alignHeaderToRows) {
|
|
182
|
+
contextWidth = 0
|
|
183
|
+
if (contextWidthRaf) cancelAnimationFrame(contextWidthRaf)
|
|
184
|
+
return
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
virtualization.topIndex
|
|
188
|
+
if (contextWidthRaf) cancelAnimationFrame(contextWidthRaf)
|
|
189
|
+
contextWidthRaf = requestAnimationFrame(() => {
|
|
190
|
+
const headerCell = elements.headers?.querySelector(
|
|
191
|
+
'[data-tably-context-measure="header"]'
|
|
192
|
+
) as HTMLElement | null
|
|
193
|
+
const rowCell = virtualization.viewport.element?.querySelector(
|
|
194
|
+
'[data-tably-context-measure="row"]'
|
|
195
|
+
) as HTMLElement | null
|
|
196
|
+
|
|
197
|
+
const width = Math.max(
|
|
198
|
+
measureContextCellWidth(headerCell),
|
|
199
|
+
measureContextCellWidth(rowCell)
|
|
200
|
+
)
|
|
201
|
+
if (width > 0 && width !== contextWidth) {
|
|
202
|
+
contextWidth = width
|
|
203
|
+
}
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
return () => {
|
|
207
|
+
if (contextWidthRaf) cancelAnimationFrame(contextWidthRaf)
|
|
208
|
+
}
|
|
209
|
+
})
|
|
210
|
+
|
|
99
211
|
/** grid-template-columns for widths */
|
|
100
212
|
let style = $state('')
|
|
101
213
|
$effect(() => {
|
|
@@ -104,7 +216,7 @@
|
|
|
104
216
|
return
|
|
105
217
|
}
|
|
106
218
|
|
|
107
|
-
const context = table.row?.snippets.context ?
|
|
219
|
+
const context = table.row?.snippets.context ? ' var(--tably-context-width)' : ''
|
|
108
220
|
|
|
109
221
|
const templateColumns =
|
|
110
222
|
columns
|
|
@@ -115,7 +227,7 @@
|
|
|
115
227
|
})
|
|
116
228
|
.join(' ') + context
|
|
117
229
|
|
|
118
|
-
const
|
|
230
|
+
const theadTemplateColumns = `
|
|
119
231
|
[data-svelte-tably="${table.cssId}"] > thead > tr,
|
|
120
232
|
[data-svelte-tably="${table.cssId}"] > tfoot > tr {
|
|
121
233
|
grid-template-columns: ${templateColumns};
|
|
@@ -124,6 +236,8 @@
|
|
|
124
236
|
|
|
125
237
|
const tbodyTemplateColumns = `
|
|
126
238
|
[data-area-class='${table.cssId}'] tr.row,
|
|
239
|
+
[data-area-class='${table.cssId}'] tr.expandable,
|
|
240
|
+
[data-area-class='${table.cssId}'] tr.filler,
|
|
127
241
|
[data-svelte-tably="${table.cssId}"] > tbody::after {
|
|
128
242
|
grid-template-columns: ${templateColumns};
|
|
129
243
|
}
|
|
@@ -154,7 +268,7 @@
|
|
|
154
268
|
)
|
|
155
269
|
.join('')
|
|
156
270
|
|
|
157
|
-
style =
|
|
271
|
+
style = theadTemplateColumns + tbodyTemplateColumns + stickyLeft + columnStyling
|
|
158
272
|
})
|
|
159
273
|
|
|
160
274
|
function observeColumnWidth(node: HTMLDivElement, isHeader = false) {
|
|
@@ -188,16 +302,34 @@
|
|
|
188
302
|
}
|
|
189
303
|
|
|
190
304
|
let tbody = $state({
|
|
191
|
-
|
|
305
|
+
scrollbar: 0,
|
|
306
|
+
viewportWidth: 0
|
|
192
307
|
})
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
if (
|
|
196
|
-
|
|
308
|
+
|
|
309
|
+
function observeScrollbar(node: HTMLElement) {
|
|
310
|
+
if (typeof ResizeObserver === 'undefined') return
|
|
311
|
+
|
|
312
|
+
const update = () => {
|
|
313
|
+
// Reserve the same gutter in header/footer as the scrollable body
|
|
314
|
+
tbody.scrollbar = Math.max(0, node.offsetWidth - node.clientWidth)
|
|
315
|
+
tbody.viewportWidth = node.clientWidth
|
|
197
316
|
}
|
|
198
317
|
|
|
199
|
-
|
|
200
|
-
|
|
318
|
+
update()
|
|
319
|
+
const observer = new ResizeObserver(update)
|
|
320
|
+
observer.observe(node)
|
|
321
|
+
|
|
322
|
+
return {
|
|
323
|
+
destroy() {
|
|
324
|
+
observer.disconnect()
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
function onscroll() {
|
|
329
|
+
const target = virtualization.viewport.element
|
|
330
|
+
if (!target) return
|
|
331
|
+
if (target.scrollTop !== virtualization.scrollTop) {
|
|
332
|
+
virtualization.scrollTop = target?.scrollTop ?? virtualization.scrollTop
|
|
201
333
|
}
|
|
202
334
|
|
|
203
335
|
if (elements.headers) {
|
|
@@ -415,7 +547,11 @@
|
|
|
415
547
|
class:fixed={true}
|
|
416
548
|
use:addRowColumnEvents={[where, column, () => args[1]]}
|
|
417
549
|
data-column={column.id}
|
|
418
|
-
class:pad={
|
|
550
|
+
class:pad={
|
|
551
|
+
(where === 'header' && column.options.padHeader) ||
|
|
552
|
+
(where === 'row' && column.options.padRow) ||
|
|
553
|
+
(where === 'statusbar' && column.options.padStatusbar)
|
|
554
|
+
}
|
|
419
555
|
class:header={isHeader}
|
|
420
556
|
class:sortable
|
|
421
557
|
use:conditional={[isHeader, (node) => table.dataState.sortAction(node, column.id)]}
|
|
@@ -443,7 +579,11 @@
|
|
|
443
579
|
use:addRowColumnEvents={[where, column, () => args[1]]}
|
|
444
580
|
use:observeColumnWidth={isHeader}
|
|
445
581
|
data-column={column.id}
|
|
446
|
-
class:pad={
|
|
582
|
+
class:pad={
|
|
583
|
+
(where === 'header' && column.options.padHeader) ||
|
|
584
|
+
(where === 'row' && column.options.padRow) ||
|
|
585
|
+
(where === 'statusbar' && column.options.padStatusbar)
|
|
586
|
+
}
|
|
447
587
|
class:header={isHeader}
|
|
448
588
|
class:resizeable={isHeader && column.options.resizeable && table.options.resizeable}
|
|
449
589
|
class:border={i == sticky.length - 1}
|
|
@@ -470,7 +610,11 @@
|
|
|
470
610
|
class={column.options.class ?? ''}
|
|
471
611
|
class:column={true}
|
|
472
612
|
data-column={column.id}
|
|
473
|
-
class:pad={
|
|
613
|
+
class:pad={
|
|
614
|
+
(where === 'header' && column.options.padHeader) ||
|
|
615
|
+
(where === 'row' && column.options.padRow) ||
|
|
616
|
+
(where === 'statusbar' && column.options.padStatusbar)
|
|
617
|
+
}
|
|
474
618
|
use:addRowColumnEvents={[where, column, () => args[1]]}
|
|
475
619
|
use:observeColumnWidth={isHeader}
|
|
476
620
|
class:resizeable={isHeader && column.options.resizeable && table.options.resizeable}
|
|
@@ -490,7 +634,7 @@
|
|
|
490
634
|
{/each}
|
|
491
635
|
{/snippet}
|
|
492
636
|
|
|
493
|
-
{#snippet defaultRow(item:
|
|
637
|
+
{#snippet defaultRow(item: any, ctx: RowColumnCtx<any, any>)}
|
|
494
638
|
{ctx.value}
|
|
495
639
|
{/snippet}
|
|
496
640
|
|
|
@@ -508,9 +652,16 @@
|
|
|
508
652
|
return table.selected?.includes(item)
|
|
509
653
|
},
|
|
510
654
|
set selected(value) {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
655
|
+
const current = table.selected
|
|
656
|
+
if (value) {
|
|
657
|
+
if (!current.includes(item)) {
|
|
658
|
+
table.selected = [...current, item]
|
|
659
|
+
}
|
|
660
|
+
return
|
|
661
|
+
}
|
|
662
|
+
if (current.includes(item)) {
|
|
663
|
+
table.selected = current.filter((v) => v !== item)
|
|
664
|
+
}
|
|
514
665
|
},
|
|
515
666
|
get itemState() {
|
|
516
667
|
return itemState
|
|
@@ -537,10 +688,8 @@
|
|
|
537
688
|
use:addRowEvents={ctx}
|
|
538
689
|
onclick={(e) => {
|
|
539
690
|
if (table.expandable?.options.click === true) {
|
|
540
|
-
|
|
541
|
-
if (
|
|
542
|
-
return
|
|
543
|
-
}
|
|
691
|
+
const target = e.target
|
|
692
|
+
if (target instanceof Element && target.closest('input, textarea, button, a')) return
|
|
544
693
|
ctx.expanded = !ctx.expanded
|
|
545
694
|
}
|
|
546
695
|
}}
|
|
@@ -566,30 +715,57 @@
|
|
|
566
715
|
'row'
|
|
567
716
|
)}
|
|
568
717
|
{#if table.row?.snippets.context}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
718
|
+
<td
|
|
719
|
+
class="context-col"
|
|
720
|
+
class:hidden={table.row?.options.context.hover && hoveredRow !== item}
|
|
721
|
+
data-tably-context-measure={
|
|
722
|
+
table.row?.options.context.alignHeaderToRows &&
|
|
723
|
+
index === virtualization.topIndex ?
|
|
724
|
+
'row'
|
|
725
|
+
: undefined
|
|
726
|
+
}
|
|
727
|
+
>
|
|
728
|
+
<div class="context-inner">
|
|
577
729
|
{@render table.row?.snippets.context?.(item, ctx)}
|
|
578
|
-
</
|
|
579
|
-
|
|
730
|
+
</div>
|
|
731
|
+
</td>
|
|
580
732
|
{/if}
|
|
581
733
|
</tr>
|
|
582
734
|
|
|
583
|
-
{@const expandableTween = new SizeTween(() => table.expandable && expandedRow.includes(item), {
|
|
584
|
-
min:
|
|
735
|
+
{@const expandableTween = new SizeTween(() => !!table.expandable && expandedRow.includes(item), {
|
|
736
|
+
min: 0,
|
|
585
737
|
duration: table.expandable?.options.slide.duration,
|
|
586
738
|
easing: table.expandable?.options.slide.easing
|
|
587
739
|
})}
|
|
588
|
-
{
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
740
|
+
{@const expanded = !!table.expandable && expandedRow.includes(item)}
|
|
741
|
+
{#if table.expandable && (expanded || expandableTween.current > 0 || expandableTween.transitioning)}
|
|
742
|
+
{@const expandId = getExpandId(item)}
|
|
743
|
+
{@const expandLabelId = `${expandId}-label`}
|
|
744
|
+
<tr class="expandable">
|
|
745
|
+
<td
|
|
746
|
+
class="expandable-cell"
|
|
747
|
+
colspan={columns.length + (table.row?.snippets.context ? 1 : 0)}
|
|
748
|
+
style="padding: 0"
|
|
749
|
+
>
|
|
750
|
+
<div class="expandable-sticky">
|
|
751
|
+
<div
|
|
752
|
+
class="expandable-clip"
|
|
753
|
+
style="height: {Math.round(expandableTween.current)}px"
|
|
754
|
+
id={expandId}
|
|
755
|
+
role="region"
|
|
756
|
+
aria-labelledby={expandLabelId}
|
|
757
|
+
aria-hidden={!expanded}
|
|
758
|
+
>
|
|
759
|
+
<span class="sr-only" id={expandLabelId}>
|
|
760
|
+
Expanded content for {getRowLabel(item, index)}
|
|
761
|
+
</span>
|
|
762
|
+
<div
|
|
763
|
+
class="expandable-content"
|
|
764
|
+
bind:offsetHeight={expandableTween.size}
|
|
765
|
+
>
|
|
766
|
+
{@render table.expandable?.snippets.content?.(item, ctx)}
|
|
767
|
+
</div>
|
|
768
|
+
</div>
|
|
593
769
|
</div>
|
|
594
770
|
</td>
|
|
595
771
|
</tr>
|
|
@@ -600,7 +776,7 @@
|
|
|
600
776
|
id={table.id}
|
|
601
777
|
data-svelte-tably={table.cssId}
|
|
602
778
|
class="table svelte-tably"
|
|
603
|
-
style="--t: {virtualization.virtualTop}px; --b: {virtualization.virtualBottom}px;"
|
|
779
|
+
style="--t: {virtualization.virtualTop}px; --b: {virtualization.virtualBottom}px; --scrollbar: {tbody.scrollbar}px; --viewport-width: {tbody.viewportWidth}px; --tably-context-width: {table.row?.options.context.alignHeaderToRows && contextWidth > 0 ? `${contextWidth}px` : (table.row?.options.context.width ?? 'max-content')};"
|
|
604
780
|
aria-rowcount={table.data.length}
|
|
605
781
|
>
|
|
606
782
|
{#if columns.some((v) => v.snippets.header)}
|
|
@@ -620,23 +796,31 @@
|
|
|
620
796
|
],
|
|
621
797
|
'header'
|
|
622
798
|
)}
|
|
623
|
-
{#if table.row?.snippets.
|
|
624
|
-
<th
|
|
625
|
-
|
|
799
|
+
{#if table.row?.snippets.context}
|
|
800
|
+
<th
|
|
801
|
+
class="context-col"
|
|
802
|
+
data-tably-context-measure={table.row?.options.context.alignHeaderToRows ? 'header' : undefined}
|
|
803
|
+
aria-hidden={table.row?.snippets.contextHeader ? undefined : true}
|
|
804
|
+
role={table.row?.snippets.contextHeader ? undefined : 'presentation'}
|
|
805
|
+
>
|
|
806
|
+
{#if table.row?.snippets.contextHeader}
|
|
807
|
+
<div class="context-inner">
|
|
808
|
+
{@render table.row?.snippets.contextHeader()}
|
|
809
|
+
</div>
|
|
810
|
+
{/if}
|
|
626
811
|
</th>
|
|
627
812
|
{/if}
|
|
628
813
|
</tr>
|
|
629
|
-
<tr style="width:400px;background:none;pointer-events:none;"></tr>
|
|
630
814
|
</thead>
|
|
631
815
|
{/if}
|
|
632
816
|
|
|
633
817
|
<tbody
|
|
634
818
|
class="content"
|
|
635
819
|
use:reorderArea={{ axis: 'y', class: table.cssId }}
|
|
820
|
+
use:observeScrollbar
|
|
636
821
|
bind:this={virtualization.viewport.element}
|
|
637
822
|
onscrollcapture={onscroll}
|
|
638
823
|
bind:clientHeight={virtualization.viewport.height}
|
|
639
|
-
bind:clientWidth={tbody.width}
|
|
640
824
|
>
|
|
641
825
|
{#if table.options.reorderable}
|
|
642
826
|
{@render reorderArea({
|
|
@@ -655,6 +839,39 @@
|
|
|
655
839
|
{@render rowSnippet(item, { index: i + virtualization.topIndex } as ItemState)}
|
|
656
840
|
{/each}
|
|
657
841
|
{/if}
|
|
842
|
+
|
|
843
|
+
{#if columns.length > 0 && virtualization.virtualTop === 0 && virtualization.virtualBottom === 0}
|
|
844
|
+
<tr class="filler" aria-hidden="true">
|
|
845
|
+
{#each fixed as column (column)}
|
|
846
|
+
{#if !hidden.includes(column)}
|
|
847
|
+
<td
|
|
848
|
+
class={`column sticky fixed ${column.options.class ?? ''}`}
|
|
849
|
+
data-column={column.id}
|
|
850
|
+
></td>
|
|
851
|
+
{/if}
|
|
852
|
+
{/each}
|
|
853
|
+
{#each sticky as column, i (column)}
|
|
854
|
+
{#if !hidden.includes(column)}
|
|
855
|
+
<td
|
|
856
|
+
class={`column sticky ${column.options.class ?? ''}`}
|
|
857
|
+
class:border={i == sticky.length - 1}
|
|
858
|
+
data-column={column.id}
|
|
859
|
+
></td>
|
|
860
|
+
{/if}
|
|
861
|
+
{/each}
|
|
862
|
+
{#each scrolled as column (column)}
|
|
863
|
+
{#if !hidden.includes(column)}
|
|
864
|
+
<td
|
|
865
|
+
class={`column ${column.options.class ?? ''}`}
|
|
866
|
+
data-column={column.id}
|
|
867
|
+
></td>
|
|
868
|
+
{/if}
|
|
869
|
+
{/each}
|
|
870
|
+
{#if table.row?.snippets.context}
|
|
871
|
+
<td class="context-col" aria-hidden="true"></td>
|
|
872
|
+
{/if}
|
|
873
|
+
</tr>
|
|
874
|
+
{/if}
|
|
658
875
|
</tbody>
|
|
659
876
|
|
|
660
877
|
{#if columns.some((v) => v.snippets.statusbar)}
|
|
@@ -671,8 +888,10 @@
|
|
|
671
888
|
],
|
|
672
889
|
'statusbar'
|
|
673
890
|
)}
|
|
891
|
+
{#if table.row?.snippets.context}
|
|
892
|
+
<td class="context-col" aria-hidden="true"></td>
|
|
893
|
+
{/if}
|
|
674
894
|
</tr>
|
|
675
|
-
<tr style="width:400px;background:none;pointer-events:none;"></tr>
|
|
676
895
|
</tfoot>
|
|
677
896
|
{/if}
|
|
678
897
|
|
|
@@ -708,11 +927,11 @@
|
|
|
708
927
|
</caption>
|
|
709
928
|
</table>
|
|
710
929
|
|
|
711
|
-
{#snippet headerSelected(ctx: HeaderSelectCtx<
|
|
930
|
+
{#snippet headerSelected(ctx: HeaderSelectCtx<any>)}
|
|
712
931
|
<input type="checkbox" indeterminate={ctx.indeterminate} bind:checked={ctx.isSelected} />
|
|
713
932
|
{/snippet}
|
|
714
933
|
|
|
715
|
-
{#snippet rowSelected(ctx: RowSelectCtx<
|
|
934
|
+
{#snippet rowSelected(ctx: RowSelectCtx<any>)}
|
|
716
935
|
<input type="checkbox" bind:checked={ctx.isSelected} tabindex="-1" />
|
|
717
936
|
{/snippet}
|
|
718
937
|
|
|
@@ -798,9 +1017,20 @@
|
|
|
798
1017
|
})}
|
|
799
1018
|
{/if}
|
|
800
1019
|
{#if expandable && expandable?.options.chevron !== 'never'}
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
1020
|
+
{@const expandId = getExpandId(item)}
|
|
1021
|
+
{@const expanded = row.expanded}
|
|
1022
|
+
{@const label = expanded ? 'Collapse row' : 'Expand row'}
|
|
1023
|
+
<button
|
|
1024
|
+
class="expand-row"
|
|
1025
|
+
tabindex="-1"
|
|
1026
|
+
type="button"
|
|
1027
|
+
aria-label={label}
|
|
1028
|
+
aria-expanded={expanded}
|
|
1029
|
+
aria-controls={expandId}
|
|
1030
|
+
onclick={() => (row.expanded = !row.expanded)}
|
|
1031
|
+
>
|
|
1032
|
+
{#if expanded || expandable.options.chevron === 'always' || (row.rowHovered && expandable.options.chevron === 'hover')}
|
|
1033
|
+
{@render chevronSnippet(expanded ? 180 : 90)}
|
|
804
1034
|
{/if}
|
|
805
1035
|
</button>
|
|
806
1036
|
{/if}
|
|
@@ -811,12 +1041,12 @@
|
|
|
811
1041
|
{/if}
|
|
812
1042
|
|
|
813
1043
|
{#if table.options.auto}
|
|
814
|
-
{#each
|
|
1044
|
+
{#each autoSchema.keys as key}
|
|
815
1045
|
<Column
|
|
816
1046
|
id={key}
|
|
817
|
-
value={(r) => r[key]}
|
|
1047
|
+
value={(r) => (r as any)?.[key]}
|
|
818
1048
|
header={capitalize(segmentize(key))}
|
|
819
|
-
sort={typeof
|
|
1049
|
+
sort={typeof autoSchema.sample?.[key] === 'number' ?
|
|
820
1050
|
(a, b) => a - b
|
|
821
1051
|
: (a, b) => String(a).localeCompare(String(b))}
|
|
822
1052
|
/>
|
|
@@ -847,24 +1077,57 @@
|
|
|
847
1077
|
justify-content: center;
|
|
848
1078
|
position: sticky;
|
|
849
1079
|
right: 0;
|
|
850
|
-
height: 100%;
|
|
851
1080
|
z-index: 3;
|
|
852
1081
|
padding: 0;
|
|
853
|
-
|
|
854
|
-
&.hover {
|
|
855
|
-
position: absolute;
|
|
856
|
-
}
|
|
1082
|
+
border-left: 1px solid var(--tably-border);
|
|
857
1083
|
&.hidden {
|
|
858
1084
|
pointer-events: none;
|
|
859
1085
|
user-select: none;
|
|
860
1086
|
border-left: none;
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
opacity: 0;
|
|
1087
|
+
> .context-inner {
|
|
1088
|
+
visibility: hidden;
|
|
864
1089
|
}
|
|
865
1090
|
}
|
|
866
1091
|
}
|
|
867
1092
|
|
|
1093
|
+
.context-inner {
|
|
1094
|
+
display: flex;
|
|
1095
|
+
align-items: center;
|
|
1096
|
+
justify-content: center;
|
|
1097
|
+
padding: calc(var(--tably-padding-y) / 2) 0;
|
|
1098
|
+
overflow: clip;
|
|
1099
|
+
width: 100%;
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
.table::before {
|
|
1103
|
+
content: '';
|
|
1104
|
+
grid-area: headers;
|
|
1105
|
+
justify-self: end;
|
|
1106
|
+
align-self: stretch;
|
|
1107
|
+
width: var(--scrollbar, 0px);
|
|
1108
|
+
background-color: var(--tably-bg);
|
|
1109
|
+
border-bottom: 1px solid var(--tably-border);
|
|
1110
|
+
border-right: 1px solid var(--tably-border);
|
|
1111
|
+
margin-right: -1px;
|
|
1112
|
+
pointer-events: none;
|
|
1113
|
+
position: relative;
|
|
1114
|
+
z-index: 4;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
.table::after {
|
|
1118
|
+
content: '';
|
|
1119
|
+
grid-area: statusbar;
|
|
1120
|
+
justify-self: end;
|
|
1121
|
+
align-self: stretch;
|
|
1122
|
+
width: var(--scrollbar, 0px);
|
|
1123
|
+
background-color: var(--tably-statusbar);
|
|
1124
|
+
border-right: 1px solid var(--tably-border);
|
|
1125
|
+
margin-right: -1px;
|
|
1126
|
+
pointer-events: none;
|
|
1127
|
+
position: relative;
|
|
1128
|
+
z-index: 4;
|
|
1129
|
+
}
|
|
1130
|
+
|
|
868
1131
|
:global(:root) {
|
|
869
1132
|
--tably-color: hsl(0, 0%, 0%);
|
|
870
1133
|
--tably-bg: hsl(0, 0%, 100%);
|
|
@@ -881,24 +1144,50 @@
|
|
|
881
1144
|
|
|
882
1145
|
.svelte-tably {
|
|
883
1146
|
position: relative;
|
|
884
|
-
overflow:
|
|
1147
|
+
overflow: hidden;
|
|
1148
|
+
border-collapse: collapse;
|
|
1149
|
+
border-spacing: 0;
|
|
885
1150
|
}
|
|
886
1151
|
|
|
887
1152
|
.expandable {
|
|
888
|
-
position: relative;
|
|
889
|
-
|
|
890
1153
|
& > td {
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
> div {
|
|
894
|
-
position: absolute;
|
|
895
|
-
overflow: auto;
|
|
896
|
-
top: -1.5px;
|
|
897
|
-
left: 0;
|
|
898
|
-
}
|
|
1154
|
+
padding: 0;
|
|
1155
|
+
border: none;
|
|
899
1156
|
}
|
|
900
1157
|
}
|
|
901
1158
|
|
|
1159
|
+
.expandable-cell {
|
|
1160
|
+
grid-column: 1 / -1;
|
|
1161
|
+
display: block;
|
|
1162
|
+
min-width: 0;
|
|
1163
|
+
width: 100%;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
.expandable-sticky {
|
|
1167
|
+
position: sticky;
|
|
1168
|
+
left: 0;
|
|
1169
|
+
width: var(--viewport-width, 100%);
|
|
1170
|
+
min-width: 0;
|
|
1171
|
+
display: block;
|
|
1172
|
+
background-color: var(--tably-bg);
|
|
1173
|
+
z-index: 1;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
.expandable-clip {
|
|
1177
|
+
overflow: hidden;
|
|
1178
|
+
width: 100%;
|
|
1179
|
+
background-color: var(--tably-bg);
|
|
1180
|
+
border-bottom: 1px solid var(--tably-border-grid);
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
.expandable-content {
|
|
1184
|
+
overflow: auto;
|
|
1185
|
+
width: 100%;
|
|
1186
|
+
background-color: var(--tably-bg);
|
|
1187
|
+
box-sizing: border-box;
|
|
1188
|
+
min-width: 0;
|
|
1189
|
+
}
|
|
1190
|
+
|
|
902
1191
|
.expand-row {
|
|
903
1192
|
display: flex;
|
|
904
1193
|
justify-content: center;
|
|
@@ -952,6 +1241,7 @@
|
|
|
952
1241
|
align-items: center;
|
|
953
1242
|
justify-content: center;
|
|
954
1243
|
gap: 0.25rem;
|
|
1244
|
+
background-color: transparent;
|
|
955
1245
|
position: absolute;
|
|
956
1246
|
top: 0;
|
|
957
1247
|
left: 0;
|
|
@@ -960,25 +1250,25 @@
|
|
|
960
1250
|
width: 100%;
|
|
961
1251
|
}
|
|
962
1252
|
|
|
1253
|
+
.__fixed > * {
|
|
1254
|
+
background-color: transparent;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
963
1257
|
thead {
|
|
964
1258
|
position: relative;
|
|
965
1259
|
}
|
|
966
1260
|
|
|
967
1261
|
tbody::before,
|
|
968
|
-
tbody::after
|
|
969
|
-
selects::before,
|
|
970
|
-
selects::after {
|
|
1262
|
+
tbody::after {
|
|
971
1263
|
content: '';
|
|
972
|
-
display:
|
|
973
|
-
|
|
1264
|
+
display: block;
|
|
1265
|
+
flex: 0 0 auto;
|
|
974
1266
|
}
|
|
975
1267
|
|
|
976
|
-
tbody::before
|
|
977
|
-
selects::before {
|
|
1268
|
+
tbody::before {
|
|
978
1269
|
height: var(--t);
|
|
979
1270
|
}
|
|
980
|
-
tbody::after
|
|
981
|
-
selects::after {
|
|
1271
|
+
tbody::after {
|
|
982
1272
|
height: var(--b);
|
|
983
1273
|
}
|
|
984
1274
|
|
|
@@ -1046,7 +1336,10 @@
|
|
|
1046
1336
|
|
|
1047
1337
|
.table {
|
|
1048
1338
|
display: grid;
|
|
1049
|
-
|
|
1339
|
+
width: 100%;
|
|
1340
|
+
min-width: 0;
|
|
1341
|
+
min-height: 0;
|
|
1342
|
+
height: 100%;
|
|
1050
1343
|
max-height: 100%;
|
|
1051
1344
|
position: relative;
|
|
1052
1345
|
|
|
@@ -1058,8 +1351,8 @@
|
|
|
1058
1351
|
'rows panel'
|
|
1059
1352
|
'statusbar panel';
|
|
1060
1353
|
|
|
1061
|
-
grid-template-columns:
|
|
1062
|
-
grid-template-rows: auto 1fr auto;
|
|
1354
|
+
grid-template-columns: 1fr min-content;
|
|
1355
|
+
grid-template-rows: auto minmax(0, 1fr) auto;
|
|
1063
1356
|
|
|
1064
1357
|
border: 1px solid var(--tably-border);
|
|
1065
1358
|
border-radius: var(--tably-radius);
|
|
@@ -1070,25 +1363,43 @@
|
|
|
1070
1363
|
grid-area: headers;
|
|
1071
1364
|
z-index: 2;
|
|
1072
1365
|
overflow: hidden;
|
|
1366
|
+
min-width: 0;
|
|
1367
|
+
padding-right: var(--scrollbar, 0px);
|
|
1368
|
+
border-bottom: 1px solid var(--tably-border);
|
|
1073
1369
|
}
|
|
1074
1370
|
|
|
1075
1371
|
.headers > tr > .column {
|
|
1076
1372
|
width: auto !important;
|
|
1077
|
-
border-bottom: 1px solid var(--tably-border);
|
|
1078
1373
|
}
|
|
1079
|
-
.headers > tr > .column
|
|
1080
|
-
.headers > tr > .context-col {
|
|
1081
|
-
border-bottom: 1px solid var(--tably-border);
|
|
1374
|
+
.headers > tr > .column:not(:first-child) {
|
|
1082
1375
|
border-left: 1px solid var(--tably-border-grid);
|
|
1083
1376
|
}
|
|
1084
1377
|
|
|
1378
|
+
.headers > tr > .context-col {
|
|
1379
|
+
border-left: 1px solid var(--tably-border);
|
|
1380
|
+
background-color: var(--tably-bg);
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1085
1383
|
.content {
|
|
1086
|
-
display:
|
|
1087
|
-
|
|
1384
|
+
display: flex;
|
|
1385
|
+
flex-direction: column;
|
|
1386
|
+
min-width: 0;
|
|
1387
|
+
min-height: 0;
|
|
1088
1388
|
|
|
1089
1389
|
grid-area: rows;
|
|
1090
1390
|
scrollbar-width: thin;
|
|
1091
|
-
overflow: auto;
|
|
1391
|
+
overflow-x: auto;
|
|
1392
|
+
overflow-y: scroll;
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
.content > tr.row,
|
|
1396
|
+
.content > tr.expandable {
|
|
1397
|
+
flex: 0 0 auto;
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
.content > tr.filler {
|
|
1401
|
+
flex: 1 0 0px;
|
|
1402
|
+
min-height: 0;
|
|
1092
1403
|
}
|
|
1093
1404
|
|
|
1094
1405
|
.statusbar {
|
|
@@ -1096,19 +1407,30 @@
|
|
|
1096
1407
|
grid-area: statusbar;
|
|
1097
1408
|
overflow: hidden;
|
|
1098
1409
|
background-color: var(--tably-statusbar);
|
|
1410
|
+
min-width: 0;
|
|
1411
|
+
padding-right: var(--scrollbar, 0px);
|
|
1099
1412
|
}
|
|
1100
1413
|
|
|
1101
1414
|
.statusbar > tr > .column {
|
|
1102
1415
|
border-top: 1px solid var(--tably-border);
|
|
1103
1416
|
padding: calc(var(--tably-padding-y) / 2) 0;
|
|
1104
1417
|
}
|
|
1418
|
+
.statusbar > tr > .context-col {
|
|
1419
|
+
border-top: 1px solid var(--tably-border);
|
|
1420
|
+
border-left: 1px solid var(--tably-border);
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
.statusbar > tr > .context-col {
|
|
1424
|
+
background-color: var(--tably-statusbar);
|
|
1425
|
+
}
|
|
1105
1426
|
|
|
1106
1427
|
.headers > tr,
|
|
1107
1428
|
.row,
|
|
1429
|
+
.expandable,
|
|
1430
|
+
.filler,
|
|
1108
1431
|
.statusbar > tr {
|
|
1109
1432
|
display: grid;
|
|
1110
1433
|
width: 100%;
|
|
1111
|
-
height: 100%;
|
|
1112
1434
|
min-width: max-content;
|
|
1113
1435
|
|
|
1114
1436
|
& > .column {
|
|
@@ -1140,16 +1462,49 @@
|
|
|
1140
1462
|
}
|
|
1141
1463
|
}
|
|
1142
1464
|
|
|
1465
|
+
.row > .context-col {
|
|
1466
|
+
background-color: var(--tably-bg);
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
.row > .context-col.hidden {
|
|
1470
|
+
background-color: transparent;
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1143
1473
|
:global(#runic-drag .row) {
|
|
1144
1474
|
border: 1px solid var(--tably-border-grid);
|
|
1145
1475
|
border-top: 2px solid var(--tably-border-grid);
|
|
1146
1476
|
}
|
|
1147
1477
|
|
|
1148
|
-
.
|
|
1478
|
+
.headers > tr > .column:not(:first-child),
|
|
1479
|
+
.row > .column:not(:first-child),
|
|
1480
|
+
.filler > .column:not(:first-child),
|
|
1481
|
+
.statusbar > tr > .column:not(:first-child) {
|
|
1149
1482
|
border-left: 1px solid var(--tably-border-grid);
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
.row,
|
|
1486
|
+
.filler {
|
|
1150
1487
|
border-bottom: 1px solid var(--tably-border-grid);
|
|
1151
1488
|
}
|
|
1152
1489
|
|
|
1490
|
+
.filler {
|
|
1491
|
+
pointer-events: none;
|
|
1492
|
+
user-select: none;
|
|
1493
|
+
background: none;
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
.sr-only {
|
|
1497
|
+
position: absolute;
|
|
1498
|
+
width: 1px;
|
|
1499
|
+
height: 1px;
|
|
1500
|
+
padding: 0;
|
|
1501
|
+
margin: -1px;
|
|
1502
|
+
overflow: hidden;
|
|
1503
|
+
clip: rect(0, 0, 0, 0);
|
|
1504
|
+
white-space: nowrap;
|
|
1505
|
+
border: 0;
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1153
1508
|
.panel {
|
|
1154
1509
|
position: relative;
|
|
1155
1510
|
grid-area: panel;
|