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