svelte-tably 1.0.0-next.15 → 1.0.0-next.17
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 +33 -12
- 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 +205 -90
- package/dist/table/table.svelte.js +3 -1
- package/dist/utility.svelte.d.ts +4 -0
- package/dist/utility.svelte.js +24 -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
|
+
- [x] Auto: Table based on data, sortable
|
|
24
25
|
|
|
25
26
|
### Usage Notes
|
|
26
27
|
|
|
@@ -37,9 +38,12 @@ A high performant, feature rich, dynamic table
|
|
|
37
38
|
let selected = $state([]) as typeof data
|
|
38
39
|
</script>
|
|
39
40
|
|
|
41
|
+
<!-- Auto: Generate Columns for you -->
|
|
42
|
+
<Table auto {data} resizeable={false} filters=[...] />
|
|
43
|
+
|
|
40
44
|
<Table {data} panel={activePanel} select bind:selected>
|
|
41
|
-
{#snippet content({ Column, Panel, state, data })}
|
|
42
|
-
<Column id='name' sticky>
|
|
45
|
+
{#snippet content({ Column, Panel, Expandable, Row, state, data })}
|
|
46
|
+
<Column id='name' sticky sort>
|
|
43
47
|
{#snippet header()}
|
|
44
48
|
Name
|
|
45
49
|
{/snippet}
|
|
@@ -52,9 +56,10 @@ A high performant, feature rich, dynamic table
|
|
|
52
56
|
{data.length}
|
|
53
57
|
{/snippet}
|
|
54
58
|
</Column>
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
|
|
60
|
+
<!-- Simplified -->
|
|
61
|
+
<Column id='age' header='Age' value={r => r.age} sort={(a,b) => a - b} />
|
|
62
|
+
|
|
58
63
|
<!-- If you want to sort/filter a virtual value, that does not exist in the data -->
|
|
59
64
|
<Column id='virtual' value={row => row.age > 18}>
|
|
60
65
|
...
|
|
@@ -64,6 +69,21 @@ A high performant, feature rich, dynamic table
|
|
|
64
69
|
...
|
|
65
70
|
</Column>
|
|
66
71
|
|
|
72
|
+
<Expandable click={false}>
|
|
73
|
+
{#snippet content(item, ctx)}
|
|
74
|
+
...
|
|
75
|
+
{/snippet}
|
|
76
|
+
</Expandable>
|
|
77
|
+
|
|
78
|
+
<Row onclick={...} oncontextmenu={...}>
|
|
79
|
+
{#snippet contextHeader()}
|
|
80
|
+
...
|
|
81
|
+
{/snippet}
|
|
82
|
+
{#snippet context(item, ctx)}
|
|
83
|
+
...
|
|
84
|
+
{/snippet}
|
|
85
|
+
</Row>
|
|
86
|
+
|
|
67
87
|
<Panel id='columns'>
|
|
68
88
|
<!-- Anything you might like -->
|
|
69
89
|
</Panel>
|
|
@@ -80,14 +100,15 @@ For quick styling
|
|
|
80
100
|
|
|
81
101
|
| CSS Variable | Description | Default |
|
|
82
102
|
| - | - | - |
|
|
83
|
-
| --tably-bg |
|
|
84
|
-
| --tably-color | color | `hsl(0, 0%, 0%)` |
|
|
85
|
-
| --tably-border |
|
|
103
|
+
| --tably-bg | Background color | `hsl(0, 0%, 100%)` |
|
|
104
|
+
| --tably-color | Text color | `hsl(0, 0%, 0%)` |
|
|
105
|
+
| --tably-border | Border for sticky columns and header | `hsl(0, 0%, 90%)` |
|
|
106
|
+
| --tably-border-grid | Border for the table-grid | `hsl(0, 0%, 98%)` |
|
|
86
107
|
| --tably-statusbar | background-color for the statusbar | `hsl(0, 0%, 98%)` |
|
|
87
108
|
| --tably-padding-y | Padding above/below each column | `.5rem` |
|
|
88
109
|
| --tably-padding-x | Padding left of each column | `1rem` |
|
|
89
110
|
| --tably-radius | Table radius | `.25rem` |
|
|
90
111
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
112
|
+
> [!NOTE]
|
|
113
|
+
> Advanced styling can be done via `:global(.svelte-tably)`
|
|
114
|
+
> `table > thead > tr > th, table > tbody > tr > td, table > tfoot > tr > td`
|
|
@@ -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,20 +13,22 @@
|
|
|
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
|
-
import { assignDescriptors, fromProps, mounted } from '../utility.svelte.js'
|
|
24
|
+
import { assignDescriptors, capitalize, fromProps, mounted, segmentize } from '../utility.svelte.js'
|
|
25
25
|
import { conditional } from '../conditional.svelte.js'
|
|
26
26
|
import { ColumnState, type RowColumnCtx } from '../column/column.svelte.js'
|
|
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
|
}
|
|
@@ -52,7 +55,7 @@
|
|
|
52
55
|
panel: _panel = $bindable(),
|
|
53
56
|
data: _data = $bindable([]),
|
|
54
57
|
...restProps
|
|
55
|
-
}: TableProps<T> & { content
|
|
58
|
+
}: TableProps<T> & { content?: ContentSnippet } = $props()
|
|
56
59
|
|
|
57
60
|
const properties = fromProps(restProps, {
|
|
58
61
|
selected: [() => _selected, v => _selected = v],
|
|
@@ -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
|
<!---------------------------------------------------->
|
|
@@ -284,14 +309,18 @@
|
|
|
284
309
|
<tr>
|
|
285
310
|
{#each renderedColumns as column}
|
|
286
311
|
<td>
|
|
287
|
-
{
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
312
|
+
{#if column.snippets.row}
|
|
313
|
+
{@render column.snippets.row(row, {
|
|
314
|
+
index: i,
|
|
315
|
+
value: column.options.value?.(row),
|
|
316
|
+
isHovered: false,
|
|
317
|
+
itemState: { index: i, dragging: false, positioning: false } as ItemState<any>,
|
|
318
|
+
selected: false,
|
|
319
|
+
expanded: false
|
|
320
|
+
})}
|
|
321
|
+
{:else}
|
|
322
|
+
{column.options.value?.(row)}
|
|
323
|
+
{/if}
|
|
295
324
|
</td>
|
|
296
325
|
{/each}
|
|
297
326
|
</tr>
|
|
@@ -321,7 +350,7 @@
|
|
|
321
350
|
{/snippet}
|
|
322
351
|
|
|
323
352
|
{#snippet dragSnippet()}
|
|
324
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
|
353
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" style='opacity: .3'>
|
|
325
354
|
<path
|
|
326
355
|
fill="currentColor"
|
|
327
356
|
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"
|
|
@@ -413,6 +442,10 @@
|
|
|
413
442
|
{/each}
|
|
414
443
|
{/snippet}
|
|
415
444
|
|
|
445
|
+
{#snippet defaultRow(item: T, ctx: RowColumnCtx<T, any>)}
|
|
446
|
+
{ctx.value}
|
|
447
|
+
{/snippet}
|
|
448
|
+
|
|
416
449
|
{#snippet rowSnippet(item: T, itemState?: ItemState<T>)}
|
|
417
450
|
{@const index = itemState?.index ?? 0}
|
|
418
451
|
|
|
@@ -444,10 +477,8 @@
|
|
|
444
477
|
|
|
445
478
|
<tr
|
|
446
479
|
aria-rowindex={index + 1}
|
|
447
|
-
data-svelte-tably={table.id}
|
|
448
480
|
style:opacity={itemState?.positioning ? 0 : 1}
|
|
449
481
|
class='row'
|
|
450
|
-
class:hover={hoveredRow === item}
|
|
451
482
|
class:dragging={itemState?.dragging}
|
|
452
483
|
class:selected={table.selected?.includes(item)}
|
|
453
484
|
class:first={index === 0}
|
|
@@ -456,6 +487,7 @@
|
|
|
456
487
|
{...(itemState?.dragging ? { 'data-svelte-tably': table.id } : {})}
|
|
457
488
|
onpointerenter={() => (hoveredRow = item)}
|
|
458
489
|
onpointerleave={() => (hoveredRow = null)}
|
|
490
|
+
use:addRowEvents={ctx}
|
|
459
491
|
onclick={(e) => {
|
|
460
492
|
if (table.expandable?.options.click === true) {
|
|
461
493
|
let target = e.target as HTMLElement
|
|
@@ -467,7 +499,7 @@
|
|
|
467
499
|
}}
|
|
468
500
|
>
|
|
469
501
|
{@render columnsSnippet(
|
|
470
|
-
(column) => column.snippets.row,
|
|
502
|
+
(column) => column.snippets.row ?? defaultRow,
|
|
471
503
|
(column) => {
|
|
472
504
|
return [
|
|
473
505
|
item,
|
|
@@ -480,6 +512,17 @@
|
|
|
480
512
|
},
|
|
481
513
|
'row'
|
|
482
514
|
)}
|
|
515
|
+
{#if table.row?.snippets.context}
|
|
516
|
+
{#if table.row?.snippets.contextHeader || !table.row?.options.context.hover || hoveredRow === item}
|
|
517
|
+
<td
|
|
518
|
+
class='context-col'
|
|
519
|
+
class:hover={!table.row?.snippets.contextHeader && table.row?.options.context.hover}
|
|
520
|
+
class:hidden={table.row?.options.context.hover && table.row?.snippets.contextHeader && hoveredRow !== item}
|
|
521
|
+
>
|
|
522
|
+
{@render table.row?.snippets.context?.(item, ctx)}
|
|
523
|
+
</td>
|
|
524
|
+
{/if}
|
|
525
|
+
{/if}
|
|
483
526
|
</tr>
|
|
484
527
|
|
|
485
528
|
{@const expandableTween = new SizeTween(
|
|
@@ -510,20 +553,30 @@
|
|
|
510
553
|
>
|
|
511
554
|
{#if columns.some(v => v.snippets.header)}
|
|
512
555
|
<thead class='headers' bind:this={elements.headers}>
|
|
513
|
-
{
|
|
514
|
-
(
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
556
|
+
<tr style='min-width: {tbody.width}px'>
|
|
557
|
+
{@render columnsSnippet(
|
|
558
|
+
(column) => column.snippets.header,
|
|
559
|
+
() => [{
|
|
560
|
+
get header() { return true },
|
|
561
|
+
get data() { return data.current }
|
|
562
|
+
}],
|
|
563
|
+
'header'
|
|
564
|
+
)}
|
|
565
|
+
{#if table.row?.snippets.contextHeader}
|
|
566
|
+
<th
|
|
567
|
+
class='context-col'
|
|
568
|
+
>
|
|
569
|
+
{@render table.row?.snippets.contextHeader()}
|
|
570
|
+
</th>
|
|
571
|
+
{/if}
|
|
572
|
+
</tr>
|
|
573
|
+
<tr style='width:400px;background:none;pointer-events:none;'></tr>
|
|
521
574
|
</thead>
|
|
522
575
|
{/if}
|
|
523
576
|
|
|
524
577
|
<tbody
|
|
525
578
|
class='content'
|
|
526
|
-
use:reorderArea={{ axis: 'y' }}
|
|
579
|
+
use:reorderArea={{ axis: 'y', class: table.id }}
|
|
527
580
|
bind:this={virtualization.viewport.element}
|
|
528
581
|
onscrollcapture={onscroll}
|
|
529
582
|
bind:clientHeight={virtualization.viewport.height}
|
|
@@ -559,6 +612,7 @@
|
|
|
559
612
|
'statusbar'
|
|
560
613
|
)}
|
|
561
614
|
</tr>
|
|
615
|
+
<tr style='width:400px;background:none;pointer-events:none;'></tr>
|
|
562
616
|
</tfoot>
|
|
563
617
|
{/if}
|
|
564
618
|
|
|
@@ -595,7 +649,7 @@
|
|
|
595
649
|
{/snippet}
|
|
596
650
|
|
|
597
651
|
{#snippet rowSelected(ctx: RowSelectCtx<T>)}
|
|
598
|
-
<input type='checkbox' bind:checked={ctx.isSelected} />
|
|
652
|
+
<input type='checkbox' bind:checked={ctx.isSelected} tabindex='-1' />
|
|
599
653
|
{/snippet}
|
|
600
654
|
|
|
601
655
|
{#if table.options.select || table.options.reorderable || table.expandable}
|
|
@@ -612,10 +666,10 @@
|
|
|
612
666
|
id='__fixed'
|
|
613
667
|
{table}
|
|
614
668
|
fixed
|
|
615
|
-
width={Math.max(
|
|
669
|
+
width={Math.max(48, 0
|
|
616
670
|
+ (select && show !== 'never' ? 34 : 0)
|
|
617
671
|
+ (reorderable ? 34 : 0)
|
|
618
|
-
+ (expandable?.options.chevron !== 'never' ? 34 : 0)
|
|
672
|
+
+ (expandable && expandable?.options.chevron !== 'never' ? 34 : 0)
|
|
619
673
|
)}
|
|
620
674
|
resizeable={false}
|
|
621
675
|
>
|
|
@@ -677,9 +731,11 @@
|
|
|
677
731
|
}
|
|
678
732
|
})}
|
|
679
733
|
{/if}
|
|
680
|
-
{#if expandable &&
|
|
681
|
-
<button class='expand-row' onclick={() => row.expanded = !row.expanded}>
|
|
682
|
-
{
|
|
734
|
+
{#if expandable && expandable?.options.chevron !== 'never'}
|
|
735
|
+
<button class='expand-row' tabindex='-1' onclick={() => row.expanded = !row.expanded}>
|
|
736
|
+
{#if row.expanded || expandable.options.chevron === 'always' || (row.isHovered && expandable.options.chevron === 'hover')}
|
|
737
|
+
{@render chevronSnippet(row.expanded ? 180 : 90)}
|
|
738
|
+
{/if}
|
|
683
739
|
</button>
|
|
684
740
|
{/if}
|
|
685
741
|
</div>
|
|
@@ -688,10 +744,26 @@
|
|
|
688
744
|
{/if}
|
|
689
745
|
{/if}
|
|
690
746
|
|
|
747
|
+
{#if table.options.auto}
|
|
748
|
+
{#each Object.keys(data.current[0] || {}) as key}
|
|
749
|
+
<Column
|
|
750
|
+
id={key}
|
|
751
|
+
value={r => r[key]}
|
|
752
|
+
header={capitalize(segmentize(key))}
|
|
753
|
+
sort={
|
|
754
|
+
typeof data.current[0]?.[key] === 'number'
|
|
755
|
+
? (a, b) => a - b
|
|
756
|
+
: (a, b) => String(a).localeCompare(String(b))
|
|
757
|
+
}
|
|
758
|
+
/>
|
|
759
|
+
{/each}
|
|
760
|
+
{/if}
|
|
761
|
+
|
|
691
762
|
{@render content?.({
|
|
692
763
|
Column,
|
|
693
764
|
Panel,
|
|
694
765
|
Expandable,
|
|
766
|
+
Row,
|
|
695
767
|
get table() {
|
|
696
768
|
return table
|
|
697
769
|
},
|
|
@@ -708,11 +780,49 @@
|
|
|
708
780
|
background-color: inherit;
|
|
709
781
|
}
|
|
710
782
|
|
|
783
|
+
.context-col {
|
|
784
|
+
display: flex;
|
|
785
|
+
align-items: center;
|
|
786
|
+
justify-content: center;
|
|
787
|
+
position: sticky;
|
|
788
|
+
right: 0;
|
|
789
|
+
height: 100%;
|
|
790
|
+
z-index: 3;
|
|
791
|
+
padding: 0;
|
|
792
|
+
|
|
793
|
+
&.hover {
|
|
794
|
+
position: absolute;
|
|
795
|
+
}
|
|
796
|
+
&.hidden {
|
|
797
|
+
pointer-events: none;
|
|
798
|
+
user-select: none;
|
|
799
|
+
border-left: none;
|
|
800
|
+
background: none;
|
|
801
|
+
> :global(*) {
|
|
802
|
+
opacity: 0;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
:global(:root) {
|
|
808
|
+
--tably-color: hsl(0, 0%, 0%);
|
|
809
|
+
--tably-bg: hsl(0, 0%, 100%);
|
|
810
|
+
--tably-statusbar: hsl(0, 0%, 98%);
|
|
811
|
+
|
|
812
|
+
--tably-border: hsl(0, 0%, 90%);
|
|
813
|
+
--tably-border-grid: hsl(0, 0%, 98%);
|
|
814
|
+
|
|
815
|
+
--tably-padding-x: 1rem;
|
|
816
|
+
--tably-padding-y: 0.5rem;
|
|
817
|
+
|
|
818
|
+
--tably-radius: 0.25rem;
|
|
819
|
+
}
|
|
820
|
+
|
|
711
821
|
.svelte-tably {
|
|
712
822
|
position: relative;
|
|
713
823
|
overflow: visible;
|
|
714
824
|
}
|
|
715
|
-
|
|
825
|
+
|
|
716
826
|
.expandable {
|
|
717
827
|
position: relative;
|
|
718
828
|
|
|
@@ -738,7 +848,7 @@
|
|
|
738
848
|
cursor: pointer;
|
|
739
849
|
background-color: transparent;
|
|
740
850
|
color: inherit;
|
|
741
|
-
width:
|
|
851
|
+
width: 20px;
|
|
742
852
|
height: 100%;
|
|
743
853
|
|
|
744
854
|
> svg {
|
|
@@ -767,12 +877,15 @@
|
|
|
767
877
|
justify-items: end;
|
|
768
878
|
margin: 0;
|
|
769
879
|
margin-left: auto;
|
|
770
|
-
margin-right: var(--tably-padding-x, 1rem);
|
|
771
880
|
> svg {
|
|
772
881
|
transition: transform 0.15s ease;
|
|
773
882
|
}
|
|
774
883
|
}
|
|
775
884
|
|
|
885
|
+
th:not(:last-child) .sorting-icon {
|
|
886
|
+
margin-right: var(--tably-padding-x);
|
|
887
|
+
}
|
|
888
|
+
|
|
776
889
|
.__fixed {
|
|
777
890
|
display: flex;
|
|
778
891
|
align-items: center;
|
|
@@ -786,13 +899,6 @@
|
|
|
786
899
|
width: 100%;
|
|
787
900
|
}
|
|
788
901
|
|
|
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
902
|
tbody::before,
|
|
797
903
|
tbody::after,
|
|
798
904
|
selects::before,
|
|
@@ -844,17 +950,6 @@
|
|
|
844
950
|
}
|
|
845
951
|
}
|
|
846
952
|
|
|
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
953
|
.sticky {
|
|
859
954
|
position: sticky;
|
|
860
955
|
/* right: 100px; */
|
|
@@ -862,16 +957,23 @@
|
|
|
862
957
|
}
|
|
863
958
|
|
|
864
959
|
.sticky.border {
|
|
865
|
-
border-right: 1px solid var(--tably-border
|
|
960
|
+
border-right: 1px solid var(--tably-border);
|
|
866
961
|
}
|
|
867
962
|
|
|
868
|
-
.headers > .column {
|
|
869
|
-
border-right: 1px solid var(--tably-border, hsl(0, 0%, 90%));
|
|
963
|
+
.headers > tr > .column {
|
|
870
964
|
overflow: hidden;
|
|
871
|
-
padding: var(--tably-padding-y
|
|
965
|
+
padding: var(--tably-padding-y) 0;
|
|
872
966
|
cursor: default;
|
|
873
967
|
user-select: none;
|
|
874
968
|
|
|
969
|
+
&.sticky.border {
|
|
970
|
+
border-right-color: var(--tably-border);
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
&:last-child {
|
|
974
|
+
border-right: none;
|
|
975
|
+
}
|
|
976
|
+
|
|
875
977
|
&.sortable {
|
|
876
978
|
cursor: pointer;
|
|
877
979
|
}
|
|
@@ -883,32 +985,41 @@
|
|
|
883
985
|
|
|
884
986
|
.table {
|
|
885
987
|
display: grid;
|
|
886
|
-
height:
|
|
988
|
+
height: auto;
|
|
989
|
+
max-height: 100%;
|
|
887
990
|
position: relative;
|
|
888
991
|
|
|
992
|
+
color: var(--tably-color);
|
|
993
|
+
background-color: var(--tably-bg);
|
|
994
|
+
|
|
889
995
|
grid-template-areas:
|
|
890
|
-
'headers
|
|
891
|
-
'rows
|
|
892
|
-
'statusbar
|
|
996
|
+
'headers panel'
|
|
997
|
+
'rows panel'
|
|
998
|
+
'statusbar panel';
|
|
893
999
|
|
|
894
1000
|
grid-template-columns: auto min-content;
|
|
895
1001
|
grid-template-rows: auto 1fr auto;
|
|
896
1002
|
|
|
897
|
-
border: 1px solid var(--tably-border
|
|
898
|
-
border-radius: var(--tably-radius
|
|
899
|
-
|
|
900
|
-
max-height: 100%;
|
|
1003
|
+
border: 1px solid var(--tably-border);
|
|
1004
|
+
border-radius: var(--tably-radius);
|
|
901
1005
|
}
|
|
902
1006
|
|
|
903
1007
|
.headers {
|
|
1008
|
+
display: flex;
|
|
904
1009
|
grid-area: headers;
|
|
905
1010
|
z-index: 2;
|
|
906
1011
|
overflow: hidden;
|
|
907
1012
|
}
|
|
908
1013
|
|
|
909
|
-
.headers > .column {
|
|
1014
|
+
.headers > tr > .column {
|
|
910
1015
|
width: auto !important;
|
|
911
|
-
border-bottom: 1px solid var(--tably-border
|
|
1016
|
+
border-bottom: 1px solid var(--tably-border);
|
|
1017
|
+
}
|
|
1018
|
+
.headers > tr {
|
|
1019
|
+
> .column, > .context-col {
|
|
1020
|
+
border-bottom: 1px solid var(--tably-border);
|
|
1021
|
+
border-left: 1px solid var(--tably-border-grid);
|
|
1022
|
+
}
|
|
912
1023
|
}
|
|
913
1024
|
|
|
914
1025
|
.content {
|
|
@@ -918,21 +1029,21 @@
|
|
|
918
1029
|
grid-area: rows;
|
|
919
1030
|
scrollbar-width: thin;
|
|
920
1031
|
overflow: auto;
|
|
921
|
-
/* height: 100%; */
|
|
922
1032
|
}
|
|
923
1033
|
|
|
924
1034
|
.statusbar {
|
|
1035
|
+
display: flex;
|
|
925
1036
|
grid-area: statusbar;
|
|
926
1037
|
overflow: hidden;
|
|
927
|
-
background-color: var(--tably-statusbar
|
|
1038
|
+
background-color: var(--tably-statusbar);
|
|
928
1039
|
}
|
|
929
1040
|
|
|
930
1041
|
.statusbar > tr > .column {
|
|
931
|
-
border-top: 1px solid var(--tably-border
|
|
932
|
-
padding: calc(var(--tably-padding-y
|
|
1042
|
+
border-top: 1px solid var(--tably-border);
|
|
1043
|
+
padding: calc(var(--tably-padding-y) / 2) 0;
|
|
933
1044
|
}
|
|
934
1045
|
|
|
935
|
-
.headers,
|
|
1046
|
+
.headers > tr,
|
|
936
1047
|
.row,
|
|
937
1048
|
.statusbar > tr {
|
|
938
1049
|
position: relative;
|
|
@@ -942,25 +1053,29 @@
|
|
|
942
1053
|
|
|
943
1054
|
& > .column {
|
|
944
1055
|
display: flex;
|
|
945
|
-
padding-left: var(--tably-padding-x
|
|
1056
|
+
padding-left: var(--tably-padding-x);
|
|
946
1057
|
overflow: hidden;
|
|
947
1058
|
}
|
|
948
1059
|
|
|
949
|
-
& > *:last-child {
|
|
1060
|
+
& > *:last-child:not(.context-col) {
|
|
950
1061
|
width: 100%;
|
|
951
|
-
padding-right: var(--tably-padding-x
|
|
1062
|
+
padding-right: var(--tably-padding-x);
|
|
952
1063
|
}
|
|
953
1064
|
}
|
|
954
1065
|
|
|
955
|
-
.row
|
|
956
|
-
|
|
1066
|
+
.row > .column {
|
|
1067
|
+
background-color: var(--tably-bg);
|
|
1068
|
+
padding: var(--tably-padding-y) 0;
|
|
957
1069
|
}
|
|
958
|
-
|
|
959
|
-
|
|
1070
|
+
|
|
1071
|
+
:global(#runic-drag .row) {
|
|
1072
|
+
border: 1px solid var(--tably-border-grid);
|
|
1073
|
+
border-top: 2px solid var(--tably-border-grid);
|
|
960
1074
|
}
|
|
961
1075
|
|
|
962
1076
|
.row > * {
|
|
963
|
-
|
|
1077
|
+
border-left: 1px solid var(--tably-border-grid);
|
|
1078
|
+
border-bottom: 1px solid var(--tably-border-grid);
|
|
964
1079
|
}
|
|
965
1080
|
|
|
966
1081
|
.panel {
|
|
@@ -968,7 +1083,7 @@
|
|
|
968
1083
|
grid-area: panel;
|
|
969
1084
|
height: 100%;
|
|
970
1085
|
overflow: hidden;
|
|
971
|
-
border-left: 1px solid var(--tably-border
|
|
1086
|
+
border-left: 1px solid var(--tably-border);
|
|
972
1087
|
|
|
973
1088
|
z-index: 4;
|
|
974
1089
|
|
|
@@ -980,7 +1095,7 @@
|
|
|
980
1095
|
width: min-content;
|
|
981
1096
|
overflow: auto;
|
|
982
1097
|
scrollbar-width: thin;
|
|
983
|
-
padding: var(--tably-padding-y
|
|
1098
|
+
padding: var(--tably-padding-y) 0;
|
|
984
1099
|
}
|
|
985
1100
|
}
|
|
986
1101
|
</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; }
|
|
@@ -27,7 +28,8 @@ export class TableState {
|
|
|
27
28
|
resizeable: this.#props.resizeable ?? true,
|
|
28
29
|
reorderable: this.#props.reorderable ?? false,
|
|
29
30
|
href: this.#props.href,
|
|
30
|
-
select: this.#props.select ?? false
|
|
31
|
+
select: this.#props.select ?? false,
|
|
32
|
+
auto: this.#props.auto ?? false
|
|
31
33
|
});
|
|
32
34
|
add(state) {
|
|
33
35
|
if (state instanceof ColumnState) {
|
package/dist/utility.svelte.d.ts
CHANGED
|
@@ -14,4 +14,8 @@ type SetterRecord = Record<PropertyKey, [() => any, (v: any) => void]>;
|
|
|
14
14
|
export declare function withSetters<T extends SetterRecord>(obj: T): T;
|
|
15
15
|
export declare function fromProps<T extends AnyRecord, B extends SetterRecord>(props: T, boundProps?: B): Simplify<{ [K in keyof B]: ReturnType<B[K][0]>; } & { readonly [K in keyof T]: T[K]; }>;
|
|
16
16
|
export declare function assignDescriptors<T extends AnyRecord, B extends AnyRecord>(target: T, source: B): T & B;
|
|
17
|
+
/** Capitalize by space */
|
|
18
|
+
export declare function capitalize(str: string): string;
|
|
19
|
+
/** Split words when going from lower case to uppercase; `someWords-split` -> `some Word Split...` */
|
|
20
|
+
export declare function segmentize(str: string): string;
|
|
17
21
|
export {};
|
package/dist/utility.svelte.js
CHANGED
|
@@ -78,3 +78,27 @@ export function assignDescriptors(target, source) {
|
|
|
78
78
|
}
|
|
79
79
|
return target;
|
|
80
80
|
}
|
|
81
|
+
/** Capitalize by space */
|
|
82
|
+
export function capitalize(str) {
|
|
83
|
+
let parts = str.split(' ');
|
|
84
|
+
let result = '';
|
|
85
|
+
for (let part of parts) {
|
|
86
|
+
result += part.charAt(0).toUpperCase() + part.slice(1) + ' ';
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
/** Split words when going from lower case to uppercase; `someWords-split` -> `some Word Split...` */
|
|
91
|
+
export function segmentize(str) {
|
|
92
|
+
let result = '';
|
|
93
|
+
for (let i = 0; i < str.length; i++) {
|
|
94
|
+
const char = str[i];
|
|
95
|
+
const prevChar = i > 0 ? str[i - 1] : '';
|
|
96
|
+
if ((char === '-' || char === char.toUpperCase()) && prevChar !== ' ' && prevChar !== prevChar.toUpperCase()) {
|
|
97
|
+
result += ' ';
|
|
98
|
+
if (char === '-')
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
result += char;
|
|
102
|
+
}
|
|
103
|
+
return result.trim();
|
|
104
|
+
}
|