svelte-tably 1.0.0-next.7 → 1.0.0-next.8
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 +3 -2
- package/dist/Column.svelte +22 -8
- package/dist/Column.svelte.d.ts +13 -8
- package/dist/Table.svelte +370 -167
- package/dist/Table.svelte.d.ts +50 -4
- package/dist/trigger.svelte.d.ts +10 -0
- package/dist/trigger.svelte.js +27 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ A high performant dynamic table
|
|
|
15
15
|
- [x] Panels
|
|
16
16
|
- [x] Virtual elements
|
|
17
17
|
- [ ] sorting
|
|
18
|
-
- [
|
|
18
|
+
- [x] select
|
|
19
19
|
- [ ] filtering
|
|
20
20
|
- [ ] orderable table
|
|
21
21
|
- [ ] row context-menu
|
|
@@ -33,9 +33,10 @@ A high performant dynamic table
|
|
|
33
33
|
])
|
|
34
34
|
|
|
35
35
|
let activePanel = $state('columns') as string | undefined
|
|
36
|
+
let selected = $state([]) as typeof data
|
|
36
37
|
</script>
|
|
37
38
|
|
|
38
|
-
<Table {data} panel={activePanel}>
|
|
39
|
+
<Table {data} panel={activePanel} select bind:selected>
|
|
39
40
|
{#snippet content({ Column, Panel, state, data })}
|
|
40
41
|
<Column id='name' sticky>
|
|
41
42
|
{#snippet header()}
|
package/dist/Column.svelte
CHANGED
|
@@ -10,6 +10,13 @@
|
|
|
10
10
|
|
|
11
11
|
<script module lang='ts'>
|
|
12
12
|
|
|
13
|
+
export type RowCtx<V> = {
|
|
14
|
+
readonly value: V
|
|
15
|
+
readonly isHovered: boolean
|
|
16
|
+
readonly index: number
|
|
17
|
+
selected: boolean
|
|
18
|
+
}
|
|
19
|
+
|
|
13
20
|
export interface Column<T = unknown, V = unknown> {
|
|
14
21
|
header?: Snippet<[
|
|
15
22
|
/**
|
|
@@ -19,12 +26,10 @@
|
|
|
19
26
|
*/
|
|
20
27
|
header?: boolean
|
|
21
28
|
]>
|
|
22
|
-
row: Snippet<[item: T, row:
|
|
23
|
-
readonly value: V
|
|
24
|
-
readonly isHovered: boolean
|
|
25
|
-
readonly index: number
|
|
26
|
-
}]>
|
|
29
|
+
row: Snippet<[item: T, row: RowCtx<V>]>
|
|
27
30
|
statusbar?: Snippet
|
|
31
|
+
|
|
32
|
+
fixed?: boolean
|
|
28
33
|
|
|
29
34
|
/** Default options for initial table */
|
|
30
35
|
defaults: {
|
|
@@ -46,7 +51,7 @@
|
|
|
46
51
|
<script lang='ts' generics='T extends Record<PropertyKey, any>, V = unknown'>
|
|
47
52
|
|
|
48
53
|
import { onDestroy, type Snippet } from 'svelte'
|
|
49
|
-
import { getTableState } from './Table.svelte'
|
|
54
|
+
import { getTableState, type TableState } from './Table.svelte'
|
|
50
55
|
|
|
51
56
|
interface Props {
|
|
52
57
|
header?: Column<T, V>['header']
|
|
@@ -57,6 +62,8 @@
|
|
|
57
62
|
|
|
58
63
|
// options
|
|
59
64
|
sticky?: boolean
|
|
65
|
+
/** Fixed is like sticky, but in its own category — meant to not be moved/hidden ex. select-boxes */
|
|
66
|
+
fixed?: boolean
|
|
60
67
|
sort?: boolean
|
|
61
68
|
show?: boolean
|
|
62
69
|
width?: number
|
|
@@ -64,24 +71,31 @@
|
|
|
64
71
|
sorting?: Column<T, V>['options']['sorting']
|
|
65
72
|
/** @default true */
|
|
66
73
|
resizeable?: boolean
|
|
74
|
+
|
|
75
|
+
/** Optional: Provide the table it is a part of */
|
|
76
|
+
table?: TableState
|
|
67
77
|
}
|
|
68
78
|
|
|
69
79
|
let {
|
|
70
80
|
header, row, statusbar, id,
|
|
71
81
|
|
|
72
82
|
sticky = false,
|
|
83
|
+
fixed = false,
|
|
73
84
|
sort = false,
|
|
74
85
|
show = true,
|
|
75
86
|
width,
|
|
76
87
|
|
|
77
88
|
resizeable = true,
|
|
78
|
-
value, sorting
|
|
89
|
+
value, sorting,
|
|
90
|
+
|
|
91
|
+
table
|
|
79
92
|
}: Props = $props()
|
|
80
93
|
|
|
81
94
|
const column: Column<T, V> = $state({
|
|
82
95
|
header,
|
|
83
96
|
row,
|
|
84
97
|
statusbar,
|
|
98
|
+
fixed,
|
|
85
99
|
defaults: {
|
|
86
100
|
sticky,
|
|
87
101
|
sort,
|
|
@@ -95,7 +109,7 @@
|
|
|
95
109
|
}
|
|
96
110
|
})
|
|
97
111
|
|
|
98
|
-
|
|
112
|
+
table ??= getTableState()
|
|
99
113
|
table.addColumn(id, column as Column)
|
|
100
114
|
|
|
101
115
|
onDestroy(() => {
|
package/dist/Column.svelte.d.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
export type RowCtx<V> = {
|
|
2
|
+
readonly value: V;
|
|
3
|
+
readonly isHovered: boolean;
|
|
4
|
+
readonly index: number;
|
|
5
|
+
selected: boolean;
|
|
6
|
+
};
|
|
1
7
|
export interface Column<T = unknown, V = unknown> {
|
|
2
8
|
header?: Snippet<[
|
|
3
9
|
/**
|
|
@@ -7,15 +13,9 @@ export interface Column<T = unknown, V = unknown> {
|
|
|
7
13
|
*/
|
|
8
14
|
header?: boolean
|
|
9
15
|
]>;
|
|
10
|
-
row: Snippet<[
|
|
11
|
-
item: T,
|
|
12
|
-
row: {
|
|
13
|
-
readonly value: V;
|
|
14
|
-
readonly isHovered: boolean;
|
|
15
|
-
readonly index: number;
|
|
16
|
-
}
|
|
17
|
-
]>;
|
|
16
|
+
row: Snippet<[item: T, row: RowCtx<V>]>;
|
|
18
17
|
statusbar?: Snippet;
|
|
18
|
+
fixed?: boolean;
|
|
19
19
|
/** Default options for initial table */
|
|
20
20
|
defaults: {
|
|
21
21
|
sticky?: boolean;
|
|
@@ -31,6 +31,7 @@ export interface Column<T = unknown, V = unknown> {
|
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
33
|
import { type Snippet } from 'svelte';
|
|
34
|
+
import { type TableState } from './Table.svelte';
|
|
34
35
|
declare class __sveltets_Render<T extends Record<PropertyKey, any>, V = unknown> {
|
|
35
36
|
props(): {
|
|
36
37
|
header?: Column<T_1, V_1>["header"];
|
|
@@ -38,6 +39,8 @@ declare class __sveltets_Render<T extends Record<PropertyKey, any>, V = unknown>
|
|
|
38
39
|
statusbar?: Column<T_1, V_1>["statusbar"];
|
|
39
40
|
id: string;
|
|
40
41
|
sticky?: boolean;
|
|
42
|
+
/** Fixed is like sticky, but in its own category — meant to not be moved/hidden ex. select-boxes */
|
|
43
|
+
fixed?: boolean;
|
|
41
44
|
sort?: boolean;
|
|
42
45
|
show?: boolean;
|
|
43
46
|
width?: number;
|
|
@@ -45,6 +48,8 @@ declare class __sveltets_Render<T extends Record<PropertyKey, any>, V = unknown>
|
|
|
45
48
|
sorting?: Column<T_1, V_1>["options"]["sorting"];
|
|
46
49
|
/** @default true */
|
|
47
50
|
resizeable?: boolean;
|
|
51
|
+
/** Optional: Provide the table it is a part of */
|
|
52
|
+
table?: TableState;
|
|
48
53
|
};
|
|
49
54
|
events(): {};
|
|
50
55
|
slots(): {};
|
package/dist/Table.svelte
CHANGED
|
@@ -33,18 +33,35 @@
|
|
|
33
33
|
return getContext<TableState<T>>('svelte5-table')
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
export type HeaderSelectCtx<T = any> = {
|
|
37
|
+
isSelected: boolean,
|
|
38
|
+
/** The list of selected items */
|
|
39
|
+
readonly selected: T[]
|
|
40
|
+
/**
|
|
41
|
+
* See [MDN :indeterminate](https://developer.mozilla.org/en-US/docs/Web/CSS/:indeterminate)
|
|
42
|
+
*/
|
|
43
|
+
readonly indeterminate: boolean
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type RowSelectCtx<T = any> = {
|
|
47
|
+
readonly item: T
|
|
48
|
+
readonly row: RowCtx<unknown>
|
|
49
|
+
data: T[]
|
|
50
|
+
isSelected: boolean
|
|
51
|
+
}
|
|
52
|
+
|
|
36
53
|
</script>
|
|
37
54
|
|
|
38
55
|
<script lang='ts' generics='T extends Record<PropertyKey, unknown>'>
|
|
39
56
|
|
|
40
|
-
import { getContext, setContext, untrack, type Snippet } from 'svelte'
|
|
41
|
-
import Column, { type Column as TColumn } from './Column.svelte'
|
|
57
|
+
import { getContext, onMount, setContext, tick, untrack, type Snippet } from 'svelte'
|
|
58
|
+
import Column, { type RowCtx, type Column as TColumn } from './Column.svelte'
|
|
42
59
|
import Panel, { PanelTween, type Panel as TPanel } from './Panel.svelte'
|
|
43
60
|
import { fly } from 'svelte/transition'
|
|
44
61
|
import { sineInOut } from 'svelte/easing'
|
|
45
|
-
import { get } from 'svelte/store'
|
|
46
|
-
|
|
47
|
-
|
|
62
|
+
import type { get } from 'svelte/store'
|
|
63
|
+
|
|
64
|
+
|
|
48
65
|
|
|
49
66
|
interface Props {
|
|
50
67
|
content: Snippet<[context: { Column: typeof Column<T>, Panel: typeof Panel, readonly table: TableState<T>, readonly data: T[] }]>
|
|
@@ -58,77 +75,148 @@
|
|
|
58
75
|
* @default true
|
|
59
76
|
*/
|
|
60
77
|
resizeable?: boolean
|
|
61
|
-
|
|
78
|
+
|
|
79
|
+
selected?: T[]
|
|
80
|
+
select?: boolean | {
|
|
81
|
+
/**
|
|
82
|
+
* The style, in which the selection is shown
|
|
83
|
+
*
|
|
84
|
+
* NOTE: If using `edge` | 'side', "show" will always be `hover`. This is due to
|
|
85
|
+
* an inconsistency/limitation of matching the scroll between the selection div and the rows.
|
|
86
|
+
*
|
|
87
|
+
* @default 'column'
|
|
88
|
+
*/
|
|
89
|
+
style?: 'column'
|
|
90
|
+
/**
|
|
91
|
+
* When to show the row-select, when not selected?
|
|
92
|
+
* @default 'hover'
|
|
93
|
+
*/
|
|
94
|
+
show?: 'hover' | 'always' | 'never'
|
|
95
|
+
/**
|
|
96
|
+
* Custom snippet
|
|
97
|
+
*/
|
|
98
|
+
headerSnippet?: Snippet<[context: HeaderSelectCtx]>
|
|
99
|
+
rowSnippet?: Snippet<[context: RowSelectCtx<T>]>
|
|
100
|
+
}
|
|
101
|
+
// | {
|
|
102
|
+
// /**
|
|
103
|
+
// * The style, in which the selection is shown
|
|
104
|
+
// *
|
|
105
|
+
// * NOTE: If using `edge` | 'side', "show" will always be `hover`. This is due to
|
|
106
|
+
// * an inconsistency/limitation of matching the scroll between the selection div and the rows.
|
|
107
|
+
// *
|
|
108
|
+
// * @default 'column'
|
|
109
|
+
// */
|
|
110
|
+
// style?: 'edge' | 'side'
|
|
111
|
+
// /**
|
|
112
|
+
// * When to show the row-select, when not selected?
|
|
113
|
+
// * @default 'hover'
|
|
114
|
+
// */
|
|
115
|
+
// show?: 'hover'
|
|
116
|
+
// /**
|
|
117
|
+
// * Custom snippet
|
|
118
|
+
// */
|
|
119
|
+
// snippet?: Snippet<[context: { item: T, data: T[], selected: boolean }]>
|
|
120
|
+
// }
|
|
121
|
+
|
|
122
|
+
/*
|
|
123
|
+
ordered?: {
|
|
124
|
+
style?: 'column' | 'side' // combine with select if both use 'column'
|
|
125
|
+
show?: 'hover' | 'always'
|
|
126
|
+
// snippet?: Snippet<[context: { item: T, data: T[], selected: boolean }]>
|
|
127
|
+
}
|
|
128
|
+
*/
|
|
62
129
|
}
|
|
63
130
|
|
|
64
131
|
let {
|
|
65
132
|
content,
|
|
66
|
-
|
|
133
|
+
selected = $bindable([]),
|
|
67
134
|
panel = $bindable(),
|
|
68
135
|
data: _data = [],
|
|
69
136
|
id = Array.from({length: 12}, () => String.fromCharCode(Math.floor(Math.random() * 26) + 97)).join(''),
|
|
70
137
|
href,
|
|
71
138
|
resizeable = true,
|
|
72
|
-
|
|
139
|
+
select
|
|
73
140
|
}: Props = $props()
|
|
74
141
|
|
|
142
|
+
let mounted = $state(false)
|
|
143
|
+
onMount(() => mounted = true)
|
|
144
|
+
|
|
75
145
|
const data = $derived([..._data])
|
|
76
146
|
|
|
77
|
-
const elements = $state({}) as Record<'headers' | 'statusbar' | 'rows', HTMLElement>
|
|
147
|
+
const elements = $state({}) as Record<'headers' | 'statusbar' | 'rows' | 'virtualTop' | 'virtualBottom' | 'selects', HTMLElement>
|
|
78
148
|
|
|
79
149
|
|
|
80
150
|
// * --- Virtualization --- *
|
|
81
151
|
let scrollTop = $state(0)
|
|
82
152
|
let viewportHeight = $state(0)
|
|
83
|
-
|
|
84
|
-
let heightPerItem = $
|
|
85
|
-
data
|
|
86
|
-
if(!elements.rows)
|
|
87
|
-
return 8
|
|
88
|
-
const result = elements.rows.scrollHeight / elements.rows.childNodes.length
|
|
89
|
-
return result
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
let renderItemLength = $derived(Math.ceil(Math.max(30, (viewportHeight / heightPerItem) * 2)))
|
|
153
|
+
|
|
154
|
+
let heightPerItem = $state(8)
|
|
93
155
|
|
|
94
156
|
const spacing = () => viewportHeight / 2
|
|
157
|
+
|
|
95
158
|
let virtualTop = $derived.by(() => {
|
|
96
|
-
let
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
return virtualTop
|
|
159
|
+
let result = Math.max(scrollTop - spacing(), 0)
|
|
160
|
+
result -= result % heightPerItem
|
|
161
|
+
return result
|
|
100
162
|
})
|
|
101
163
|
let virtualBottom = $derived.by(() => {
|
|
102
|
-
|
|
103
|
-
|
|
164
|
+
let result = (heightPerItem * data.length) - virtualTop - spacing() * 4
|
|
165
|
+
result = Math.max(result, 0)
|
|
166
|
+
return result
|
|
104
167
|
})
|
|
105
|
-
|
|
106
|
-
|
|
168
|
+
|
|
169
|
+
let renderItemLength = $derived(Math.ceil(Math.max(30, (viewportHeight / heightPerItem) * 2)))
|
|
170
|
+
|
|
171
|
+
/** The area of data being rendered */
|
|
172
|
+
let area = $derived.by(() => {
|
|
107
173
|
const index = (virtualTop / heightPerItem) || 0
|
|
108
|
-
const end = index +
|
|
109
|
-
|
|
174
|
+
const end = index + renderItemLength
|
|
175
|
+
const result = data.slice(
|
|
110
176
|
index,
|
|
111
177
|
end
|
|
112
178
|
)
|
|
179
|
+
return result
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
function calculateHeightPerItem() {
|
|
183
|
+
if(!elements.rows) {
|
|
184
|
+
heightPerItem = 8
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
tick().then(() => {
|
|
188
|
+
const firstRow = elements.rows.children[0].getBoundingClientRect().top
|
|
189
|
+
const lastRow = elements.rows.children[elements.rows.children.length - 1].getBoundingClientRect().bottom
|
|
190
|
+
heightPerItem = (lastRow - firstRow) / area.length
|
|
191
|
+
})
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
$effect(() => {
|
|
195
|
+
data
|
|
196
|
+
untrack(calculateHeightPerItem)
|
|
113
197
|
})
|
|
114
198
|
// * --- Virtualization --- *
|
|
115
199
|
|
|
200
|
+
let cols: TableState<T>['columns'] = $state({})
|
|
201
|
+
let positions: TableState<T>['positions'] = $state({
|
|
202
|
+
fixed: [],
|
|
203
|
+
sticky: [],
|
|
204
|
+
scroll: [],
|
|
205
|
+
hidden: [],
|
|
206
|
+
toggle(key) {
|
|
207
|
+
if(table.positions.hidden.includes(key))
|
|
208
|
+
table.positions.hidden = table.positions.hidden.filter(column => column !== key)
|
|
209
|
+
else
|
|
210
|
+
table.positions.hidden.push(key)
|
|
211
|
+
}
|
|
212
|
+
})
|
|
213
|
+
|
|
116
214
|
|
|
117
215
|
const table: TableState<T> = $state({
|
|
118
|
-
columns:
|
|
119
|
-
selected
|
|
216
|
+
columns: cols,
|
|
217
|
+
selected,
|
|
120
218
|
panels: {},
|
|
121
|
-
positions
|
|
122
|
-
sticky: [],
|
|
123
|
-
scroll: [],
|
|
124
|
-
hidden: [],
|
|
125
|
-
toggle(key) {
|
|
126
|
-
if(table.positions.hidden.includes(key))
|
|
127
|
-
table.positions.hidden = table.positions.hidden.filter(column => column !== key)
|
|
128
|
-
else
|
|
129
|
-
table.positions.hidden.push(key)
|
|
130
|
-
}
|
|
131
|
-
},
|
|
219
|
+
positions,
|
|
132
220
|
get href() {
|
|
133
221
|
return href
|
|
134
222
|
},
|
|
@@ -144,6 +232,12 @@
|
|
|
144
232
|
if(column.defaults.sort)
|
|
145
233
|
table.sortby = key
|
|
146
234
|
|
|
235
|
+
if(column.fixed) {
|
|
236
|
+
// @ts-expect-error
|
|
237
|
+
table.positions.fixed.push(key)
|
|
238
|
+
return
|
|
239
|
+
}
|
|
240
|
+
|
|
147
241
|
if(!column.defaults.show)
|
|
148
242
|
table.positions.hidden.push(key)
|
|
149
243
|
|
|
@@ -154,6 +248,8 @@
|
|
|
154
248
|
},
|
|
155
249
|
removeColumn(key) {
|
|
156
250
|
delete table.columns[key]
|
|
251
|
+
// @ts-expect-error fixed is not typed
|
|
252
|
+
table.positions.fixed = table.positions.fixed.filter(column => column !== key)
|
|
157
253
|
table.positions.sticky = table.positions.sticky.filter(column => column !== key)
|
|
158
254
|
table.positions.scroll = table.positions.scroll.filter(column => column !== key)
|
|
159
255
|
table.positions.hidden = table.positions.hidden.filter(column => column !== key)
|
|
@@ -169,10 +265,15 @@
|
|
|
169
265
|
let hoveredRow: T | null = $state(null)
|
|
170
266
|
|
|
171
267
|
/** Order of columns */
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
268
|
+
const fixed = $derived(
|
|
269
|
+
// @ts-expect-error
|
|
270
|
+
positions.fixed
|
|
271
|
+
) as string[]
|
|
272
|
+
const hidden = $derived(positions.hidden)
|
|
273
|
+
const notHidden = (key: string) => !positions.hidden.includes(key)
|
|
274
|
+
const sticky = $derived(positions.sticky.filter(notHidden))
|
|
275
|
+
const scrolled = $derived(positions.scroll.filter(notHidden))
|
|
276
|
+
const columns = $derived([ ...fixed, ...sticky, ...scrolled ])
|
|
176
277
|
|
|
177
278
|
/** Width of each column */
|
|
178
279
|
const columnWidths = $state({}) as Record<string, number>
|
|
@@ -181,10 +282,11 @@
|
|
|
181
282
|
|
|
182
283
|
/** grid-template-columns for widths */
|
|
183
284
|
const style = $derived.by(() => {
|
|
285
|
+
if(!mounted) return ''
|
|
184
286
|
const templateColumns = `
|
|
185
287
|
#${id} > .headers,
|
|
186
|
-
#${id} >
|
|
187
|
-
#${id} >
|
|
288
|
+
#${id} > tbody > .row,
|
|
289
|
+
#${id} > tfoot > tr,
|
|
188
290
|
#${id} > .content > .virtual.bottom {
|
|
189
291
|
grid-template-columns: ${
|
|
190
292
|
columns.map((key, i, arr) => {
|
|
@@ -198,34 +300,40 @@
|
|
|
198
300
|
`
|
|
199
301
|
|
|
200
302
|
let sum = 0
|
|
201
|
-
const stickyLeft = sticky.map((key, i, arr) => {
|
|
303
|
+
const stickyLeft = [...fixed, ...sticky].map((key, i, arr) => {
|
|
202
304
|
sum += getWidth(arr[i - 1], i === 0 ? 0 : undefined)
|
|
203
305
|
return `
|
|
204
306
|
#${id} .column.sticky[data-column='${key}'] {
|
|
205
307
|
left: ${sum}px;
|
|
206
308
|
}
|
|
207
|
-
|
|
309
|
+
`
|
|
208
310
|
}).join('')
|
|
209
311
|
|
|
210
312
|
return templateColumns + stickyLeft
|
|
211
313
|
})
|
|
212
|
-
|
|
314
|
+
|
|
213
315
|
function observeColumnWidth(node: HTMLDivElement, isHeader = false) {
|
|
214
316
|
if(!isHeader) return
|
|
215
317
|
|
|
216
318
|
const key = node.getAttribute('data-column')!
|
|
217
319
|
node.style.width = getWidth(key) + 'px'
|
|
218
320
|
|
|
219
|
-
const observer = new MutationObserver(() =>
|
|
321
|
+
const observer = new MutationObserver(() => {
|
|
322
|
+
columnWidths[key] = parseFloat(node.style.width)
|
|
323
|
+
})
|
|
220
324
|
|
|
221
325
|
observer.observe(node, {attributes: true})
|
|
222
326
|
return { destroy: () => observer.disconnect() }
|
|
223
327
|
}
|
|
224
328
|
|
|
225
|
-
function onscroll(
|
|
226
|
-
const target =
|
|
329
|
+
async function onscroll() {
|
|
330
|
+
const target = elements.rows
|
|
227
331
|
if(target.scrollTop !== scrollTop) {
|
|
228
|
-
scrollTop = target
|
|
332
|
+
scrollTop = target?.scrollTop ?? scrollTop
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if(elements.selects) {
|
|
336
|
+
elements.selects.scrollTop = target?.scrollTop
|
|
229
337
|
}
|
|
230
338
|
|
|
231
339
|
if(!elements.headers) return
|
|
@@ -233,8 +341,13 @@
|
|
|
233
341
|
elements.statusbar.scrollLeft = target.scrollLeft
|
|
234
342
|
}
|
|
235
343
|
|
|
344
|
+
|
|
236
345
|
export {
|
|
237
|
-
|
|
346
|
+
selected,
|
|
347
|
+
positions,
|
|
348
|
+
data,
|
|
349
|
+
href,
|
|
350
|
+
cols as columns
|
|
238
351
|
}
|
|
239
352
|
|
|
240
353
|
</script>
|
|
@@ -249,88 +362,102 @@
|
|
|
249
362
|
arg: null | ((column: string) => any[]) = null,
|
|
250
363
|
isHeader = false
|
|
251
364
|
)}
|
|
252
|
-
{#each
|
|
253
|
-
{#if !
|
|
365
|
+
{#each fixed as column, i (column)}
|
|
366
|
+
{#if !hidden.includes(column)}
|
|
367
|
+
{@const args = arg ? arg(column) : []}
|
|
368
|
+
<svelte:element
|
|
369
|
+
this={isHeader ? 'th' : 'td'}
|
|
370
|
+
class='column sticky fixed'
|
|
371
|
+
data-column={column}
|
|
372
|
+
class:header={isHeader}
|
|
373
|
+
>
|
|
374
|
+
{@render renderable(column)?.(args[0], args[1])}
|
|
375
|
+
</svelte:element>
|
|
376
|
+
{/if}
|
|
377
|
+
{/each}
|
|
378
|
+
{#each sticky as column, i (column)}
|
|
379
|
+
{#if !hidden.includes(column)}
|
|
254
380
|
{@const args = arg ? arg(column) : []}
|
|
255
|
-
<
|
|
381
|
+
<svelte:element
|
|
382
|
+
this={isHeader ? 'th' : 'td'}
|
|
256
383
|
class='column sticky'
|
|
257
384
|
use:observeColumnWidth={isHeader}
|
|
258
385
|
data-column={column}
|
|
259
|
-
class:
|
|
260
|
-
class:
|
|
386
|
+
class:header={isHeader}
|
|
387
|
+
class:resizeable={isHeader && table.columns[column].options.resizeable && table.resizeable}
|
|
388
|
+
class:border={i == sticky.length - 1}
|
|
261
389
|
>
|
|
262
390
|
{@render renderable(column)?.(args[0], args[1])}
|
|
263
|
-
</
|
|
391
|
+
</svelte:element>
|
|
264
392
|
{/if}
|
|
265
393
|
{/each}
|
|
266
|
-
{#each
|
|
267
|
-
{#if !
|
|
394
|
+
{#each scrolled as column, i (column)}
|
|
395
|
+
{#if !hidden.includes(column)}
|
|
268
396
|
{@const args = arg ? arg(column) : []}
|
|
269
|
-
<
|
|
397
|
+
<svelte:element
|
|
398
|
+
this={isHeader ? 'th' : 'td'}
|
|
270
399
|
class='column'
|
|
271
400
|
data-column={column}
|
|
272
401
|
use:observeColumnWidth={isHeader}
|
|
273
|
-
class:resizeable={table.columns[column].options.resizeable && table.resizeable}
|
|
402
|
+
class:resizeable={isHeader && table.columns[column].options.resizeable && table.resizeable}
|
|
274
403
|
>
|
|
275
404
|
{@render renderable(column)?.(args[0], args[1])}
|
|
276
|
-
</
|
|
405
|
+
</svelte:element>
|
|
277
406
|
{/if}
|
|
278
407
|
{/each}
|
|
279
408
|
{/snippet}
|
|
280
409
|
|
|
281
|
-
<
|
|
410
|
+
<table
|
|
411
|
+
id={id}
|
|
412
|
+
class='table svelte-tably'
|
|
413
|
+
style='--t: {virtualTop}px; --b: {virtualBottom}px;'
|
|
414
|
+
aria-rowcount='{data.length}'
|
|
415
|
+
>
|
|
282
416
|
|
|
283
|
-
<
|
|
417
|
+
<thead class='headers' bind:this={elements.headers}>
|
|
284
418
|
{@render columnsSnippet((column) => table.columns[column]?.header, () => [true], true)}
|
|
285
|
-
</
|
|
286
|
-
|
|
287
|
-
<
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
{
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
}
|
|
320
|
-
)}
|
|
321
|
-
</a>
|
|
322
|
-
{/each}
|
|
323
|
-
</div>
|
|
324
|
-
<div class='virtual bottom' style='height: {virtualBottom}px'>
|
|
325
|
-
{@render columnsSnippet(() => undefined)}
|
|
326
|
-
</div>
|
|
327
|
-
</div>
|
|
419
|
+
</thead>
|
|
420
|
+
|
|
421
|
+
<tbody class='content' bind:this={elements.rows} onscrollcapture={onscroll} bind:clientHeight={viewportHeight}>
|
|
422
|
+
{#each area as item, i (item)}
|
|
423
|
+
{@const props = table.href ? { href: table.href(item) } : {}}
|
|
424
|
+
{@const index = data.indexOf(item) + 1}
|
|
425
|
+
<svelte:element
|
|
426
|
+
this={table.href ? 'a' : 'tr'}
|
|
427
|
+
class='row'
|
|
428
|
+
class:hover={hoveredRow === item}
|
|
429
|
+
class:selected={table.selected?.includes(item)}
|
|
430
|
+
class:first={i === 0}
|
|
431
|
+
class:last={i === area.length - 1}
|
|
432
|
+
{...props}
|
|
433
|
+
aria-rowindex='{index}'
|
|
434
|
+
onpointerenter={() => hoveredRow = item}
|
|
435
|
+
onpointerleave={() => hoveredRow = null}
|
|
436
|
+
>
|
|
437
|
+
{@render columnsSnippet(
|
|
438
|
+
(column) => table.columns[column]!.row,
|
|
439
|
+
(column) => {
|
|
440
|
+
const col = table.columns[column]!
|
|
441
|
+
return [item, {
|
|
442
|
+
get index() { return index - 1 },
|
|
443
|
+
get value() { return col.options.value ? col.options.value(item) : undefined },
|
|
444
|
+
get isHovered() { return hoveredRow === item },
|
|
445
|
+
get selected() { return table.selected?.includes(item) },
|
|
446
|
+
set selected(value) { value ? table.selected!.push(item) : table.selected!.splice(table.selected!.indexOf(item), 1) }
|
|
447
|
+
}]
|
|
448
|
+
}
|
|
449
|
+
)}
|
|
450
|
+
</svelte:element>
|
|
451
|
+
{/each}
|
|
452
|
+
</tbody>
|
|
328
453
|
|
|
329
|
-
<
|
|
330
|
-
|
|
331
|
-
|
|
454
|
+
<tfoot class='statusbar' bind:this={elements.statusbar}>
|
|
455
|
+
<tr>
|
|
456
|
+
{@render columnsSnippet((column) => table.columns[column]?.statusbar)}
|
|
457
|
+
</tr>
|
|
458
|
+
</tfoot>
|
|
332
459
|
|
|
333
|
-
<
|
|
460
|
+
<caption class='panel' style='width: {(panelTween.current)}px;' style:overflow={panelTween.transitioning ? 'hidden' : 'auto'}>
|
|
334
461
|
{#if panel && panel in table.panels}
|
|
335
462
|
<div
|
|
336
463
|
class='panel-content'
|
|
@@ -341,16 +468,75 @@
|
|
|
341
468
|
{@render table.panels[panel].content({ get table() { return table }, get data() { return data } })}
|
|
342
469
|
</div>
|
|
343
470
|
{/if}
|
|
344
|
-
</
|
|
345
|
-
<
|
|
471
|
+
</caption>
|
|
472
|
+
<caption
|
|
346
473
|
class='backdrop'
|
|
347
|
-
aria-label='Panel backdrop'
|
|
348
|
-
tabindex='-1'
|
|
349
474
|
aria-hidden={panel && table.panels[panel]?.backdrop ? false : true}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
475
|
+
>
|
|
476
|
+
<button
|
|
477
|
+
aria-label='Panel backdrop'
|
|
478
|
+
tabindex='-1'
|
|
479
|
+
onclick={() => panel = undefined}
|
|
480
|
+
></button>
|
|
481
|
+
</caption>
|
|
482
|
+
</table>
|
|
483
|
+
|
|
484
|
+
{#snippet headerSelected(ctx: HeaderSelectCtx<T>)}
|
|
485
|
+
<input
|
|
486
|
+
type='checkbox'
|
|
487
|
+
indeterminate={ctx.indeterminate}
|
|
488
|
+
bind:checked={ctx.isSelected}
|
|
489
|
+
/>
|
|
490
|
+
{/snippet}
|
|
353
491
|
|
|
492
|
+
{#snippet rowSelected(ctx: RowSelectCtx<T>)}
|
|
493
|
+
<input
|
|
494
|
+
type='checkbox'
|
|
495
|
+
bind:checked={ctx.isSelected}
|
|
496
|
+
/>
|
|
497
|
+
{/snippet}
|
|
498
|
+
|
|
499
|
+
{#if select}
|
|
500
|
+
{@const { show = 'hover', style = 'column', rowSnippet = rowSelected, headerSnippet = headerSelected } = typeof select === 'boolean' ? {} : select}
|
|
501
|
+
{#if show !== 'never'}
|
|
502
|
+
<Column id='__fixed' {table} fixed width={56} resizeable={false}>
|
|
503
|
+
{#snippet header()}
|
|
504
|
+
{@render headerSnippet({
|
|
505
|
+
get isSelected() {
|
|
506
|
+
return table.data.length === table.selected?.length
|
|
507
|
+
},
|
|
508
|
+
set isSelected(value) {
|
|
509
|
+
if(value) {
|
|
510
|
+
table.selected = table.data
|
|
511
|
+
} else {
|
|
512
|
+
table.selected = []
|
|
513
|
+
}
|
|
514
|
+
},
|
|
515
|
+
get selected() {
|
|
516
|
+
return table.selected!
|
|
517
|
+
},
|
|
518
|
+
get indeterminate() {
|
|
519
|
+
return (table.selected?.length || 0) > 0
|
|
520
|
+
&& table.data.length !== table.selected?.length
|
|
521
|
+
}
|
|
522
|
+
})}
|
|
523
|
+
{/snippet}
|
|
524
|
+
{#snippet row(item, row)}
|
|
525
|
+
<div class='__fixed'>
|
|
526
|
+
{#if row.selected || show === 'always' || (row.isHovered && show === 'hover')}
|
|
527
|
+
{@render rowSnippet({
|
|
528
|
+
get isSelected() { return row.selected },
|
|
529
|
+
set isSelected(value) { row.selected = value },
|
|
530
|
+
get row() { return row },
|
|
531
|
+
get item() { return item },
|
|
532
|
+
get data() { return table.data }
|
|
533
|
+
})}
|
|
534
|
+
{/if}
|
|
535
|
+
</div>
|
|
536
|
+
{/snippet}
|
|
537
|
+
</Column>
|
|
538
|
+
{/if}
|
|
539
|
+
{/if}
|
|
354
540
|
|
|
355
541
|
{@render content?.({ Column, Panel, get table() { return table }, get data() { return data } })}
|
|
356
542
|
|
|
@@ -359,42 +545,61 @@
|
|
|
359
545
|
<!---------------------------------------------------->
|
|
360
546
|
<style>
|
|
361
547
|
|
|
362
|
-
.
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
opacity: 1;
|
|
368
|
-
left: 2px;
|
|
369
|
-
overflow: visible;
|
|
370
|
-
background-color: transparent;
|
|
371
|
-
transition: .15s ease;
|
|
372
|
-
> input {
|
|
373
|
-
width: 18px;
|
|
374
|
-
height: 18px;
|
|
375
|
-
border-radius: 1rem;
|
|
376
|
-
cursor: pointer;
|
|
377
|
-
}
|
|
548
|
+
.svelte-tably *, .svelte-tably {
|
|
549
|
+
all: unset;
|
|
550
|
+
box-sizing: border-box;
|
|
551
|
+
background-color: inherit;
|
|
552
|
+
}
|
|
378
553
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
554
|
+
.svelte-tably {
|
|
555
|
+
position: relative;
|
|
556
|
+
overflow: visible;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
input[type='checkbox'] {
|
|
560
|
+
all: revert;
|
|
561
|
+
width: 18px;
|
|
562
|
+
height: 18px;
|
|
563
|
+
cursor: pointer;
|
|
386
564
|
}
|
|
387
|
-
|
|
565
|
+
|
|
566
|
+
.__fixed {
|
|
567
|
+
display: flex;
|
|
568
|
+
align-items: center;
|
|
569
|
+
justify-content: center;
|
|
570
|
+
gap: .5rem;
|
|
571
|
+
position: absolute;
|
|
572
|
+
top: 0; left: 0;
|
|
573
|
+
right: 0; bottom: 0;
|
|
574
|
+
width: 100%;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
.first .__fixed {
|
|
578
|
+
top: var(--tably-padding-y, .5rem);
|
|
579
|
+
}
|
|
580
|
+
.last .__fixed {
|
|
581
|
+
bottom: var(--tably-padding-y, .5rem);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
tbody::before, tbody::after, selects::before, selects::after {
|
|
585
|
+
content: '';
|
|
586
|
+
display: grid;
|
|
587
|
+
min-height: 100%;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
tbody::before, selects::before {
|
|
591
|
+
height: var(--t);
|
|
592
|
+
}
|
|
593
|
+
tbody::after, selects::after {
|
|
594
|
+
height: var(--b);
|
|
595
|
+
}
|
|
388
596
|
|
|
389
597
|
a.row {
|
|
390
598
|
color: inherit;
|
|
391
599
|
text-decoration: inherit;
|
|
392
600
|
}
|
|
393
601
|
|
|
394
|
-
|
|
395
|
-
box-sizing: border-box;
|
|
396
|
-
background-color: inherit;
|
|
397
|
-
}
|
|
602
|
+
|
|
398
603
|
|
|
399
604
|
.backdrop {
|
|
400
605
|
position: absolute;
|
|
@@ -410,6 +615,14 @@
|
|
|
410
615
|
outline: none;
|
|
411
616
|
cursor: pointer;
|
|
412
617
|
|
|
618
|
+
> button {
|
|
619
|
+
position: absolute;
|
|
620
|
+
left: 0px;
|
|
621
|
+
top: 0px;
|
|
622
|
+
bottom: 0px;
|
|
623
|
+
right: 0px;
|
|
624
|
+
}
|
|
625
|
+
|
|
413
626
|
&[aria-hidden='true'] {
|
|
414
627
|
opacity: 0;
|
|
415
628
|
pointer-events: none;
|
|
@@ -478,19 +691,13 @@
|
|
|
478
691
|
}
|
|
479
692
|
|
|
480
693
|
.content {
|
|
481
|
-
grid-area: rows;
|
|
482
694
|
display: grid;
|
|
695
|
+
grid-auto-rows: max-content;
|
|
696
|
+
|
|
697
|
+
grid-area: rows;
|
|
483
698
|
scrollbar-width: thin;
|
|
484
699
|
overflow: auto;
|
|
485
|
-
height: 100%;
|
|
486
|
-
grid-template-rows: auto auto 1fr;
|
|
487
|
-
|
|
488
|
-
> .rows, > .virtual.bottom {
|
|
489
|
-
display: grid;
|
|
490
|
-
}
|
|
491
|
-
> .virtual.bottom {
|
|
492
|
-
min-height: 100%;
|
|
493
|
-
}
|
|
700
|
+
/* height: 100%; */
|
|
494
701
|
}
|
|
495
702
|
|
|
496
703
|
.statusbar {
|
|
@@ -499,12 +706,12 @@
|
|
|
499
706
|
background-color: var(--tably-statusbar, hsl(0, 0%, 98%));
|
|
500
707
|
}
|
|
501
708
|
|
|
502
|
-
.statusbar > .column {
|
|
709
|
+
.statusbar > tr > .column {
|
|
503
710
|
border-top: 1px solid var(--tably-border, hsl(0, 0%, 90%));
|
|
504
711
|
padding: calc(var(--tably-padding-y, .5rem) / 2) 0;
|
|
505
712
|
}
|
|
506
713
|
|
|
507
|
-
.headers, .row, .statusbar {
|
|
714
|
+
.headers, .row, .statusbar > tr {
|
|
508
715
|
position: relative;
|
|
509
716
|
display: grid;
|
|
510
717
|
width: 100%;
|
|
@@ -553,8 +760,4 @@
|
|
|
553
760
|
}
|
|
554
761
|
}
|
|
555
762
|
|
|
556
|
-
.statusbar {
|
|
557
|
-
grid-area: statusbar;
|
|
558
|
-
}
|
|
559
|
-
|
|
560
763
|
</style>
|
package/dist/Table.svelte.d.ts
CHANGED
|
@@ -18,8 +18,23 @@ export interface TableState<T extends Record<PropertyKey, any> = Record<Property
|
|
|
18
18
|
removeColumn(key: string): void;
|
|
19
19
|
}
|
|
20
20
|
export declare function getTableState<T extends Record<PropertyKey, any> = Record<PropertyKey, any>>(): TableState<T>;
|
|
21
|
+
export type HeaderSelectCtx<T = any> = {
|
|
22
|
+
isSelected: boolean;
|
|
23
|
+
/** The list of selected items */
|
|
24
|
+
readonly selected: T[];
|
|
25
|
+
/**
|
|
26
|
+
* See [MDN :indeterminate](https://developer.mozilla.org/en-US/docs/Web/CSS/:indeterminate)
|
|
27
|
+
*/
|
|
28
|
+
readonly indeterminate: boolean;
|
|
29
|
+
};
|
|
30
|
+
export type RowSelectCtx<T = any> = {
|
|
31
|
+
readonly item: T;
|
|
32
|
+
readonly row: RowCtx<unknown>;
|
|
33
|
+
data: T[];
|
|
34
|
+
isSelected: boolean;
|
|
35
|
+
};
|
|
21
36
|
import { type Snippet } from 'svelte';
|
|
22
|
-
import Column, { type Column as TColumn } from './Column.svelte';
|
|
37
|
+
import Column, { type RowCtx, type Column as TColumn } from './Column.svelte';
|
|
23
38
|
import Panel, { type Panel as TPanel } from './Panel.svelte';
|
|
24
39
|
declare class __sveltets_Render<T extends Record<PropertyKey, unknown>> {
|
|
25
40
|
props(): {
|
|
@@ -31,12 +46,14 @@ declare class __sveltets_Render<T extends Record<PropertyKey, unknown>> {
|
|
|
31
46
|
statusbar?: Column<T_1, V>["statusbar"];
|
|
32
47
|
id: string;
|
|
33
48
|
sticky?: boolean;
|
|
49
|
+
fixed?: boolean;
|
|
34
50
|
sort?: boolean;
|
|
35
51
|
show?: boolean;
|
|
36
52
|
width?: number;
|
|
37
53
|
value?: Column<T_1, V>["options"]["value"];
|
|
38
54
|
sorting?: Column<T_1, V>["options"]["sorting"];
|
|
39
55
|
resizeable?: boolean;
|
|
56
|
+
table?: TableState;
|
|
40
57
|
}): {};
|
|
41
58
|
new (options: import("svelte").ComponentConstructorOptions<{
|
|
42
59
|
header?: Column<T_1, V>["header"];
|
|
@@ -44,24 +61,28 @@ declare class __sveltets_Render<T extends Record<PropertyKey, unknown>> {
|
|
|
44
61
|
statusbar?: Column<T_1, V>["statusbar"];
|
|
45
62
|
id: string;
|
|
46
63
|
sticky?: boolean;
|
|
64
|
+
fixed?: boolean;
|
|
47
65
|
sort?: boolean;
|
|
48
66
|
show?: boolean;
|
|
49
67
|
width?: number;
|
|
50
68
|
value?: Column<T_1, V>["options"]["value"];
|
|
51
69
|
sorting?: Column<T_1, V>["options"]["sorting"];
|
|
52
70
|
resizeable?: boolean;
|
|
71
|
+
table?: TableState;
|
|
53
72
|
}>): SvelteComponent<{
|
|
54
73
|
header?: Column<T_1, V>["header"];
|
|
55
74
|
row: Column<T_1, V>["row"];
|
|
56
75
|
statusbar?: Column<T_1, V>["statusbar"];
|
|
57
76
|
id: string;
|
|
58
77
|
sticky?: boolean;
|
|
78
|
+
fixed?: boolean;
|
|
59
79
|
sort?: boolean;
|
|
60
80
|
show?: boolean;
|
|
61
81
|
width?: number;
|
|
62
82
|
value?: Column<T_1, V>["options"]["value"];
|
|
63
83
|
sorting?: Column<T_1, V>["options"]["sorting"];
|
|
64
84
|
resizeable?: boolean;
|
|
85
|
+
table?: TableState;
|
|
65
86
|
}, {}, {}> & {
|
|
66
87
|
$$bindings?: ReturnType<() => "">;
|
|
67
88
|
};
|
|
@@ -80,13 +101,38 @@ declare class __sveltets_Render<T extends Record<PropertyKey, unknown>> {
|
|
|
80
101
|
* @default true
|
|
81
102
|
*/
|
|
82
103
|
resizeable?: boolean;
|
|
83
|
-
|
|
104
|
+
selected?: T[] | undefined;
|
|
105
|
+
select?: boolean | {
|
|
106
|
+
/**
|
|
107
|
+
* The style, in which the selection is shown
|
|
108
|
+
*
|
|
109
|
+
* NOTE: If using `edge` | 'side', "show" will always be `hover`. This is due to
|
|
110
|
+
* an inconsistency/limitation of matching the scroll between the selection div and the rows.
|
|
111
|
+
*
|
|
112
|
+
* @default 'column'
|
|
113
|
+
*/
|
|
114
|
+
style?: "column";
|
|
115
|
+
/**
|
|
116
|
+
* When to show the row-select, when not selected?
|
|
117
|
+
* @default 'hover'
|
|
118
|
+
*/
|
|
119
|
+
show?: "hover" | "always" | "never";
|
|
120
|
+
/**
|
|
121
|
+
* Custom snippet
|
|
122
|
+
*/
|
|
123
|
+
headerSnippet?: Snippet<[context: HeaderSelectCtx]>;
|
|
124
|
+
rowSnippet?: Snippet<[context: RowSelectCtx<T>]> | undefined;
|
|
125
|
+
} | undefined;
|
|
84
126
|
};
|
|
85
127
|
events(): {};
|
|
86
128
|
slots(): {};
|
|
87
|
-
bindings(): "panel";
|
|
129
|
+
bindings(): "selected" | "panel";
|
|
88
130
|
exports(): {
|
|
89
|
-
|
|
131
|
+
selected: T[];
|
|
132
|
+
positions: TableState<T_1>["positions"];
|
|
133
|
+
data: T[];
|
|
134
|
+
href: ((item: T) => string) | undefined;
|
|
135
|
+
columns: Record<string, TColumn<T, unknown>>;
|
|
90
136
|
};
|
|
91
137
|
}
|
|
92
138
|
interface $$IsomorphicComponent {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare class Trigger<T> {
|
|
2
|
+
#private;
|
|
3
|
+
constructor();
|
|
4
|
+
trigger(value?: T): void;
|
|
5
|
+
onTrigger<E extends HTMLElement>(node: E, fn: (node: E, value?: T) => void): {
|
|
6
|
+
destroy: () => boolean;
|
|
7
|
+
};
|
|
8
|
+
/** Subscribe to the trigger; returns the value if any. */
|
|
9
|
+
get current(): T | undefined;
|
|
10
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createSubscriber } from 'svelte/reactivity';
|
|
2
|
+
export class Trigger {
|
|
3
|
+
#subscribe;
|
|
4
|
+
#update = () => { };
|
|
5
|
+
#subscribers = new Set();
|
|
6
|
+
#value;
|
|
7
|
+
constructor() {
|
|
8
|
+
this.#subscribe = createSubscriber(update => this.#update = update);
|
|
9
|
+
}
|
|
10
|
+
trigger(value) {
|
|
11
|
+
this.#value = value;
|
|
12
|
+
this.#update();
|
|
13
|
+
this.#subscribers.forEach(fn => fn(value));
|
|
14
|
+
}
|
|
15
|
+
onTrigger(node, fn) {
|
|
16
|
+
const f = (value) => fn(node, value);
|
|
17
|
+
this.#subscribers.add(f);
|
|
18
|
+
return {
|
|
19
|
+
destroy: () => this.#subscribers.delete(f)
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** Subscribe to the trigger; returns the value if any. */
|
|
23
|
+
get current() {
|
|
24
|
+
this.#subscribe();
|
|
25
|
+
return this.#value;
|
|
26
|
+
}
|
|
27
|
+
}
|