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