svelte-tably 1.0.0-next.18 → 1.0.0-next.20
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.
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {} from 'svelte';
|
|
2
2
|
import { TableState } from '../table/table.svelte.js';
|
|
3
|
-
import {
|
|
3
|
+
import {} from '../utility.svelte.js';
|
|
4
4
|
import { getDefaultHeader } from './Column.svelte';
|
|
5
5
|
export class ColumnState {
|
|
6
6
|
#props = {};
|
|
@@ -13,7 +13,7 @@ export class ColumnState {
|
|
|
13
13
|
header: typeof this.#props.header === 'string' ? getDefaultHeader(this.#props.header) : this.#props.header,
|
|
14
14
|
/** Title is the header-snippet, with header-ctx: `{ header: false }` */
|
|
15
15
|
title: (...args) => {
|
|
16
|
-
const getData = () => this.table.
|
|
16
|
+
const getData = () => this.table.dataState.current;
|
|
17
17
|
return this.snippets.header?.(...[args[0], () => ({
|
|
18
18
|
get header() { return false; },
|
|
19
19
|
get data() {
|
|
@@ -43,7 +43,9 @@ export class ColumnState {
|
|
|
43
43
|
resizeable: this.#props.resizeable ?? true,
|
|
44
44
|
style: this.#props.style,
|
|
45
45
|
class: this.#props.class,
|
|
46
|
-
onclick: this.#props.onclick
|
|
46
|
+
onclick: this.#props.onclick,
|
|
47
|
+
padRow: this.#props.pad === 'row' || this.#props.pad === 'both',
|
|
48
|
+
padHeader: this.#props.pad === 'header' || this.#props.pad === 'both'
|
|
47
49
|
});
|
|
48
50
|
toggleVisiblity() {
|
|
49
51
|
const index = this.table.positions.hidden.indexOf(this);
|
package/dist/table/Table.svelte
CHANGED
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
-->
|
|
10
10
|
|
|
11
11
|
<script module lang="ts">
|
|
12
|
-
|
|
13
12
|
</script>
|
|
14
13
|
|
|
15
14
|
<script lang="ts">
|
|
@@ -18,7 +17,13 @@
|
|
|
18
17
|
import { sineInOut } from 'svelte/easing'
|
|
19
18
|
import reorder, { type ItemState } from 'runic-reorder'
|
|
20
19
|
import { Virtualization } from './virtualization.svelte.js'
|
|
21
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
TableState,
|
|
22
|
+
type HeaderSelectCtx,
|
|
23
|
+
type RowCtx,
|
|
24
|
+
type RowSelectCtx,
|
|
25
|
+
type TableProps
|
|
26
|
+
} from './table.svelte.js'
|
|
22
27
|
import Panel from '../panel/Panel.svelte'
|
|
23
28
|
import Column from '../column/Column.svelte'
|
|
24
29
|
import { assignDescriptors, capitalize, fromProps, mounted, segmentize } from '../utility.svelte.js'
|
|
@@ -28,12 +33,13 @@
|
|
|
28
33
|
import { SizeTween } from '../size-tween.svelte.js'
|
|
29
34
|
import { on } from 'svelte/events'
|
|
30
35
|
import Row from '../row/Row.svelte'
|
|
31
|
-
|
|
32
36
|
|
|
33
37
|
type T = $$Generic<Record<PropertyKey, unknown>>
|
|
34
38
|
|
|
35
|
-
type ConstructorReturnType<T extends new (...args: any[]) => any> =
|
|
36
|
-
|
|
39
|
+
type ConstructorReturnType<T extends new (...args: any[]) => any> =
|
|
40
|
+
T extends new (...args: any[]) => infer K ? K : never
|
|
41
|
+
type ConstructorParams<T extends new (...args: any[]) => any> =
|
|
42
|
+
T extends new (...args: infer K) => any ? K : never
|
|
37
43
|
|
|
38
44
|
type ContentCtx<T extends Record<PropertyKey, unknown>> = {
|
|
39
45
|
Column: {
|
|
@@ -44,7 +50,6 @@
|
|
|
44
50
|
Expandable: typeof Expandable<T>
|
|
45
51
|
Row: typeof Row<T>
|
|
46
52
|
readonly table: TableState<T>
|
|
47
|
-
readonly data: T[]
|
|
48
53
|
}
|
|
49
54
|
|
|
50
55
|
type ContentSnippet = Snippet<[context: ContentCtx<T>]>
|
|
@@ -58,9 +63,9 @@
|
|
|
58
63
|
}: TableProps<T> & { content?: ContentSnippet } = $props()
|
|
59
64
|
|
|
60
65
|
const properties = fromProps(restProps, {
|
|
61
|
-
selected: [() => _selected, v => _selected = v],
|
|
62
|
-
panel: [() => _panel, v => _panel = v],
|
|
63
|
-
data: [() => _data, v => _data = v]
|
|
66
|
+
selected: [() => _selected, (v) => (_selected = v)],
|
|
67
|
+
panel: [() => _panel, (v) => (_panel = v)],
|
|
68
|
+
data: [() => _data, (v) => (_data = v)]
|
|
64
69
|
}) as TableProps<T>
|
|
65
70
|
|
|
66
71
|
const mount = mounted()
|
|
@@ -71,15 +76,15 @@
|
|
|
71
76
|
'headers' | 'statusbar' | 'rows' | 'virtualTop' | 'virtualBottom' | 'selects',
|
|
72
77
|
HTMLElement
|
|
73
78
|
>
|
|
74
|
-
|
|
79
|
+
|
|
75
80
|
const table = new TableState<T>(properties) as TableState<T>
|
|
76
|
-
|
|
77
|
-
|
|
81
|
+
|
|
78
82
|
const virtualization = new Virtualization(table)
|
|
79
|
-
|
|
83
|
+
|
|
80
84
|
const panelTween = new SizeTween(() => !!properties.panel)
|
|
81
85
|
|
|
82
86
|
let hoveredRow: T | null = $state(null)
|
|
87
|
+
let hoveredColumn: ColumnState | null = $state(null)
|
|
83
88
|
|
|
84
89
|
/** Order of columns */
|
|
85
90
|
const fixed = $derived(table.positions.fixed)
|
|
@@ -99,15 +104,16 @@
|
|
|
99
104
|
const style = $derived.by(() => {
|
|
100
105
|
if (!mount.isMounted) return ''
|
|
101
106
|
|
|
102
|
-
const context = table.row?.snippets.context ? table.row?.options.context.width : ''
|
|
107
|
+
const context = table.row?.snippets.context ? table.row?.options.context.width : ''
|
|
103
108
|
|
|
104
|
-
const templateColumns =
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
const templateColumns =
|
|
110
|
+
columns
|
|
111
|
+
.map((column, i, arr) => {
|
|
112
|
+
const width = getWidth(column.id)
|
|
113
|
+
if (i === arr.length - 1) return `minmax(${width}px, 1fr)`
|
|
114
|
+
return `${width}px`
|
|
115
|
+
})
|
|
116
|
+
.join(' ') + context
|
|
111
117
|
|
|
112
118
|
const theadTempla3teColumns = `
|
|
113
119
|
#${table.id} > thead > tr,
|
|
@@ -136,11 +142,17 @@
|
|
|
136
142
|
})
|
|
137
143
|
.join('')
|
|
138
144
|
|
|
139
|
-
const columnStyling = columns
|
|
145
|
+
const columnStyling = columns
|
|
146
|
+
.map((column) =>
|
|
147
|
+
!column.options.style ?
|
|
148
|
+
''
|
|
149
|
+
: `
|
|
140
150
|
[data-area-class='${table.id}'] .column[data-column='${column.id}'] {
|
|
141
151
|
${column.options.style}
|
|
142
152
|
}
|
|
143
|
-
`
|
|
153
|
+
`
|
|
154
|
+
)
|
|
155
|
+
.join('')
|
|
144
156
|
|
|
145
157
|
return theadTempla3teColumns + tbodyTemplateColumns + stickyLeft + columnStyling
|
|
146
158
|
})
|
|
@@ -155,15 +167,19 @@
|
|
|
155
167
|
|
|
156
168
|
const observer = new MutationObserver(() => {
|
|
157
169
|
const width = parseFloat(node.style.width)
|
|
158
|
-
if(width === columnWidths[key]) return
|
|
170
|
+
if (width === columnWidths[key]) return
|
|
159
171
|
columnWidths[key] = width
|
|
160
|
-
if(!mouseup) {
|
|
172
|
+
if (!mouseup) {
|
|
161
173
|
mouseup = true
|
|
162
|
-
window.addEventListener(
|
|
163
|
-
|
|
164
|
-
e
|
|
165
|
-
|
|
166
|
-
|
|
174
|
+
window.addEventListener(
|
|
175
|
+
'click',
|
|
176
|
+
(e) => {
|
|
177
|
+
e.preventDefault()
|
|
178
|
+
e.stopPropagation()
|
|
179
|
+
mouseup = false
|
|
180
|
+
},
|
|
181
|
+
{ once: true, capture: true }
|
|
182
|
+
)
|
|
167
183
|
}
|
|
168
184
|
})
|
|
169
185
|
|
|
@@ -189,7 +205,6 @@
|
|
|
189
205
|
elements.statusbar.scrollLeft = target.scrollLeft
|
|
190
206
|
}
|
|
191
207
|
|
|
192
|
-
|
|
193
208
|
// * --- CSV --- *
|
|
194
209
|
let csv = $state(false) as false | { selected?: boolean }
|
|
195
210
|
let csvElement = $state() as undefined | HTMLTableElement
|
|
@@ -202,11 +217,11 @@
|
|
|
202
217
|
export async function toCSV(opts: CSVOptions = {}) {
|
|
203
218
|
csv = { selected: !!opts.selected }
|
|
204
219
|
let resolve: (value: HTMLTableElement) => void
|
|
205
|
-
const promise = new Promise<HTMLTableElement>(r => resolve = r)
|
|
220
|
+
const promise = new Promise<HTMLTableElement>((r) => (resolve = r))
|
|
206
221
|
|
|
207
222
|
const clean = $effect.root(() => {
|
|
208
223
|
$effect(() => {
|
|
209
|
-
if(csvElement) {
|
|
224
|
+
if (csvElement) {
|
|
210
225
|
resolve(csvElement)
|
|
211
226
|
}
|
|
212
227
|
})
|
|
@@ -215,86 +230,80 @@
|
|
|
215
230
|
let table = await promise
|
|
216
231
|
clean()
|
|
217
232
|
|
|
218
|
-
const separator = opts.semicolon ?
|
|
233
|
+
const separator = opts.semicolon ? ';' : ','
|
|
219
234
|
const rows = Array.from(table.rows)
|
|
220
235
|
const csvRows = []
|
|
221
236
|
|
|
222
237
|
for (const row of rows) {
|
|
223
238
|
const cells = Array.from(row.cells)
|
|
224
|
-
const csvCells = cells.map(cell => {
|
|
239
|
+
const csvCells = cells.map((cell) => {
|
|
225
240
|
let text = cell.textContent?.trim() || ''
|
|
226
241
|
|
|
227
242
|
// Escape double quotes and wrap in quotes if needed
|
|
228
|
-
if(text.includes('"')) {
|
|
243
|
+
if (text.includes('"')) {
|
|
229
244
|
text = text.replace(/"/g, '""')
|
|
230
245
|
}
|
|
231
|
-
if(text.includes(separator) || text.includes('"') || text.includes('\n')) {
|
|
246
|
+
if (text.includes(separator) || text.includes('"') || text.includes('\n')) {
|
|
232
247
|
text = `"${text}"`
|
|
233
248
|
}
|
|
234
|
-
|
|
249
|
+
|
|
235
250
|
return text
|
|
236
251
|
})
|
|
237
252
|
csvRows.push(csvCells.join(separator))
|
|
238
253
|
}
|
|
239
254
|
|
|
240
255
|
csv = false
|
|
241
|
-
return csvRows.join(
|
|
256
|
+
return csvRows.join('\n')
|
|
242
257
|
}
|
|
243
258
|
// * --- CSV --- *
|
|
244
259
|
|
|
245
|
-
|
|
246
260
|
let expandedRow = $state([]) as T[]
|
|
247
261
|
let expandTick = false
|
|
248
262
|
function toggleExpand(item: T, value?: boolean) {
|
|
249
|
-
if(expandTick) return
|
|
263
|
+
if (expandTick) return
|
|
250
264
|
expandTick = true
|
|
251
|
-
requestAnimationFrame(() => expandTick = false)
|
|
265
|
+
requestAnimationFrame(() => (expandTick = false))
|
|
252
266
|
|
|
253
267
|
let indexOf = expandedRow.indexOf(item)
|
|
254
|
-
if(value === undefined) {
|
|
268
|
+
if (value === undefined) {
|
|
255
269
|
value = indexOf === -1
|
|
256
270
|
}
|
|
257
|
-
if(!value) {
|
|
271
|
+
if (!value) {
|
|
258
272
|
expandedRow.splice(indexOf, 1)
|
|
259
273
|
return
|
|
260
274
|
}
|
|
261
|
-
if(table.expandable?.options.multiple === true) {
|
|
275
|
+
if (table.expandable?.options.multiple === true) {
|
|
262
276
|
expandedRow.push(item)
|
|
263
|
-
}
|
|
264
|
-
else {
|
|
277
|
+
} else {
|
|
265
278
|
expandedRow[0] = item
|
|
266
279
|
}
|
|
267
280
|
}
|
|
268
281
|
|
|
269
282
|
function addRowColumnEvents(
|
|
270
|
-
node: HTMLTableColElement,
|
|
283
|
+
node: HTMLTableColElement,
|
|
271
284
|
opts: ['header' | 'row' | 'statusbar', ColumnState, () => RowColumnCtx<T, any>]
|
|
272
285
|
) {
|
|
273
286
|
const [where, column, value] = opts
|
|
274
|
-
if(where !== 'row') return
|
|
275
|
-
if(column.options.onclick) {
|
|
276
|
-
$effect(() => on(node, 'click', e => column.options.onclick!(e, value())))
|
|
287
|
+
if (where !== 'row') return
|
|
288
|
+
if (column.options.onclick) {
|
|
289
|
+
$effect(() => on(node, 'click', (e) => column.options.onclick!(e, value())))
|
|
277
290
|
}
|
|
278
291
|
}
|
|
279
292
|
|
|
280
|
-
function addRowEvents(
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
) {
|
|
284
|
-
if(table.row?.events.onclick) {
|
|
285
|
-
$effect(() => on(node, 'click', e => table.row?.events.onclick!(e, ctx)))
|
|
293
|
+
function addRowEvents(node: HTMLTableRowElement, ctx: RowCtx<T>) {
|
|
294
|
+
if (table.row?.events.onclick) {
|
|
295
|
+
$effect(() => on(node, 'click', (e) => table.row?.events.onclick!(e, ctx)))
|
|
286
296
|
}
|
|
287
|
-
if(table.row?.events.oncontextmenu) {
|
|
288
|
-
$effect(() => on(node, 'contextmenu', e => table.row?.events.oncontextmenu!(e, ctx)))
|
|
297
|
+
if (table.row?.events.oncontextmenu) {
|
|
298
|
+
$effect(() => on(node, 'contextmenu', (e) => table.row?.events.oncontextmenu!(e, ctx)))
|
|
289
299
|
}
|
|
290
300
|
}
|
|
291
|
-
|
|
292
301
|
</script>
|
|
293
302
|
|
|
294
303
|
<!---------------------------------------------------->
|
|
295
304
|
|
|
296
305
|
{#if csv !== false}
|
|
297
|
-
{@const renderedColumns = columns.filter(v => v.id !== '__fixed')}
|
|
306
|
+
{@const renderedColumns = columns.filter((v) => v.id !== '__fixed')}
|
|
298
307
|
<table bind:this={csvElement} hidden>
|
|
299
308
|
<thead>
|
|
300
309
|
<tr>
|
|
@@ -304,7 +313,7 @@
|
|
|
304
313
|
</tr>
|
|
305
314
|
</thead>
|
|
306
315
|
<tbody>
|
|
307
|
-
{#each data
|
|
316
|
+
{#each table.data as row, i}
|
|
308
317
|
{#if (csv.selected && table.selected.includes(row)) || !csv.selected}
|
|
309
318
|
<tr>
|
|
310
319
|
{#each renderedColumns as column}
|
|
@@ -313,10 +322,15 @@
|
|
|
313
322
|
{@render column.snippets.row(row, {
|
|
314
323
|
index: i,
|
|
315
324
|
value: column.options.value?.(row),
|
|
316
|
-
|
|
317
|
-
|
|
325
|
+
columnHovered: false,
|
|
326
|
+
rowHovered: false,
|
|
327
|
+
itemState: {
|
|
328
|
+
index: i,
|
|
329
|
+
dragging: false,
|
|
330
|
+
positioning: false
|
|
331
|
+
} as ItemState<any>,
|
|
318
332
|
selected: false,
|
|
319
|
-
expanded: false
|
|
333
|
+
expanded: false,
|
|
320
334
|
})}
|
|
321
335
|
{:else}
|
|
322
336
|
{column.options.value?.(row)}
|
|
@@ -350,7 +364,7 @@
|
|
|
350
364
|
{/snippet}
|
|
351
365
|
|
|
352
366
|
{#snippet dragSnippet()}
|
|
353
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" style=
|
|
367
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" style="opacity: .3">
|
|
354
368
|
<path
|
|
355
369
|
fill="currentColor"
|
|
356
370
|
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"
|
|
@@ -376,14 +390,17 @@
|
|
|
376
390
|
class:fixed={true}
|
|
377
391
|
use:addRowColumnEvents={[where, column, () => args[1]]}
|
|
378
392
|
data-column={column.id}
|
|
393
|
+
class:pad={(isHeader && column.options.padHeader) || (!isHeader && column.options.padRow)}
|
|
379
394
|
class:header={isHeader}
|
|
380
395
|
class:sortable
|
|
381
|
-
use:conditional={[isHeader, (node) =>
|
|
396
|
+
use:conditional={[isHeader, (node) => table.dataState.sortAction(node, column.id)]}
|
|
397
|
+
onpointerenter={() => (hoveredColumn = column)}
|
|
398
|
+
onpointerleave={() => (hoveredColumn = null)}
|
|
382
399
|
>
|
|
383
400
|
{@render renderable(column)?.(args[0], args[1])}
|
|
384
|
-
{#if isHeader &&
|
|
401
|
+
{#if isHeader && table.dataState.sortby === column.id && sortable}
|
|
385
402
|
<span class='sorting-icon'>
|
|
386
|
-
{@render chevronSnippet(
|
|
403
|
+
{@render chevronSnippet(table.dataState.sortReverse ? 0 : 180)}
|
|
387
404
|
</span>
|
|
388
405
|
{/if}
|
|
389
406
|
</svelte:element>
|
|
@@ -401,16 +418,19 @@
|
|
|
401
418
|
use:addRowColumnEvents={[where, column, () => args[1]]}
|
|
402
419
|
use:observeColumnWidth={isHeader}
|
|
403
420
|
data-column={column.id}
|
|
421
|
+
class:pad={(isHeader && column.options.padHeader) || (!isHeader && column.options.padRow)}
|
|
404
422
|
class:header={isHeader}
|
|
405
423
|
class:resizeable={isHeader && column.options.resizeable && table.options.resizeable}
|
|
406
424
|
class:border={i == sticky.length - 1}
|
|
407
425
|
class:sortable
|
|
408
|
-
use:conditional={[isHeader, (node) =>
|
|
426
|
+
use:conditional={[isHeader, (node) => table.dataState.sortAction(node, column.id)]}
|
|
427
|
+
onpointerenter={() => (hoveredColumn = column)}
|
|
428
|
+
onpointerleave={() => (hoveredColumn = null)}
|
|
409
429
|
>
|
|
410
430
|
{@render renderable(column)?.(args[0], args[1])}
|
|
411
|
-
{#if isHeader &&
|
|
431
|
+
{#if isHeader && table.dataState.sortby === column.id && sortable}
|
|
412
432
|
<span class='sorting-icon'>
|
|
413
|
-
{@render chevronSnippet(
|
|
433
|
+
{@render chevronSnippet(table.dataState.sortReverse ? 0 : 180)}
|
|
414
434
|
</span>
|
|
415
435
|
{/if}
|
|
416
436
|
</svelte:element>
|
|
@@ -425,16 +445,19 @@
|
|
|
425
445
|
class={column.options.class ?? ''}
|
|
426
446
|
class:column={true}
|
|
427
447
|
data-column={column.id}
|
|
448
|
+
class:pad={(isHeader && column.options.padHeader) || (!isHeader && column.options.padRow)}
|
|
428
449
|
use:addRowColumnEvents={[where, column, () => args[1]]}
|
|
429
450
|
use:observeColumnWidth={isHeader}
|
|
430
451
|
class:resizeable={isHeader && column.options.resizeable && table.options.resizeable}
|
|
431
452
|
class:sortable
|
|
432
|
-
use:conditional={[isHeader, (node) =>
|
|
453
|
+
use:conditional={[isHeader, (node) => table.dataState.sortAction(node, column.id)]}
|
|
454
|
+
onpointerenter={() => (hoveredColumn = column)}
|
|
455
|
+
onpointerleave={() => (hoveredColumn = null)}
|
|
433
456
|
>
|
|
434
457
|
{@render renderable(column)?.(args[0], args[1])}
|
|
435
|
-
{#if isHeader &&
|
|
458
|
+
{#if isHeader && table.dataState.sortby === column.id && sortable}
|
|
436
459
|
<span class='sorting-icon'>
|
|
437
|
-
{@render chevronSnippet(
|
|
460
|
+
{@render chevronSnippet(table.dataState.sortReverse ? 0 : 180)}
|
|
438
461
|
</span>
|
|
439
462
|
{/if}
|
|
440
463
|
</svelte:element>
|
|
@@ -453,7 +476,7 @@
|
|
|
453
476
|
get index() {
|
|
454
477
|
return index
|
|
455
478
|
},
|
|
456
|
-
get
|
|
479
|
+
get rowHovered() {
|
|
457
480
|
return hoveredRow === item
|
|
458
481
|
},
|
|
459
482
|
get selected() {
|
|
@@ -478,20 +501,19 @@
|
|
|
478
501
|
<tr
|
|
479
502
|
aria-rowindex={index + 1}
|
|
480
503
|
style:opacity={itemState?.positioning ? 0 : 1}
|
|
481
|
-
class=
|
|
504
|
+
class="row"
|
|
482
505
|
class:dragging={itemState?.dragging}
|
|
483
506
|
class:selected={table.selected?.includes(item)}
|
|
484
507
|
class:first={index === 0}
|
|
485
508
|
class:last={index === virtualization.area.length - 1}
|
|
486
|
-
{...
|
|
487
|
-
{...(itemState?.dragging ? { 'data-svelte-tably': table.id } : {})}
|
|
509
|
+
{...itemState?.dragging ? { 'data-svelte-tably': table.id } : {}}
|
|
488
510
|
onpointerenter={() => (hoveredRow = item)}
|
|
489
511
|
onpointerleave={() => (hoveredRow = null)}
|
|
490
512
|
use:addRowEvents={ctx}
|
|
491
513
|
onclick={(e) => {
|
|
492
514
|
if (table.expandable?.options.click === true) {
|
|
493
515
|
let target = e.target as HTMLElement
|
|
494
|
-
if(['INPUT', 'TEXTAREA', 'BUTTON', 'A'].includes(target.tagName)) {
|
|
516
|
+
if (['INPUT', 'TEXTAREA', 'BUTTON', 'A'].includes(target.tagName)) {
|
|
495
517
|
return
|
|
496
518
|
}
|
|
497
519
|
ctx.expanded = !ctx.expanded
|
|
@@ -503,11 +525,17 @@
|
|
|
503
525
|
(column) => {
|
|
504
526
|
return [
|
|
505
527
|
item,
|
|
506
|
-
assignDescriptors(
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
528
|
+
assignDescriptors(
|
|
529
|
+
{
|
|
530
|
+
get value() {
|
|
531
|
+
return column.options.value ? column.options.value(item) : undefined
|
|
532
|
+
},
|
|
533
|
+
get columnHovered() {
|
|
534
|
+
return hoveredColumn === column
|
|
535
|
+
}
|
|
536
|
+
},
|
|
537
|
+
ctx
|
|
538
|
+
)
|
|
511
539
|
]
|
|
512
540
|
},
|
|
513
541
|
'row'
|
|
@@ -515,9 +543,11 @@
|
|
|
515
543
|
{#if table.row?.snippets.context}
|
|
516
544
|
{#if table.row?.snippets.contextHeader || !table.row?.options.context.hover || hoveredRow === item}
|
|
517
545
|
<td
|
|
518
|
-
class=
|
|
546
|
+
class="context-col"
|
|
519
547
|
class:hover={!table.row?.snippets.contextHeader && table.row?.options.context.hover}
|
|
520
|
-
class:hidden={table.row?.options.context.hover &&
|
|
548
|
+
class:hidden={table.row?.options.context.hover &&
|
|
549
|
+
table.row?.snippets.contextHeader &&
|
|
550
|
+
hoveredRow !== item}
|
|
521
551
|
>
|
|
522
552
|
{@render table.row?.snippets.context?.(item, ctx)}
|
|
523
553
|
</td>
|
|
@@ -525,19 +555,15 @@
|
|
|
525
555
|
{/if}
|
|
526
556
|
</tr>
|
|
527
557
|
|
|
528
|
-
{@const expandableTween = new SizeTween(
|
|
529
|
-
|
|
530
|
-
|
|
558
|
+
{@const expandableTween = new SizeTween(() => table.expandable && expandedRow.includes(item), {
|
|
559
|
+
min: 1,
|
|
560
|
+
duration: table.expandable?.options.slide.duration,
|
|
561
|
+
easing: table.expandable?.options.slide.easing
|
|
562
|
+
})}
|
|
531
563
|
{#if expandableTween.current > 0}
|
|
532
|
-
<tr class=
|
|
533
|
-
<td
|
|
534
|
-
|
|
535
|
-
style='height: {expandableTween.current}px'
|
|
536
|
-
>
|
|
537
|
-
<div
|
|
538
|
-
bind:offsetHeight={expandableTween.size}
|
|
539
|
-
style='width: {tbody.width - 3}px'
|
|
540
|
-
>
|
|
564
|
+
<tr class="expandable" style="height: {expandableTween.current}px">
|
|
565
|
+
<td colspan={columns.length} style="height: {expandableTween.current}px">
|
|
566
|
+
<div bind:offsetHeight={expandableTween.size} style="width: {tbody.width - 3}px">
|
|
541
567
|
{@render table.expandable!.snippets.content?.(item, ctx)}
|
|
542
568
|
</div>
|
|
543
569
|
</td>
|
|
@@ -547,35 +573,39 @@
|
|
|
547
573
|
|
|
548
574
|
<table
|
|
549
575
|
id={table.id}
|
|
550
|
-
class=
|
|
551
|
-
style=
|
|
552
|
-
aria-rowcount={data.
|
|
576
|
+
class="table svelte-tably"
|
|
577
|
+
style="--t: {virtualization.virtualTop}px; --b: {virtualization.virtualBottom}px;"
|
|
578
|
+
aria-rowcount={table.data.length}
|
|
553
579
|
>
|
|
554
|
-
{#if columns.some(v => v.snippets.header)}
|
|
555
|
-
<thead class=
|
|
556
|
-
<tr style=
|
|
580
|
+
{#if columns.some((v) => v.snippets.header)}
|
|
581
|
+
<thead class="headers" bind:this={elements.headers}>
|
|
582
|
+
<tr style="min-width: {tbody.width}px">
|
|
557
583
|
{@render columnsSnippet(
|
|
558
584
|
(column) => column.snippets.header,
|
|
559
|
-
() => [
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
585
|
+
() => [
|
|
586
|
+
{
|
|
587
|
+
get header() {
|
|
588
|
+
return true
|
|
589
|
+
},
|
|
590
|
+
get data() {
|
|
591
|
+
return table.data
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
],
|
|
563
595
|
'header'
|
|
564
596
|
)}
|
|
565
597
|
{#if table.row?.snippets.contextHeader}
|
|
566
|
-
<th
|
|
567
|
-
class='context-col'
|
|
568
|
-
>
|
|
598
|
+
<th class="context-col">
|
|
569
599
|
{@render table.row?.snippets.contextHeader()}
|
|
570
600
|
</th>
|
|
571
601
|
{/if}
|
|
572
602
|
</tr>
|
|
573
|
-
<tr style=
|
|
603
|
+
<tr style="width:400px;background:none;pointer-events:none;"></tr>
|
|
574
604
|
</thead>
|
|
575
605
|
{/if}
|
|
576
606
|
|
|
577
607
|
<tbody
|
|
578
|
-
class=
|
|
608
|
+
class="content"
|
|
579
609
|
use:reorderArea={{ axis: 'y', class: table.id }}
|
|
580
610
|
bind:this={virtualization.viewport.element}
|
|
581
611
|
onscrollcapture={onscroll}
|
|
@@ -588,7 +618,7 @@
|
|
|
588
618
|
return virtualization.area
|
|
589
619
|
},
|
|
590
620
|
get modify() {
|
|
591
|
-
return
|
|
621
|
+
return table.dataState.origin
|
|
592
622
|
},
|
|
593
623
|
get startIndex() {
|
|
594
624
|
return virtualization.topIndex
|
|
@@ -601,28 +631,29 @@
|
|
|
601
631
|
{/if}
|
|
602
632
|
</tbody>
|
|
603
633
|
|
|
604
|
-
{#if columns.some(v => v.snippets.statusbar)}
|
|
605
|
-
<tfoot class=
|
|
634
|
+
{#if columns.some((v) => v.snippets.statusbar)}
|
|
635
|
+
<tfoot class="statusbar" bind:this={elements.statusbar}>
|
|
606
636
|
<tr>
|
|
607
637
|
{@render columnsSnippet(
|
|
608
638
|
(column) => column.snippets.statusbar,
|
|
609
|
-
() => [
|
|
610
|
-
|
|
611
|
-
|
|
639
|
+
() => [
|
|
640
|
+
{
|
|
641
|
+
get data() {
|
|
642
|
+
return table.data
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
],
|
|
612
646
|
'statusbar'
|
|
613
647
|
)}
|
|
614
648
|
</tr>
|
|
615
|
-
<tr style=
|
|
649
|
+
<tr style="width:400px;background:none;pointer-events:none;"></tr>
|
|
616
650
|
</tfoot>
|
|
617
651
|
{/if}
|
|
618
652
|
|
|
619
|
-
<caption
|
|
620
|
-
class='panel'
|
|
621
|
-
style='width: {panelTween.current}px;'
|
|
622
|
-
>
|
|
653
|
+
<caption class="panel" style="width: {panelTween.current}px;">
|
|
623
654
|
{#if properties.panel && properties.panel in table.panels}
|
|
624
655
|
<div
|
|
625
|
-
class=
|
|
656
|
+
class="panel-content"
|
|
626
657
|
bind:offsetWidth={panelTween.size}
|
|
627
658
|
in:fly={{ x: 100, easing: sineInOut, duration: 300 }}
|
|
628
659
|
out:fly={{ x: 100, duration: 200, easing: sineInOut }}
|
|
@@ -632,24 +663,31 @@
|
|
|
632
663
|
return table
|
|
633
664
|
},
|
|
634
665
|
get data() {
|
|
635
|
-
return data
|
|
666
|
+
return table.data
|
|
636
667
|
}
|
|
637
668
|
})}
|
|
638
669
|
</div>
|
|
639
670
|
{/if}
|
|
640
671
|
</caption>
|
|
641
|
-
<caption
|
|
642
|
-
|
|
672
|
+
<caption
|
|
673
|
+
class="backdrop"
|
|
674
|
+
aria-hidden={properties.panel && table.panels[properties.panel]?.backdrop ? false : true}
|
|
675
|
+
>
|
|
676
|
+
<button
|
|
677
|
+
aria-label="Panel backdrop"
|
|
678
|
+
class="btn-backdrop"
|
|
679
|
+
tabindex="-1"
|
|
680
|
+
onclick={() => (properties.panel = undefined)}
|
|
643
681
|
></button>
|
|
644
682
|
</caption>
|
|
645
683
|
</table>
|
|
646
684
|
|
|
647
685
|
{#snippet headerSelected(ctx: HeaderSelectCtx<T>)}
|
|
648
|
-
<input type=
|
|
686
|
+
<input type="checkbox" indeterminate={ctx.indeterminate} bind:checked={ctx.isSelected} />
|
|
649
687
|
{/snippet}
|
|
650
688
|
|
|
651
689
|
{#snippet rowSelected(ctx: RowSelectCtx<T>)}
|
|
652
|
-
<input type=
|
|
690
|
+
<input type="checkbox" bind:checked={ctx.isSelected} tabindex="-1" />
|
|
653
691
|
{/snippet}
|
|
654
692
|
|
|
655
693
|
{#if table.options.select || table.options.reorderable || table.expandable}
|
|
@@ -663,29 +701,31 @@
|
|
|
663
701
|
} = typeof select === 'boolean' ? {} : select}
|
|
664
702
|
{#if show !== 'never' || reorderable || expandable?.options.chevron !== 'never'}
|
|
665
703
|
<Column
|
|
666
|
-
id=
|
|
704
|
+
id="__fixed"
|
|
667
705
|
{table}
|
|
668
706
|
fixed
|
|
669
|
-
width={Math.max(
|
|
670
|
-
|
|
671
|
-
+
|
|
672
|
-
|
|
707
|
+
width={Math.max(
|
|
708
|
+
48,
|
|
709
|
+
0 +
|
|
710
|
+
(select && show !== 'never' ? 34 : 0) +
|
|
711
|
+
(reorderable ? 34 : 0) +
|
|
712
|
+
(expandable && expandable?.options.chevron !== 'never' ? 34 : 0)
|
|
673
713
|
)}
|
|
674
714
|
resizeable={false}
|
|
675
715
|
>
|
|
676
716
|
{#snippet header()}
|
|
677
|
-
<div class=
|
|
717
|
+
<div class="__fixed">
|
|
678
718
|
{#if reorderable}
|
|
679
|
-
<span style=
|
|
719
|
+
<span style="width: 16px; display: flex; align-items: center;"></span>
|
|
680
720
|
{/if}
|
|
681
721
|
{#if select}
|
|
682
722
|
{@render headerSnippet({
|
|
683
723
|
get isSelected() {
|
|
684
|
-
return data.
|
|
724
|
+
return table.data.length === table.selected?.length && table.data.length > 0
|
|
685
725
|
},
|
|
686
726
|
set isSelected(value) {
|
|
687
727
|
if (value) {
|
|
688
|
-
table.selected = data
|
|
728
|
+
table.selected = table.data
|
|
689
729
|
} else {
|
|
690
730
|
table.selected = []
|
|
691
731
|
}
|
|
@@ -696,7 +736,7 @@
|
|
|
696
736
|
get indeterminate() {
|
|
697
737
|
return (
|
|
698
738
|
(table.selected?.length || 0) > 0 &&
|
|
699
|
-
data.
|
|
739
|
+
table.data.length !== table.selected?.length
|
|
700
740
|
)
|
|
701
741
|
}
|
|
702
742
|
})}
|
|
@@ -704,15 +744,15 @@
|
|
|
704
744
|
</div>
|
|
705
745
|
{/snippet}
|
|
706
746
|
{#snippet row(item, row)}
|
|
707
|
-
<div class=
|
|
747
|
+
<div class="__fixed">
|
|
708
748
|
{#if reorderable && row.itemState}
|
|
709
|
-
<span style=
|
|
710
|
-
{#if (row.
|
|
749
|
+
<span style="width: 16px; display: flex; align-items: center;" use:row.itemState.handle>
|
|
750
|
+
{#if (row.rowHovered && !row.itemState.area.isTarget) || row.itemState.dragging}
|
|
711
751
|
{@render dragSnippet()}
|
|
712
752
|
{/if}
|
|
713
753
|
</span>
|
|
714
754
|
{/if}
|
|
715
|
-
{#if select && (row.selected || show === 'always' || (row.
|
|
755
|
+
{#if select && (row.selected || show === 'always' || (row.rowHovered && show === 'hover') || row.expanded)}
|
|
716
756
|
{@render rowSnippet({
|
|
717
757
|
get isSelected() {
|
|
718
758
|
return row.selected
|
|
@@ -727,13 +767,13 @@
|
|
|
727
767
|
return item
|
|
728
768
|
},
|
|
729
769
|
get data() {
|
|
730
|
-
return data
|
|
770
|
+
return table.data
|
|
731
771
|
}
|
|
732
772
|
})}
|
|
733
773
|
{/if}
|
|
734
774
|
{#if expandable && expandable?.options.chevron !== 'never'}
|
|
735
|
-
<button class=
|
|
736
|
-
{#if row.expanded || expandable.options.chevron === 'always' || (row.
|
|
775
|
+
<button class="expand-row" tabindex="-1" onclick={() => (row.expanded = !row.expanded)}>
|
|
776
|
+
{#if row.expanded || expandable.options.chevron === 'always' || (row.rowHovered && expandable.options.chevron === 'hover')}
|
|
737
777
|
{@render chevronSnippet(row.expanded ? 180 : 90)}
|
|
738
778
|
{/if}
|
|
739
779
|
</button>
|
|
@@ -745,16 +785,14 @@
|
|
|
745
785
|
{/if}
|
|
746
786
|
|
|
747
787
|
{#if table.options.auto}
|
|
748
|
-
{#each Object.keys(data
|
|
788
|
+
{#each Object.keys(table.data[0] || {}) as key}
|
|
749
789
|
<Column
|
|
750
790
|
id={key}
|
|
751
|
-
value={r => r[key]}
|
|
791
|
+
value={(r) => r[key]}
|
|
752
792
|
header={capitalize(segmentize(key))}
|
|
753
|
-
sort={
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
: (a, b) => String(a).localeCompare(String(b))
|
|
757
|
-
}
|
|
793
|
+
sort={typeof table.data[0]?.[key] === 'number' ?
|
|
794
|
+
(a, b) => a - b
|
|
795
|
+
: (a, b) => String(a).localeCompare(String(b))}
|
|
758
796
|
/>
|
|
759
797
|
{/each}
|
|
760
798
|
{/if}
|
|
@@ -766,9 +804,6 @@
|
|
|
766
804
|
Row,
|
|
767
805
|
get table() {
|
|
768
806
|
return table
|
|
769
|
-
},
|
|
770
|
-
get data() {
|
|
771
|
-
return data.current
|
|
772
807
|
}
|
|
773
808
|
})}
|
|
774
809
|
|
|
@@ -822,10 +857,10 @@
|
|
|
822
857
|
position: relative;
|
|
823
858
|
overflow: visible;
|
|
824
859
|
}
|
|
825
|
-
|
|
860
|
+
|
|
826
861
|
.expandable {
|
|
827
862
|
position: relative;
|
|
828
|
-
|
|
863
|
+
|
|
829
864
|
& > td {
|
|
830
865
|
position: sticky;
|
|
831
866
|
left: 1px;
|
|
@@ -1016,7 +1051,8 @@
|
|
|
1016
1051
|
border-bottom: 1px solid var(--tably-border);
|
|
1017
1052
|
}
|
|
1018
1053
|
.headers > tr {
|
|
1019
|
-
> .column,
|
|
1054
|
+
> .column,
|
|
1055
|
+
> .context-col {
|
|
1020
1056
|
border-bottom: 1px solid var(--tably-border);
|
|
1021
1057
|
border-left: 1px solid var(--tably-border-grid);
|
|
1022
1058
|
}
|
|
@@ -1053,19 +1089,28 @@
|
|
|
1053
1089
|
|
|
1054
1090
|
& > .column {
|
|
1055
1091
|
display: flex;
|
|
1056
|
-
padding-left: var(--tably-padding-x);
|
|
1057
1092
|
overflow: hidden;
|
|
1093
|
+
|
|
1094
|
+
&:not(.pad), &.pad > :global(*:first-child) {
|
|
1095
|
+
padding-left: var(--tably-padding-x);
|
|
1096
|
+
}
|
|
1058
1097
|
}
|
|
1059
1098
|
|
|
1060
1099
|
& > *:last-child:not(.context-col) {
|
|
1061
1100
|
width: 100%;
|
|
1062
|
-
|
|
1101
|
+
|
|
1102
|
+
&:not(.pad), &.pad > :global(*:first-child) {
|
|
1103
|
+
padding-right: var(--tably-padding-x);
|
|
1104
|
+
}
|
|
1063
1105
|
}
|
|
1064
1106
|
}
|
|
1065
1107
|
|
|
1066
1108
|
.row > .column {
|
|
1067
1109
|
background-color: var(--tably-bg);
|
|
1068
|
-
|
|
1110
|
+
&:not(.pad), &.pad > :global(*:first-child) {
|
|
1111
|
+
padding-top: var(--tably-padding-y);
|
|
1112
|
+
padding-bottom: var(--tably-padding-y);
|
|
1113
|
+
}
|
|
1069
1114
|
}
|
|
1070
1115
|
|
|
1071
1116
|
:global(#runic-drag .row) {
|
|
@@ -1084,7 +1129,7 @@
|
|
|
1084
1129
|
height: 100%;
|
|
1085
1130
|
overflow: hidden;
|
|
1086
1131
|
border-left: 1px solid var(--tably-border);
|
|
1087
|
-
|
|
1132
|
+
|
|
1088
1133
|
z-index: 4;
|
|
1089
1134
|
|
|
1090
1135
|
> .panel-content {
|
|
@@ -41,7 +41,7 @@ export class Data {
|
|
|
41
41
|
if (sort === true) {
|
|
42
42
|
sort = (a, b) => String(a).localeCompare(String(b));
|
|
43
43
|
}
|
|
44
|
-
if (this
|
|
44
|
+
if (this.sortReverse) {
|
|
45
45
|
this.sorted = this.origin.toSorted((a, b) => sort(value(b), value(a)));
|
|
46
46
|
}
|
|
47
47
|
else {
|
|
@@ -6,7 +6,8 @@ import { assign, boundAssign, boundPick, pick } from '../utility.svelte.js';
|
|
|
6
6
|
export class TableState {
|
|
7
7
|
#props = {};
|
|
8
8
|
id = $state();
|
|
9
|
-
|
|
9
|
+
dataState = $state({});
|
|
10
|
+
data = $derived(this.dataState.current ?? []);
|
|
10
11
|
columns = $state({});
|
|
11
12
|
panels = $state({});
|
|
12
13
|
expandable = $state();
|
|
@@ -27,7 +28,6 @@ export class TableState {
|
|
|
27
28
|
filters: this.#props.reorderable ? false : (this.#props.filters ?? []),
|
|
28
29
|
resizeable: this.#props.resizeable ?? true,
|
|
29
30
|
reorderable: this.#props.reorderable ?? false,
|
|
30
|
-
href: this.#props.href,
|
|
31
31
|
select: this.#props.select ?? false,
|
|
32
32
|
auto: this.#props.auto ?? false
|
|
33
33
|
});
|
|
@@ -43,7 +43,7 @@ export class TableState {
|
|
|
43
43
|
this.positions.hidden = this.positions.hidden.filter((column) => column !== state);
|
|
44
44
|
};
|
|
45
45
|
if (state.defaults.sortby)
|
|
46
|
-
this.
|
|
46
|
+
this.dataState.sortBy(key);
|
|
47
47
|
if (state.options.fixed) {
|
|
48
48
|
this.positions.fixed.push(state);
|
|
49
49
|
return clean;
|
|
@@ -71,7 +71,7 @@ export class TableState {
|
|
|
71
71
|
constructor(tableProps) {
|
|
72
72
|
this.#props = tableProps;
|
|
73
73
|
this.id = tableProps.id ?? Array.from({ length: 12 }, () => String.fromCharCode(Math.floor(Math.random() * 26) + 97)).join('');
|
|
74
|
-
this.
|
|
74
|
+
this.dataState = new Data(this, tableProps);
|
|
75
75
|
setContext('svelte-tably', this);
|
|
76
76
|
}
|
|
77
77
|
}
|
|
@@ -19,7 +19,7 @@ export class Virtualization {
|
|
|
19
19
|
constructor(table) {
|
|
20
20
|
let ticked = $state(false);
|
|
21
21
|
$effect.pre(() => {
|
|
22
|
-
table.
|
|
22
|
+
table.dataState.origin;
|
|
23
23
|
untrack(() => {
|
|
24
24
|
ticked = false;
|
|
25
25
|
requestAnimationFrame(() => ticked = true);
|
|
@@ -28,7 +28,7 @@ export class Virtualization {
|
|
|
28
28
|
$effect(() => {
|
|
29
29
|
if (!ticked)
|
|
30
30
|
return;
|
|
31
|
-
table.
|
|
31
|
+
table.dataState.current;
|
|
32
32
|
untrack(() => {
|
|
33
33
|
if (!this.viewport.element) {
|
|
34
34
|
this.#heightPerItem = 8;
|
|
@@ -50,8 +50,8 @@ export class Virtualization {
|
|
|
50
50
|
return;
|
|
51
51
|
this.scrollTop;
|
|
52
52
|
this.#heightPerItem;
|
|
53
|
-
table.
|
|
54
|
-
table.
|
|
53
|
+
table.dataState.current.length;
|
|
54
|
+
table.dataState.current;
|
|
55
55
|
untrack(() => {
|
|
56
56
|
if (!waitAnimationFrame) {
|
|
57
57
|
setTimeout(() => {
|
|
@@ -59,7 +59,7 @@ export class Virtualization {
|
|
|
59
59
|
let virtualTop = Math.max(this.scrollTop - this.#spacing, 0);
|
|
60
60
|
virtualTop -= virtualTop % this.#heightPerItem;
|
|
61
61
|
this.#virtualTop = virtualTop;
|
|
62
|
-
let virtualBottom = this.#heightPerItem * table.
|
|
62
|
+
let virtualBottom = this.#heightPerItem * table.dataState.current.length - virtualTop - this.#spacing * 4;
|
|
63
63
|
virtualBottom = Math.max(virtualBottom, 0);
|
|
64
64
|
this.#virtualBottom = virtualBottom;
|
|
65
65
|
}, 1000 / 60);
|
|
@@ -70,16 +70,16 @@ export class Virtualization {
|
|
|
70
70
|
$effect(() => {
|
|
71
71
|
if (!ticked)
|
|
72
72
|
return;
|
|
73
|
-
table.
|
|
74
|
-
table.
|
|
73
|
+
table.dataState.sortReverse;
|
|
74
|
+
table.dataState.sortby;
|
|
75
75
|
this.#heightPerItem;
|
|
76
76
|
this.#virtualTop;
|
|
77
|
-
table.
|
|
78
|
-
table.
|
|
77
|
+
table.dataState.current.length;
|
|
78
|
+
table.dataState.current;
|
|
79
79
|
untrack(() => {
|
|
80
80
|
this.#topIndex = Math.round(this.#virtualTop / this.#heightPerItem || 0);
|
|
81
81
|
const end = this.#topIndex + this.#renderItemLength;
|
|
82
|
-
this.#area = table.
|
|
82
|
+
this.#area = table.dataState.current.slice(this.#topIndex, end);
|
|
83
83
|
});
|
|
84
84
|
});
|
|
85
85
|
}
|