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