svelte-tably 1.0.0-next.15 → 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 +168 -79
- 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) {
|
|
@@ -264,6 +277,18 @@
|
|
|
264
277
|
}
|
|
265
278
|
}
|
|
266
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
|
+
|
|
267
292
|
</script>
|
|
268
293
|
|
|
269
294
|
<!---------------------------------------------------->
|
|
@@ -413,6 +438,10 @@
|
|
|
413
438
|
{/each}
|
|
414
439
|
{/snippet}
|
|
415
440
|
|
|
441
|
+
{#snippet defaultRow(item: T, ctx: RowColumnCtx<T, any>)}
|
|
442
|
+
{ctx.value}
|
|
443
|
+
{/snippet}
|
|
444
|
+
|
|
416
445
|
{#snippet rowSnippet(item: T, itemState?: ItemState<T>)}
|
|
417
446
|
{@const index = itemState?.index ?? 0}
|
|
418
447
|
|
|
@@ -444,10 +473,8 @@
|
|
|
444
473
|
|
|
445
474
|
<tr
|
|
446
475
|
aria-rowindex={index + 1}
|
|
447
|
-
data-svelte-tably={table.id}
|
|
448
476
|
style:opacity={itemState?.positioning ? 0 : 1}
|
|
449
477
|
class='row'
|
|
450
|
-
class:hover={hoveredRow === item}
|
|
451
478
|
class:dragging={itemState?.dragging}
|
|
452
479
|
class:selected={table.selected?.includes(item)}
|
|
453
480
|
class:first={index === 0}
|
|
@@ -456,6 +483,7 @@
|
|
|
456
483
|
{...(itemState?.dragging ? { 'data-svelte-tably': table.id } : {})}
|
|
457
484
|
onpointerenter={() => (hoveredRow = item)}
|
|
458
485
|
onpointerleave={() => (hoveredRow = null)}
|
|
486
|
+
use:addRowEvents={ctx}
|
|
459
487
|
onclick={(e) => {
|
|
460
488
|
if (table.expandable?.options.click === true) {
|
|
461
489
|
let target = e.target as HTMLElement
|
|
@@ -467,7 +495,7 @@
|
|
|
467
495
|
}}
|
|
468
496
|
>
|
|
469
497
|
{@render columnsSnippet(
|
|
470
|
-
(column) => column.snippets.row,
|
|
498
|
+
(column) => column.snippets.row ?? defaultRow,
|
|
471
499
|
(column) => {
|
|
472
500
|
return [
|
|
473
501
|
item,
|
|
@@ -480,6 +508,17 @@
|
|
|
480
508
|
},
|
|
481
509
|
'row'
|
|
482
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}
|
|
483
522
|
</tr>
|
|
484
523
|
|
|
485
524
|
{@const expandableTween = new SizeTween(
|
|
@@ -510,20 +549,30 @@
|
|
|
510
549
|
>
|
|
511
550
|
{#if columns.some(v => v.snippets.header)}
|
|
512
551
|
<thead class='headers' bind:this={elements.headers}>
|
|
513
|
-
{
|
|
514
|
-
(
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
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>
|
|
521
570
|
</thead>
|
|
522
571
|
{/if}
|
|
523
572
|
|
|
524
573
|
<tbody
|
|
525
574
|
class='content'
|
|
526
|
-
use:reorderArea={{ axis: 'y' }}
|
|
575
|
+
use:reorderArea={{ axis: 'y', class: table.id }}
|
|
527
576
|
bind:this={virtualization.viewport.element}
|
|
528
577
|
onscrollcapture={onscroll}
|
|
529
578
|
bind:clientHeight={virtualization.viewport.height}
|
|
@@ -559,6 +608,7 @@
|
|
|
559
608
|
'statusbar'
|
|
560
609
|
)}
|
|
561
610
|
</tr>
|
|
611
|
+
<tr style='width:400px;background:none;pointer-events:none;'></tr>
|
|
562
612
|
</tfoot>
|
|
563
613
|
{/if}
|
|
564
614
|
|
|
@@ -595,7 +645,7 @@
|
|
|
595
645
|
{/snippet}
|
|
596
646
|
|
|
597
647
|
{#snippet rowSelected(ctx: RowSelectCtx<T>)}
|
|
598
|
-
<input type='checkbox' bind:checked={ctx.isSelected} />
|
|
648
|
+
<input type='checkbox' bind:checked={ctx.isSelected} tabindex='-1' />
|
|
599
649
|
{/snippet}
|
|
600
650
|
|
|
601
651
|
{#if table.options.select || table.options.reorderable || table.expandable}
|
|
@@ -677,9 +727,11 @@
|
|
|
677
727
|
}
|
|
678
728
|
})}
|
|
679
729
|
{/if}
|
|
680
|
-
{#if expandable &&
|
|
681
|
-
<button class='expand-row' onclick={() => row.expanded = !row.expanded}>
|
|
682
|
-
{
|
|
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}
|
|
683
735
|
</button>
|
|
684
736
|
{/if}
|
|
685
737
|
</div>
|
|
@@ -692,6 +744,7 @@
|
|
|
692
744
|
Column,
|
|
693
745
|
Panel,
|
|
694
746
|
Expandable,
|
|
747
|
+
Row,
|
|
695
748
|
get table() {
|
|
696
749
|
return table
|
|
697
750
|
},
|
|
@@ -708,11 +761,49 @@
|
|
|
708
761
|
background-color: inherit;
|
|
709
762
|
}
|
|
710
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
|
+
|
|
711
802
|
.svelte-tably {
|
|
712
803
|
position: relative;
|
|
713
804
|
overflow: visible;
|
|
714
805
|
}
|
|
715
|
-
|
|
806
|
+
|
|
716
807
|
.expandable {
|
|
717
808
|
position: relative;
|
|
718
809
|
|
|
@@ -738,7 +829,7 @@
|
|
|
738
829
|
cursor: pointer;
|
|
739
830
|
background-color: transparent;
|
|
740
831
|
color: inherit;
|
|
741
|
-
width:
|
|
832
|
+
width: 20px;
|
|
742
833
|
height: 100%;
|
|
743
834
|
|
|
744
835
|
> svg {
|
|
@@ -767,12 +858,15 @@
|
|
|
767
858
|
justify-items: end;
|
|
768
859
|
margin: 0;
|
|
769
860
|
margin-left: auto;
|
|
770
|
-
margin-right: var(--tably-padding-x, 1rem);
|
|
771
861
|
> svg {
|
|
772
862
|
transition: transform 0.15s ease;
|
|
773
863
|
}
|
|
774
864
|
}
|
|
775
865
|
|
|
866
|
+
th:not(:last-child) .sorting-icon {
|
|
867
|
+
margin-right: var(--tably-padding-x);
|
|
868
|
+
}
|
|
869
|
+
|
|
776
870
|
.__fixed {
|
|
777
871
|
display: flex;
|
|
778
872
|
align-items: center;
|
|
@@ -786,13 +880,6 @@
|
|
|
786
880
|
width: 100%;
|
|
787
881
|
}
|
|
788
882
|
|
|
789
|
-
.first .__fixed {
|
|
790
|
-
top: var(--tably-padding-y, 0.5rem);
|
|
791
|
-
}
|
|
792
|
-
.last .__fixed {
|
|
793
|
-
bottom: var(--tably-padding-y, 0.5rem);
|
|
794
|
-
}
|
|
795
|
-
|
|
796
883
|
tbody::before,
|
|
797
884
|
tbody::after,
|
|
798
885
|
selects::before,
|
|
@@ -844,17 +931,6 @@
|
|
|
844
931
|
}
|
|
845
932
|
}
|
|
846
933
|
|
|
847
|
-
.headers,
|
|
848
|
-
.statusbar {
|
|
849
|
-
/* So that the scrollbar doesn't cause the headers/statusbar to shift */
|
|
850
|
-
padding-right: 11px;
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
.table {
|
|
854
|
-
color: var(--tably-color, hsl(0, 0%, 0%));
|
|
855
|
-
background-color: var(--tably-bg, hsl(0, 0%, 100%));
|
|
856
|
-
}
|
|
857
|
-
|
|
858
934
|
.sticky {
|
|
859
935
|
position: sticky;
|
|
860
936
|
/* right: 100px; */
|
|
@@ -862,16 +938,23 @@
|
|
|
862
938
|
}
|
|
863
939
|
|
|
864
940
|
.sticky.border {
|
|
865
|
-
border-right: 1px solid var(--tably-border
|
|
941
|
+
border-right: 1px solid var(--tably-border);
|
|
866
942
|
}
|
|
867
943
|
|
|
868
|
-
.headers > .column {
|
|
869
|
-
border-right: 1px solid var(--tably-border, hsl(0, 0%, 90%));
|
|
944
|
+
.headers > tr > .column {
|
|
870
945
|
overflow: hidden;
|
|
871
|
-
padding: var(--tably-padding-y
|
|
946
|
+
padding: var(--tably-padding-y) 0;
|
|
872
947
|
cursor: default;
|
|
873
948
|
user-select: none;
|
|
874
949
|
|
|
950
|
+
&.sticky.border {
|
|
951
|
+
border-right-color: var(--tably-border);
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
&:last-child {
|
|
955
|
+
border-right: none;
|
|
956
|
+
}
|
|
957
|
+
|
|
875
958
|
&.sortable {
|
|
876
959
|
cursor: pointer;
|
|
877
960
|
}
|
|
@@ -883,32 +966,41 @@
|
|
|
883
966
|
|
|
884
967
|
.table {
|
|
885
968
|
display: grid;
|
|
886
|
-
height:
|
|
969
|
+
height: auto;
|
|
970
|
+
max-height: 100%;
|
|
887
971
|
position: relative;
|
|
888
972
|
|
|
973
|
+
color: var(--tably-color);
|
|
974
|
+
background-color: var(--tably-bg);
|
|
975
|
+
|
|
889
976
|
grid-template-areas:
|
|
890
|
-
'headers
|
|
891
|
-
'rows
|
|
892
|
-
'statusbar
|
|
977
|
+
'headers panel'
|
|
978
|
+
'rows panel'
|
|
979
|
+
'statusbar panel';
|
|
893
980
|
|
|
894
981
|
grid-template-columns: auto min-content;
|
|
895
982
|
grid-template-rows: auto 1fr auto;
|
|
896
983
|
|
|
897
|
-
border: 1px solid var(--tably-border
|
|
898
|
-
border-radius: var(--tably-radius
|
|
899
|
-
|
|
900
|
-
max-height: 100%;
|
|
984
|
+
border: 1px solid var(--tably-border);
|
|
985
|
+
border-radius: var(--tably-radius);
|
|
901
986
|
}
|
|
902
987
|
|
|
903
988
|
.headers {
|
|
989
|
+
display: flex;
|
|
904
990
|
grid-area: headers;
|
|
905
991
|
z-index: 2;
|
|
906
992
|
overflow: hidden;
|
|
907
993
|
}
|
|
908
994
|
|
|
909
|
-
.headers > .column {
|
|
995
|
+
.headers > tr > .column {
|
|
910
996
|
width: auto !important;
|
|
911
|
-
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
|
+
}
|
|
912
1004
|
}
|
|
913
1005
|
|
|
914
1006
|
.content {
|
|
@@ -918,21 +1010,21 @@
|
|
|
918
1010
|
grid-area: rows;
|
|
919
1011
|
scrollbar-width: thin;
|
|
920
1012
|
overflow: auto;
|
|
921
|
-
/* height: 100%; */
|
|
922
1013
|
}
|
|
923
1014
|
|
|
924
1015
|
.statusbar {
|
|
1016
|
+
display: flex;
|
|
925
1017
|
grid-area: statusbar;
|
|
926
1018
|
overflow: hidden;
|
|
927
|
-
background-color: var(--tably-statusbar
|
|
1019
|
+
background-color: var(--tably-statusbar);
|
|
928
1020
|
}
|
|
929
1021
|
|
|
930
1022
|
.statusbar > tr > .column {
|
|
931
|
-
border-top: 1px solid var(--tably-border
|
|
932
|
-
padding: calc(var(--tably-padding-y
|
|
1023
|
+
border-top: 1px solid var(--tably-border);
|
|
1024
|
+
padding: calc(var(--tably-padding-y) / 2) 0;
|
|
933
1025
|
}
|
|
934
1026
|
|
|
935
|
-
.headers,
|
|
1027
|
+
.headers > tr,
|
|
936
1028
|
.row,
|
|
937
1029
|
.statusbar > tr {
|
|
938
1030
|
position: relative;
|
|
@@ -942,25 +1034,22 @@
|
|
|
942
1034
|
|
|
943
1035
|
& > .column {
|
|
944
1036
|
display: flex;
|
|
945
|
-
padding-left: var(--tably-padding-x
|
|
1037
|
+
padding-left: var(--tably-padding-x);
|
|
946
1038
|
overflow: hidden;
|
|
947
1039
|
}
|
|
948
1040
|
|
|
949
|
-
& > *:last-child {
|
|
1041
|
+
& > *:last-child:not(.context-col) {
|
|
950
1042
|
width: 100%;
|
|
951
|
-
padding-right: var(--tably-padding-x
|
|
1043
|
+
padding-right: var(--tably-padding-x);
|
|
952
1044
|
}
|
|
953
1045
|
}
|
|
954
1046
|
|
|
955
|
-
.row
|
|
956
|
-
padding
|
|
1047
|
+
.row > .column {
|
|
1048
|
+
padding: var(--tably-padding-y) 0;
|
|
957
1049
|
}
|
|
958
|
-
.row:last-child:not(.dragging) > * {
|
|
959
|
-
padding-bottom: calc(var(--tably-padding-y, 0.5rem) + calc(var(--tably-padding-y, 0.5rem) / 2));
|
|
960
|
-
}
|
|
961
|
-
|
|
962
1050
|
.row > * {
|
|
963
|
-
|
|
1051
|
+
border-left: 1px solid var(--tably-border-grid);
|
|
1052
|
+
border-bottom: 1px solid var(--tably-border-grid);
|
|
964
1053
|
}
|
|
965
1054
|
|
|
966
1055
|
.panel {
|
|
@@ -968,7 +1057,7 @@
|
|
|
968
1057
|
grid-area: panel;
|
|
969
1058
|
height: 100%;
|
|
970
1059
|
overflow: hidden;
|
|
971
|
-
border-left: 1px solid var(--tably-border
|
|
1060
|
+
border-left: 1px solid var(--tably-border);
|
|
972
1061
|
|
|
973
1062
|
z-index: 4;
|
|
974
1063
|
|
|
@@ -980,7 +1069,7 @@
|
|
|
980
1069
|
width: min-content;
|
|
981
1070
|
overflow: auto;
|
|
982
1071
|
scrollbar-width: thin;
|
|
983
|
-
padding: var(--tably-padding-y
|
|
1072
|
+
padding: var(--tably-padding-y) 0;
|
|
984
1073
|
}
|
|
985
1074
|
}
|
|
986
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; }
|