svelte-tably 1.0.0-next.14 → 1.0.0-next.16
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 +23 -6
- package/dist/column/Column.svelte +20 -3
- package/dist/column/Column.svelte.d.ts +3 -0
- package/dist/column/column.svelte.js +3 -2
- package/dist/panel/Panel.svelte +0 -24
- package/dist/panel/Panel.svelte.d.ts +0 -9
- package/dist/row/Row.svelte +24 -0
- package/dist/row/Row.svelte.d.ts +25 -0
- package/dist/row/row.svelte.js +28 -0
- package/dist/table/Table.svelte +191 -98
- package/dist/table/table.svelte.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,9 +18,10 @@ A high performant, feature rich, dynamic table
|
|
|
18
18
|
- [x] select
|
|
19
19
|
- [x] filtering
|
|
20
20
|
- [x] reorderable table
|
|
21
|
-
- [
|
|
21
|
+
- [x] Row context menus
|
|
22
22
|
- [x] Expandable rows
|
|
23
23
|
- [x] to CSV
|
|
24
|
+
- [ ] Auto: Table based on data, sortable
|
|
24
25
|
|
|
25
26
|
### Usage Notes
|
|
26
27
|
|
|
@@ -38,8 +39,8 @@ A high performant, feature rich, dynamic table
|
|
|
38
39
|
</script>
|
|
39
40
|
|
|
40
41
|
<Table {data} panel={activePanel} select bind:selected>
|
|
41
|
-
{#snippet content({ Column, Panel, state, data })}
|
|
42
|
-
<Column id='name' sticky>
|
|
42
|
+
{#snippet content({ Column, Panel, Expandable, Row, state, data })}
|
|
43
|
+
<Column id='name' sticky sort>
|
|
43
44
|
{#snippet header()}
|
|
44
45
|
Name
|
|
45
46
|
{/snippet}
|
|
@@ -52,9 +53,10 @@ A high performant, feature rich, dynamic table
|
|
|
52
53
|
{data.length}
|
|
53
54
|
{/snippet}
|
|
54
55
|
</Column>
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
|
|
57
|
+
<!-- Simplified -->
|
|
58
|
+
<Column id='age' header='Age' value={r => r.age} sort={(a,b) => a - b} />
|
|
59
|
+
|
|
58
60
|
<!-- If you want to sort/filter a virtual value, that does not exist in the data -->
|
|
59
61
|
<Column id='virtual' value={row => row.age > 18}>
|
|
60
62
|
...
|
|
@@ -64,6 +66,21 @@ A high performant, feature rich, dynamic table
|
|
|
64
66
|
...
|
|
65
67
|
</Column>
|
|
66
68
|
|
|
69
|
+
<Expandable click={false}>
|
|
70
|
+
{#snippet content(item, ctx)}
|
|
71
|
+
...
|
|
72
|
+
{/snippet}
|
|
73
|
+
</Expandable>
|
|
74
|
+
|
|
75
|
+
<Row onclick={...} oncontextmenu={...}>
|
|
76
|
+
{#snippet contextHeader()}
|
|
77
|
+
...
|
|
78
|
+
{/snippet}
|
|
79
|
+
{#snippet context(item, ctx)}
|
|
80
|
+
...
|
|
81
|
+
{/snippet}
|
|
82
|
+
</Row>
|
|
83
|
+
|
|
67
84
|
<Panel id='columns'>
|
|
68
85
|
<!-- Anything you might like -->
|
|
69
86
|
</Panel>
|
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
|
|
11
11
|
<script lang="ts">
|
|
12
12
|
|
|
13
|
-
import { fromProps } from '../utility.svelte.js'
|
|
14
|
-
import {
|
|
13
|
+
import { fromProps, type AnyRecord } from '../utility.svelte.js'
|
|
14
|
+
import type { Snippet } from 'svelte'
|
|
15
|
+
import { ColumnState, type ColumnProps, type HeaderCtx, type ColumnSnippets } from './column.svelte.js'
|
|
15
16
|
|
|
16
17
|
type T = $$Generic<Record<PropertyKey, any>>
|
|
17
18
|
type V = $$Generic
|
|
@@ -21,4 +22,20 @@
|
|
|
21
22
|
|
|
22
23
|
new ColumnState<T, V>(properties)
|
|
23
24
|
|
|
24
|
-
</script>
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<script module lang='ts'>
|
|
28
|
+
|
|
29
|
+
declare const defaultHeader: <T extends AnyRecord>(anchor: unknown, title: () => string, ctx: HeaderCtx<T>) => ReturnType<Snippet>
|
|
30
|
+
|
|
31
|
+
export function getDefaultHeader<T extends AnyRecord,V>(title: string) {
|
|
32
|
+
return (
|
|
33
|
+
(anchor: unknown, ctx: HeaderCtx<T>) => defaultHeader<T>(anchor, () => title, ctx)
|
|
34
|
+
) as Exclude<ColumnSnippets<T, V>['header'], string>
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
{#snippet defaultHeader(title: string, ctx: HeaderCtx<T>)}
|
|
40
|
+
{title}
|
|
41
|
+
{/snippet}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
export declare function getDefaultHeader<T extends AnyRecord, V>(title: string): Exclude<ColumnSnippets<T, V>["header"], string>;
|
|
2
|
+
import { type AnyRecord } from '../utility.svelte.js';
|
|
3
|
+
import { type ColumnSnippets } from './column.svelte.js';
|
|
1
4
|
declare class __sveltets_Render<T extends Record<PropertyKey, any>, V> {
|
|
2
5
|
props(): ColumnProps<T_1, V_1>;
|
|
3
6
|
events(): {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {} from 'svelte';
|
|
2
2
|
import { TableState } from '../table/table.svelte.js';
|
|
3
3
|
import { assign, pick } from '../utility.svelte.js';
|
|
4
|
+
import { getDefaultHeader } from './Column.svelte';
|
|
4
5
|
export class ColumnState {
|
|
5
6
|
#props = {};
|
|
6
7
|
id = $derived(this.#props.id);
|
|
@@ -9,11 +10,11 @@ export class ColumnState {
|
|
|
9
10
|
*/
|
|
10
11
|
table;
|
|
11
12
|
snippets = $derived({
|
|
12
|
-
header: this.#props.header,
|
|
13
|
+
header: typeof this.#props.header === 'string' ? getDefaultHeader(this.#props.header) : this.#props.header,
|
|
13
14
|
/** Title is the header-snippet, with header-ctx: `{ header: false }` */
|
|
14
15
|
title: (...args) => {
|
|
15
16
|
const getData = () => this.table.data.current;
|
|
16
|
-
return this
|
|
17
|
+
return this.snippets.header?.(...[args[0], () => ({
|
|
17
18
|
get header() { return false; },
|
|
18
19
|
get data() {
|
|
19
20
|
return getData();
|
package/dist/panel/Panel.svelte
CHANGED
|
@@ -8,30 +8,6 @@
|
|
|
8
8
|
|
|
9
9
|
-->
|
|
10
10
|
|
|
11
|
-
<script module lang='ts'>
|
|
12
|
-
|
|
13
|
-
export class PanelTween {
|
|
14
|
-
#tween = new Tween(0, { duration: 300, easing: sineInOut })
|
|
15
|
-
current = $derived(this.#tween.current)
|
|
16
|
-
transitioning = $state(false)
|
|
17
|
-
|
|
18
|
-
/** bind:clientWidth */
|
|
19
|
-
width = $state(0)
|
|
20
|
-
|
|
21
|
-
set target(value: number) {
|
|
22
|
-
this.transitioning = true
|
|
23
|
-
this.#tween.set(value).then(() => this.transitioning = false)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
constructor(cb: () => string | undefined, added = 0) {
|
|
27
|
-
$effect.pre(() => {
|
|
28
|
-
this.target = cb() ? this.width + added : 0
|
|
29
|
-
})
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
</script>
|
|
34
|
-
|
|
35
11
|
<script lang='ts' generics='T extends Record<PropertyKey, unknown>'>
|
|
36
12
|
|
|
37
13
|
import { Tween } from 'svelte/motion'
|
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
export declare class PanelTween {
|
|
2
|
-
#private;
|
|
3
|
-
current: number;
|
|
4
|
-
transitioning: boolean;
|
|
5
|
-
/** bind:clientWidth */
|
|
6
|
-
width: number;
|
|
7
|
-
set target(value: number);
|
|
8
|
-
constructor(cb: () => string | undefined, added?: number);
|
|
9
|
-
}
|
|
10
1
|
declare class __sveltets_Render<T extends Record<PropertyKey, unknown>> {
|
|
11
2
|
props(): PanelProps<T_1>;
|
|
12
3
|
events(): {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
|
|
3
|
+
This is a description, \
|
|
4
|
+
on how to use this.
|
|
5
|
+
|
|
6
|
+
@example
|
|
7
|
+
<Component />
|
|
8
|
+
|
|
9
|
+
-->
|
|
10
|
+
|
|
11
|
+
<script lang='ts'>
|
|
12
|
+
|
|
13
|
+
import { RowState, type RowProps } from './row.svelte.js'
|
|
14
|
+
import type { AnyRecord } from '../utility.svelte.js'
|
|
15
|
+
import { fromProps } from '../utility.svelte.js'
|
|
16
|
+
|
|
17
|
+
type T = $$Generic<AnyRecord>
|
|
18
|
+
|
|
19
|
+
let { ...restProps }: RowProps<T> = $props()
|
|
20
|
+
|
|
21
|
+
const properties = fromProps(restProps)
|
|
22
|
+
new RowState<T>(properties)
|
|
23
|
+
|
|
24
|
+
</script>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { AnyRecord } from '../utility.svelte.js';
|
|
2
|
+
declare class __sveltets_Render<T extends AnyRecord> {
|
|
3
|
+
props(): RowProps<T_1>;
|
|
4
|
+
events(): {};
|
|
5
|
+
slots(): {};
|
|
6
|
+
bindings(): "";
|
|
7
|
+
exports(): {};
|
|
8
|
+
}
|
|
9
|
+
interface $$IsomorphicComponent {
|
|
10
|
+
new <T extends AnyRecord>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
|
|
11
|
+
$$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
|
|
12
|
+
} & ReturnType<__sveltets_Render<T>['exports']>;
|
|
13
|
+
<T extends AnyRecord>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
|
|
14
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* This is a description, \
|
|
18
|
+
* on how to use this.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* <Component />
|
|
22
|
+
*/
|
|
23
|
+
declare const Row: $$IsomorphicComponent;
|
|
24
|
+
type Row<T extends AnyRecord> = InstanceType<typeof Row<T>>;
|
|
25
|
+
export default Row;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { TableState } from '../table/table.svelte.js';
|
|
2
|
+
export class RowState {
|
|
3
|
+
#table;
|
|
4
|
+
#props = {};
|
|
5
|
+
snippets = $derived({
|
|
6
|
+
context: this.#props.context,
|
|
7
|
+
contextHeader: this.#props.contextHeader
|
|
8
|
+
});
|
|
9
|
+
events = $derived({
|
|
10
|
+
onclick: this.#props.onclick,
|
|
11
|
+
oncontextmenu: this.#props.oncontextmenu
|
|
12
|
+
});
|
|
13
|
+
options = $derived({
|
|
14
|
+
context: {
|
|
15
|
+
hover: this.#props.contextOptions?.hover ?? true,
|
|
16
|
+
width: this.#props.contextOptions?.width ?? 'max-content'
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
constructor(props) {
|
|
20
|
+
this.#props = props;
|
|
21
|
+
this.#table = TableState.getContext();
|
|
22
|
+
if (!this.#table) {
|
|
23
|
+
throw new Error('svelte-tably: Expandable must be associated with a Table');
|
|
24
|
+
}
|
|
25
|
+
this.#table.row = this;
|
|
26
|
+
$effect(() => () => this.#table.row === this && (this.#table.row = undefined));
|
|
27
|
+
}
|
|
28
|
+
}
|
package/dist/table/Table.svelte
CHANGED
|
@@ -13,13 +13,13 @@
|
|
|
13
13
|
</script>
|
|
14
14
|
|
|
15
15
|
<script lang="ts">
|
|
16
|
-
import {
|
|
16
|
+
import { type Snippet } from 'svelte'
|
|
17
17
|
import { fly } from 'svelte/transition'
|
|
18
18
|
import { sineInOut } from 'svelte/easing'
|
|
19
19
|
import reorder, { type ItemState } from 'runic-reorder'
|
|
20
20
|
import { Virtualization } from './virtualization.svelte.js'
|
|
21
21
|
import { TableState, type HeaderSelectCtx, type RowCtx, type RowSelectCtx, type TableProps } from './table.svelte.js'
|
|
22
|
-
import Panel
|
|
22
|
+
import Panel from '../panel/Panel.svelte'
|
|
23
23
|
import Column from '../column/Column.svelte'
|
|
24
24
|
import { assignDescriptors, fromProps, mounted } from '../utility.svelte.js'
|
|
25
25
|
import { conditional } from '../conditional.svelte.js'
|
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
import Expandable from '../expandable/Expandable.svelte'
|
|
28
28
|
import { SizeTween } from '../size-tween.svelte.js'
|
|
29
29
|
import { on } from 'svelte/events'
|
|
30
|
+
import Row from '../row/Row.svelte'
|
|
31
|
+
|
|
30
32
|
|
|
31
33
|
type T = $$Generic<Record<PropertyKey, unknown>>
|
|
32
34
|
|
|
@@ -40,6 +42,7 @@
|
|
|
40
42
|
}
|
|
41
43
|
Panel: typeof Panel<T>
|
|
42
44
|
Expandable: typeof Expandable<T>
|
|
45
|
+
Row: typeof Row<T>
|
|
43
46
|
readonly table: TableState<T>
|
|
44
47
|
readonly data: T[]
|
|
45
48
|
}
|
|
@@ -95,18 +98,28 @@
|
|
|
95
98
|
/** grid-template-columns for widths */
|
|
96
99
|
const style = $derived.by(() => {
|
|
97
100
|
if (!mount.isMounted) return ''
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
#${table.id} > .content > .virtual.bottom {
|
|
103
|
-
grid-template-columns: ${columns
|
|
101
|
+
|
|
102
|
+
const context = table.row?.snippets.context ? table.row?.options.context.width : ''
|
|
103
|
+
|
|
104
|
+
const templateColumns = columns
|
|
104
105
|
.map((column, i, arr) => {
|
|
105
106
|
const width = getWidth(column.id)
|
|
106
107
|
if (i === arr.length - 1) return `minmax(${width}px, 1fr)`
|
|
107
108
|
return `${width}px`
|
|
108
109
|
})
|
|
109
|
-
.join(' ')
|
|
110
|
+
.join(' ') + context
|
|
111
|
+
|
|
112
|
+
const theadTempla3teColumns = `
|
|
113
|
+
#${table.id} > thead > tr,
|
|
114
|
+
#${table.id} > tfoot > tr {
|
|
115
|
+
grid-template-columns: ${templateColumns};
|
|
116
|
+
}
|
|
117
|
+
`
|
|
118
|
+
|
|
119
|
+
const tbodyTemplateColumns = `
|
|
120
|
+
[data-area-class='${table.id}'] tr.row,
|
|
121
|
+
#${table.id} > tbody::after {
|
|
122
|
+
grid-template-columns: ${templateColumns};
|
|
110
123
|
}
|
|
111
124
|
`
|
|
112
125
|
|
|
@@ -124,12 +137,12 @@
|
|
|
124
137
|
.join('')
|
|
125
138
|
|
|
126
139
|
const columnStyling = columns.map(column => !column.options.style ? '' : `
|
|
127
|
-
|
|
140
|
+
[data-area-class='${table.id}'] .column[data-column='${column.id}'] {
|
|
128
141
|
${column.options.style}
|
|
129
142
|
}
|
|
130
143
|
`).join('')
|
|
131
144
|
|
|
132
|
-
return
|
|
145
|
+
return theadTempla3teColumns + tbodyTemplateColumns + stickyLeft + columnStyling
|
|
133
146
|
})
|
|
134
147
|
|
|
135
148
|
function observeColumnWidth(node: HTMLDivElement, isHeader = false) {
|
|
@@ -231,6 +244,27 @@
|
|
|
231
244
|
|
|
232
245
|
|
|
233
246
|
let expandedRow = $state([]) as T[]
|
|
247
|
+
let expandTick = false
|
|
248
|
+
function toggleExpand(item: T, value?: boolean) {
|
|
249
|
+
if(expandTick) return
|
|
250
|
+
expandTick = true
|
|
251
|
+
requestAnimationFrame(() => expandTick = false)
|
|
252
|
+
|
|
253
|
+
let indexOf = expandedRow.indexOf(item)
|
|
254
|
+
if(value === undefined) {
|
|
255
|
+
value = indexOf === -1
|
|
256
|
+
}
|
|
257
|
+
if(!value) {
|
|
258
|
+
expandedRow.splice(indexOf, 1)
|
|
259
|
+
return
|
|
260
|
+
}
|
|
261
|
+
if(table.expandable?.options.multiple === true) {
|
|
262
|
+
expandedRow.push(item)
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
expandedRow[0] = item
|
|
266
|
+
}
|
|
267
|
+
}
|
|
234
268
|
|
|
235
269
|
function addRowColumnEvents(
|
|
236
270
|
node: HTMLTableColElement,
|
|
@@ -243,6 +277,18 @@
|
|
|
243
277
|
}
|
|
244
278
|
}
|
|
245
279
|
|
|
280
|
+
function addRowEvents(
|
|
281
|
+
node: HTMLTableRowElement,
|
|
282
|
+
ctx: RowCtx<T>
|
|
283
|
+
) {
|
|
284
|
+
if(table.row?.events.onclick) {
|
|
285
|
+
$effect(() => on(node, 'click', e => table.row?.events.onclick!(e, ctx)))
|
|
286
|
+
}
|
|
287
|
+
if(table.row?.events.oncontextmenu) {
|
|
288
|
+
$effect(() => on(node, 'contextmenu', e => table.row?.events.oncontextmenu!(e, ctx)))
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
246
292
|
</script>
|
|
247
293
|
|
|
248
294
|
<!---------------------------------------------------->
|
|
@@ -392,25 +438,12 @@
|
|
|
392
438
|
{/each}
|
|
393
439
|
{/snippet}
|
|
394
440
|
|
|
441
|
+
{#snippet defaultRow(item: T, ctx: RowColumnCtx<T, any>)}
|
|
442
|
+
{ctx.value}
|
|
443
|
+
{/snippet}
|
|
444
|
+
|
|
395
445
|
{#snippet rowSnippet(item: T, itemState?: ItemState<T>)}
|
|
396
446
|
{@const index = itemState?.index ?? 0}
|
|
397
|
-
{@const toggleExpand = (value?: boolean) => {
|
|
398
|
-
let indexOf = expandedRow.indexOf(item)
|
|
399
|
-
if(value !== undefined) {
|
|
400
|
-
value = indexOf === -1
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
if(!value) {
|
|
404
|
-
expandedRow.splice(indexOf, 1)
|
|
405
|
-
return
|
|
406
|
-
}
|
|
407
|
-
if(table.expandable?.options.multiple === true) {
|
|
408
|
-
expandedRow.push(item)
|
|
409
|
-
}
|
|
410
|
-
else {
|
|
411
|
-
expandedRow[0] = item
|
|
412
|
-
}
|
|
413
|
-
}}
|
|
414
447
|
|
|
415
448
|
{@const ctx: RowCtx<T> = {
|
|
416
449
|
get index() {
|
|
@@ -434,16 +467,14 @@
|
|
|
434
467
|
return expandedRow.includes(item)
|
|
435
468
|
},
|
|
436
469
|
set expanded(value) {
|
|
437
|
-
toggleExpand(value)
|
|
470
|
+
toggleExpand(item, value)
|
|
438
471
|
}
|
|
439
472
|
}}
|
|
440
473
|
|
|
441
474
|
<tr
|
|
442
475
|
aria-rowindex={index + 1}
|
|
443
|
-
data-svelte-tably={table.id}
|
|
444
476
|
style:opacity={itemState?.positioning ? 0 : 1}
|
|
445
477
|
class='row'
|
|
446
|
-
class:hover={hoveredRow === item}
|
|
447
478
|
class:dragging={itemState?.dragging}
|
|
448
479
|
class:selected={table.selected?.includes(item)}
|
|
449
480
|
class:first={index === 0}
|
|
@@ -452,18 +483,19 @@
|
|
|
452
483
|
{...(itemState?.dragging ? { 'data-svelte-tably': table.id } : {})}
|
|
453
484
|
onpointerenter={() => (hoveredRow = item)}
|
|
454
485
|
onpointerleave={() => (hoveredRow = null)}
|
|
486
|
+
use:addRowEvents={ctx}
|
|
455
487
|
onclick={(e) => {
|
|
456
488
|
if (table.expandable?.options.click === true) {
|
|
457
489
|
let target = e.target as HTMLElement
|
|
458
490
|
if(['INPUT', 'TEXTAREA', 'BUTTON', 'A'].includes(target.tagName)) {
|
|
459
491
|
return
|
|
460
492
|
}
|
|
461
|
-
|
|
493
|
+
ctx.expanded = !ctx.expanded
|
|
462
494
|
}
|
|
463
495
|
}}
|
|
464
496
|
>
|
|
465
497
|
{@render columnsSnippet(
|
|
466
|
-
(column) => column.snippets.row,
|
|
498
|
+
(column) => column.snippets.row ?? defaultRow,
|
|
467
499
|
(column) => {
|
|
468
500
|
return [
|
|
469
501
|
item,
|
|
@@ -476,6 +508,17 @@
|
|
|
476
508
|
},
|
|
477
509
|
'row'
|
|
478
510
|
)}
|
|
511
|
+
{#if table.row?.snippets.context}
|
|
512
|
+
{#if table.row?.snippets.contextHeader || !table.row?.options.context.hover || hoveredRow === item}
|
|
513
|
+
<td
|
|
514
|
+
class='context-col'
|
|
515
|
+
class:hover={!table.row?.snippets.contextHeader && table.row?.options.context.hover}
|
|
516
|
+
class:hidden={table.row?.options.context.hover && table.row?.snippets.contextHeader && hoveredRow !== item}
|
|
517
|
+
>
|
|
518
|
+
{@render table.row?.snippets.context?.(item, ctx)}
|
|
519
|
+
</td>
|
|
520
|
+
{/if}
|
|
521
|
+
{/if}
|
|
479
522
|
</tr>
|
|
480
523
|
|
|
481
524
|
{@const expandableTween = new SizeTween(
|
|
@@ -506,20 +549,30 @@
|
|
|
506
549
|
>
|
|
507
550
|
{#if columns.some(v => v.snippets.header)}
|
|
508
551
|
<thead class='headers' bind:this={elements.headers}>
|
|
509
|
-
{
|
|
510
|
-
(
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
552
|
+
<tr style='min-width: {tbody.width}px'>
|
|
553
|
+
{@render columnsSnippet(
|
|
554
|
+
(column) => column.snippets.header,
|
|
555
|
+
() => [{
|
|
556
|
+
get header() { return true },
|
|
557
|
+
get data() { return data.current }
|
|
558
|
+
}],
|
|
559
|
+
'header'
|
|
560
|
+
)}
|
|
561
|
+
{#if table.row?.snippets.contextHeader}
|
|
562
|
+
<th
|
|
563
|
+
class='context-col'
|
|
564
|
+
>
|
|
565
|
+
{@render table.row?.snippets.contextHeader()}
|
|
566
|
+
</th>
|
|
567
|
+
{/if}
|
|
568
|
+
</tr>
|
|
569
|
+
<tr style='width:400px;background:none;pointer-events:none;'></tr>
|
|
517
570
|
</thead>
|
|
518
571
|
{/if}
|
|
519
572
|
|
|
520
573
|
<tbody
|
|
521
574
|
class='content'
|
|
522
|
-
use:reorderArea={{ axis: 'y' }}
|
|
575
|
+
use:reorderArea={{ axis: 'y', class: table.id }}
|
|
523
576
|
bind:this={virtualization.viewport.element}
|
|
524
577
|
onscrollcapture={onscroll}
|
|
525
578
|
bind:clientHeight={virtualization.viewport.height}
|
|
@@ -555,6 +608,7 @@
|
|
|
555
608
|
'statusbar'
|
|
556
609
|
)}
|
|
557
610
|
</tr>
|
|
611
|
+
<tr style='width:400px;background:none;pointer-events:none;'></tr>
|
|
558
612
|
</tfoot>
|
|
559
613
|
{/if}
|
|
560
614
|
|
|
@@ -591,7 +645,7 @@
|
|
|
591
645
|
{/snippet}
|
|
592
646
|
|
|
593
647
|
{#snippet rowSelected(ctx: RowSelectCtx<T>)}
|
|
594
|
-
<input type='checkbox' bind:checked={ctx.isSelected} />
|
|
648
|
+
<input type='checkbox' bind:checked={ctx.isSelected} tabindex='-1' />
|
|
595
649
|
{/snippet}
|
|
596
650
|
|
|
597
651
|
{#if table.options.select || table.options.reorderable || table.expandable}
|
|
@@ -673,9 +727,11 @@
|
|
|
673
727
|
}
|
|
674
728
|
})}
|
|
675
729
|
{/if}
|
|
676
|
-
{#if expandable &&
|
|
677
|
-
<button class='expand-row' onclick={() => row.expanded = !row.expanded}>
|
|
678
|
-
{
|
|
730
|
+
{#if expandable && expandable?.options.chevron !== 'never'}
|
|
731
|
+
<button class='expand-row' tabindex='-1' onclick={() => row.expanded = !row.expanded}>
|
|
732
|
+
{#if row.expanded || expandable.options.chevron === 'always' || (row.isHovered && expandable.options.chevron === 'hover')}
|
|
733
|
+
{@render chevronSnippet(row.expanded ? 180 : 90)}
|
|
734
|
+
{/if}
|
|
679
735
|
</button>
|
|
680
736
|
{/if}
|
|
681
737
|
</div>
|
|
@@ -688,6 +744,7 @@
|
|
|
688
744
|
Column,
|
|
689
745
|
Panel,
|
|
690
746
|
Expandable,
|
|
747
|
+
Row,
|
|
691
748
|
get table() {
|
|
692
749
|
return table
|
|
693
750
|
},
|
|
@@ -704,11 +761,49 @@
|
|
|
704
761
|
background-color: inherit;
|
|
705
762
|
}
|
|
706
763
|
|
|
764
|
+
.context-col {
|
|
765
|
+
display: flex;
|
|
766
|
+
align-items: center;
|
|
767
|
+
justify-content: center;
|
|
768
|
+
position: sticky;
|
|
769
|
+
right: 0;
|
|
770
|
+
height: 100%;
|
|
771
|
+
z-index: 3;
|
|
772
|
+
padding: 0;
|
|
773
|
+
|
|
774
|
+
&.hover {
|
|
775
|
+
position: absolute;
|
|
776
|
+
}
|
|
777
|
+
&.hidden {
|
|
778
|
+
pointer-events: none;
|
|
779
|
+
user-select: none;
|
|
780
|
+
border-left: none;
|
|
781
|
+
background: none;
|
|
782
|
+
> :global(*) {
|
|
783
|
+
opacity: 0;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
:global(:root) {
|
|
789
|
+
--tably-color: hsl(0, 0%, 0%);
|
|
790
|
+
--tably-bg: hsl(0, 0%, 100%);
|
|
791
|
+
--tably-statusbar: hsl(0, 0%, 98%);
|
|
792
|
+
|
|
793
|
+
--tably-border: hsl(0, 0%, 90%);
|
|
794
|
+
--tably-border-grid: hsl(0, 0%, 98%);
|
|
795
|
+
|
|
796
|
+
--tably-padding-x: 1rem;
|
|
797
|
+
--tably-padding-y: 0.5rem;
|
|
798
|
+
|
|
799
|
+
--tably-radius: 0.25rem;
|
|
800
|
+
}
|
|
801
|
+
|
|
707
802
|
.svelte-tably {
|
|
708
803
|
position: relative;
|
|
709
804
|
overflow: visible;
|
|
710
805
|
}
|
|
711
|
-
|
|
806
|
+
|
|
712
807
|
.expandable {
|
|
713
808
|
position: relative;
|
|
714
809
|
|
|
@@ -734,7 +829,7 @@
|
|
|
734
829
|
cursor: pointer;
|
|
735
830
|
background-color: transparent;
|
|
736
831
|
color: inherit;
|
|
737
|
-
width:
|
|
832
|
+
width: 20px;
|
|
738
833
|
height: 100%;
|
|
739
834
|
|
|
740
835
|
> svg {
|
|
@@ -763,12 +858,15 @@
|
|
|
763
858
|
justify-items: end;
|
|
764
859
|
margin: 0;
|
|
765
860
|
margin-left: auto;
|
|
766
|
-
margin-right: var(--tably-padding-x, 1rem);
|
|
767
861
|
> svg {
|
|
768
862
|
transition: transform 0.15s ease;
|
|
769
863
|
}
|
|
770
864
|
}
|
|
771
865
|
|
|
866
|
+
th:not(:last-child) .sorting-icon {
|
|
867
|
+
margin-right: var(--tably-padding-x);
|
|
868
|
+
}
|
|
869
|
+
|
|
772
870
|
.__fixed {
|
|
773
871
|
display: flex;
|
|
774
872
|
align-items: center;
|
|
@@ -782,13 +880,6 @@
|
|
|
782
880
|
width: 100%;
|
|
783
881
|
}
|
|
784
882
|
|
|
785
|
-
.first .__fixed {
|
|
786
|
-
top: var(--tably-padding-y, 0.5rem);
|
|
787
|
-
}
|
|
788
|
-
.last .__fixed {
|
|
789
|
-
bottom: var(--tably-padding-y, 0.5rem);
|
|
790
|
-
}
|
|
791
|
-
|
|
792
883
|
tbody::before,
|
|
793
884
|
tbody::after,
|
|
794
885
|
selects::before,
|
|
@@ -840,17 +931,6 @@
|
|
|
840
931
|
}
|
|
841
932
|
}
|
|
842
933
|
|
|
843
|
-
.headers,
|
|
844
|
-
.statusbar {
|
|
845
|
-
/* So that the scrollbar doesn't cause the headers/statusbar to shift */
|
|
846
|
-
padding-right: 11px;
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
.table {
|
|
850
|
-
color: var(--tably-color, hsl(0, 0%, 0%));
|
|
851
|
-
background-color: var(--tably-bg, hsl(0, 0%, 100%));
|
|
852
|
-
}
|
|
853
|
-
|
|
854
934
|
.sticky {
|
|
855
935
|
position: sticky;
|
|
856
936
|
/* right: 100px; */
|
|
@@ -858,16 +938,23 @@
|
|
|
858
938
|
}
|
|
859
939
|
|
|
860
940
|
.sticky.border {
|
|
861
|
-
border-right: 1px solid var(--tably-border
|
|
941
|
+
border-right: 1px solid var(--tably-border);
|
|
862
942
|
}
|
|
863
943
|
|
|
864
|
-
.headers > .column {
|
|
865
|
-
border-right: 1px solid var(--tably-border, hsl(0, 0%, 90%));
|
|
944
|
+
.headers > tr > .column {
|
|
866
945
|
overflow: hidden;
|
|
867
|
-
padding: var(--tably-padding-y
|
|
946
|
+
padding: var(--tably-padding-y) 0;
|
|
868
947
|
cursor: default;
|
|
869
948
|
user-select: none;
|
|
870
949
|
|
|
950
|
+
&.sticky.border {
|
|
951
|
+
border-right-color: var(--tably-border);
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
&:last-child {
|
|
955
|
+
border-right: none;
|
|
956
|
+
}
|
|
957
|
+
|
|
871
958
|
&.sortable {
|
|
872
959
|
cursor: pointer;
|
|
873
960
|
}
|
|
@@ -879,32 +966,41 @@
|
|
|
879
966
|
|
|
880
967
|
.table {
|
|
881
968
|
display: grid;
|
|
882
|
-
height:
|
|
969
|
+
height: auto;
|
|
970
|
+
max-height: 100%;
|
|
883
971
|
position: relative;
|
|
884
972
|
|
|
973
|
+
color: var(--tably-color);
|
|
974
|
+
background-color: var(--tably-bg);
|
|
975
|
+
|
|
885
976
|
grid-template-areas:
|
|
886
|
-
'headers
|
|
887
|
-
'rows
|
|
888
|
-
'statusbar
|
|
977
|
+
'headers panel'
|
|
978
|
+
'rows panel'
|
|
979
|
+
'statusbar panel';
|
|
889
980
|
|
|
890
981
|
grid-template-columns: auto min-content;
|
|
891
982
|
grid-template-rows: auto 1fr auto;
|
|
892
983
|
|
|
893
|
-
border: 1px solid var(--tably-border
|
|
894
|
-
border-radius: var(--tably-radius
|
|
895
|
-
|
|
896
|
-
max-height: 100%;
|
|
984
|
+
border: 1px solid var(--tably-border);
|
|
985
|
+
border-radius: var(--tably-radius);
|
|
897
986
|
}
|
|
898
987
|
|
|
899
988
|
.headers {
|
|
989
|
+
display: flex;
|
|
900
990
|
grid-area: headers;
|
|
901
991
|
z-index: 2;
|
|
902
992
|
overflow: hidden;
|
|
903
993
|
}
|
|
904
994
|
|
|
905
|
-
.headers > .column {
|
|
995
|
+
.headers > tr > .column {
|
|
906
996
|
width: auto !important;
|
|
907
|
-
border-bottom: 1px solid var(--tably-border
|
|
997
|
+
border-bottom: 1px solid var(--tably-border);
|
|
998
|
+
}
|
|
999
|
+
.headers > tr {
|
|
1000
|
+
> .column, > .context-col {
|
|
1001
|
+
border-bottom: 1px solid var(--tably-border);
|
|
1002
|
+
border-left: 1px solid var(--tably-border-grid);
|
|
1003
|
+
}
|
|
908
1004
|
}
|
|
909
1005
|
|
|
910
1006
|
.content {
|
|
@@ -914,21 +1010,21 @@
|
|
|
914
1010
|
grid-area: rows;
|
|
915
1011
|
scrollbar-width: thin;
|
|
916
1012
|
overflow: auto;
|
|
917
|
-
/* height: 100%; */
|
|
918
1013
|
}
|
|
919
1014
|
|
|
920
1015
|
.statusbar {
|
|
1016
|
+
display: flex;
|
|
921
1017
|
grid-area: statusbar;
|
|
922
1018
|
overflow: hidden;
|
|
923
|
-
background-color: var(--tably-statusbar
|
|
1019
|
+
background-color: var(--tably-statusbar);
|
|
924
1020
|
}
|
|
925
1021
|
|
|
926
1022
|
.statusbar > tr > .column {
|
|
927
|
-
border-top: 1px solid var(--tably-border
|
|
928
|
-
padding: calc(var(--tably-padding-y
|
|
1023
|
+
border-top: 1px solid var(--tably-border);
|
|
1024
|
+
padding: calc(var(--tably-padding-y) / 2) 0;
|
|
929
1025
|
}
|
|
930
1026
|
|
|
931
|
-
.headers,
|
|
1027
|
+
.headers > tr,
|
|
932
1028
|
.row,
|
|
933
1029
|
.statusbar > tr {
|
|
934
1030
|
position: relative;
|
|
@@ -938,25 +1034,22 @@
|
|
|
938
1034
|
|
|
939
1035
|
& > .column {
|
|
940
1036
|
display: flex;
|
|
941
|
-
padding-left: var(--tably-padding-x
|
|
1037
|
+
padding-left: var(--tably-padding-x);
|
|
942
1038
|
overflow: hidden;
|
|
943
1039
|
}
|
|
944
1040
|
|
|
945
|
-
& > *:last-child {
|
|
1041
|
+
& > *:last-child:not(.context-col) {
|
|
946
1042
|
width: 100%;
|
|
947
|
-
padding-right: var(--tably-padding-x
|
|
1043
|
+
padding-right: var(--tably-padding-x);
|
|
948
1044
|
}
|
|
949
1045
|
}
|
|
950
1046
|
|
|
951
|
-
.row
|
|
952
|
-
padding
|
|
953
|
-
}
|
|
954
|
-
.row:last-child:not(.dragging) > * {
|
|
955
|
-
padding-bottom: calc(var(--tably-padding-y, 0.5rem) + calc(var(--tably-padding-y, 0.5rem) / 2));
|
|
1047
|
+
.row > .column {
|
|
1048
|
+
padding: var(--tably-padding-y) 0;
|
|
956
1049
|
}
|
|
957
|
-
|
|
958
1050
|
.row > * {
|
|
959
|
-
|
|
1051
|
+
border-left: 1px solid var(--tably-border-grid);
|
|
1052
|
+
border-bottom: 1px solid var(--tably-border-grid);
|
|
960
1053
|
}
|
|
961
1054
|
|
|
962
1055
|
.panel {
|
|
@@ -964,7 +1057,7 @@
|
|
|
964
1057
|
grid-area: panel;
|
|
965
1058
|
height: 100%;
|
|
966
1059
|
overflow: hidden;
|
|
967
|
-
border-left: 1px solid var(--tably-border
|
|
1060
|
+
border-left: 1px solid var(--tably-border);
|
|
968
1061
|
|
|
969
1062
|
z-index: 4;
|
|
970
1063
|
|
|
@@ -976,7 +1069,7 @@
|
|
|
976
1069
|
width: min-content;
|
|
977
1070
|
overflow: auto;
|
|
978
1071
|
scrollbar-width: thin;
|
|
979
|
-
padding: var(--tably-padding-y
|
|
1072
|
+
padding: var(--tably-padding-y) 0;
|
|
980
1073
|
}
|
|
981
1074
|
}
|
|
982
1075
|
</style>
|
|
@@ -10,6 +10,7 @@ export class TableState {
|
|
|
10
10
|
columns = $state({});
|
|
11
11
|
panels = $state({});
|
|
12
12
|
expandable = $state();
|
|
13
|
+
row = $state();
|
|
13
14
|
/** Currently selected items */
|
|
14
15
|
get selected() { return this.#props.selected ??= []; }
|
|
15
16
|
set selected(items) { this.#props.selected = items; }
|