svelte-tably 1.0.0-next.6 → 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 +388 -172
- 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>
|
|
@@ -180,10 +281,12 @@
|
|
|
180
281
|
const getWidth = (key: string, def: number = 150) => columnWidths[key] || table.columns[key]?.defaults.width || def
|
|
181
282
|
|
|
182
283
|
/** grid-template-columns for widths */
|
|
183
|
-
const style = $derived(
|
|
284
|
+
const style = $derived.by(() => {
|
|
285
|
+
if(!mounted) return ''
|
|
286
|
+
const templateColumns = `
|
|
184
287
|
#${id} > .headers,
|
|
185
|
-
#${id} >
|
|
186
|
-
#${id} >
|
|
288
|
+
#${id} > tbody > .row,
|
|
289
|
+
#${id} > tfoot > tr,
|
|
187
290
|
#${id} > .content > .virtual.bottom {
|
|
188
291
|
grid-template-columns: ${
|
|
189
292
|
columns.map((key, i, arr) => {
|
|
@@ -194,26 +297,43 @@
|
|
|
194
297
|
}).join(' ')
|
|
195
298
|
};
|
|
196
299
|
}
|
|
197
|
-
|
|
198
|
-
#${id} .column.sticky[data-column='${key}'] {
|
|
199
|
-
left: ${getWidth(arr[i - 1], 0)}px;
|
|
200
|
-
}
|
|
201
|
-
`).join(''))
|
|
300
|
+
`
|
|
202
301
|
|
|
302
|
+
let sum = 0
|
|
303
|
+
const stickyLeft = [...fixed, ...sticky].map((key, i, arr) => {
|
|
304
|
+
sum += getWidth(arr[i - 1], i === 0 ? 0 : undefined)
|
|
305
|
+
return `
|
|
306
|
+
#${id} .column.sticky[data-column='${key}'] {
|
|
307
|
+
left: ${sum}px;
|
|
308
|
+
}
|
|
309
|
+
`
|
|
310
|
+
}).join('')
|
|
311
|
+
|
|
312
|
+
return templateColumns + stickyLeft
|
|
313
|
+
})
|
|
314
|
+
|
|
203
315
|
function observeColumnWidth(node: HTMLDivElement, isHeader = false) {
|
|
204
316
|
if(!isHeader) return
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
317
|
+
|
|
318
|
+
const key = node.getAttribute('data-column')!
|
|
319
|
+
node.style.width = getWidth(key) + 'px'
|
|
320
|
+
|
|
321
|
+
const observer = new MutationObserver(() => {
|
|
322
|
+
columnWidths[key] = parseFloat(node.style.width)
|
|
208
323
|
})
|
|
324
|
+
|
|
209
325
|
observer.observe(node, {attributes: true})
|
|
210
326
|
return { destroy: () => observer.disconnect() }
|
|
211
327
|
}
|
|
212
328
|
|
|
213
|
-
function onscroll(
|
|
214
|
-
const target =
|
|
329
|
+
async function onscroll() {
|
|
330
|
+
const target = elements.rows
|
|
215
331
|
if(target.scrollTop !== scrollTop) {
|
|
216
|
-
scrollTop = target
|
|
332
|
+
scrollTop = target?.scrollTop ?? scrollTop
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if(elements.selects) {
|
|
336
|
+
elements.selects.scrollTop = target?.scrollTop
|
|
217
337
|
}
|
|
218
338
|
|
|
219
339
|
if(!elements.headers) return
|
|
@@ -221,8 +341,13 @@
|
|
|
221
341
|
elements.statusbar.scrollLeft = target.scrollLeft
|
|
222
342
|
}
|
|
223
343
|
|
|
344
|
+
|
|
224
345
|
export {
|
|
225
|
-
|
|
346
|
+
selected,
|
|
347
|
+
positions,
|
|
348
|
+
data,
|
|
349
|
+
href,
|
|
350
|
+
cols as columns
|
|
226
351
|
}
|
|
227
352
|
|
|
228
353
|
</script>
|
|
@@ -237,87 +362,102 @@
|
|
|
237
362
|
arg: null | ((column: string) => any[]) = null,
|
|
238
363
|
isHeader = false
|
|
239
364
|
)}
|
|
240
|
-
{#each
|
|
241
|
-
{#if !
|
|
365
|
+
{#each fixed as column, i (column)}
|
|
366
|
+
{#if !hidden.includes(column)}
|
|
242
367
|
{@const args = arg ? arg(column) : []}
|
|
243
|
-
<
|
|
244
|
-
|
|
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)}
|
|
380
|
+
{@const args = arg ? arg(column) : []}
|
|
381
|
+
<svelte:element
|
|
382
|
+
this={isHeader ? 'th' : 'td'}
|
|
383
|
+
class='column sticky'
|
|
245
384
|
use:observeColumnWidth={isHeader}
|
|
246
385
|
data-column={column}
|
|
247
|
-
class:
|
|
248
|
-
class:
|
|
386
|
+
class:header={isHeader}
|
|
387
|
+
class:resizeable={isHeader && table.columns[column].options.resizeable && table.resizeable}
|
|
388
|
+
class:border={i == sticky.length - 1}
|
|
249
389
|
>
|
|
250
390
|
{@render renderable(column)?.(args[0], args[1])}
|
|
251
|
-
</
|
|
391
|
+
</svelte:element>
|
|
252
392
|
{/if}
|
|
253
393
|
{/each}
|
|
254
|
-
{#each
|
|
255
|
-
{#if !
|
|
394
|
+
{#each scrolled as column, i (column)}
|
|
395
|
+
{#if !hidden.includes(column)}
|
|
256
396
|
{@const args = arg ? arg(column) : []}
|
|
257
|
-
<
|
|
397
|
+
<svelte:element
|
|
398
|
+
this={isHeader ? 'th' : 'td'}
|
|
258
399
|
class='column'
|
|
259
400
|
data-column={column}
|
|
260
401
|
use:observeColumnWidth={isHeader}
|
|
261
|
-
class:resizeable={table.columns[column].options.resizeable && table.resizeable}
|
|
402
|
+
class:resizeable={isHeader && table.columns[column].options.resizeable && table.resizeable}
|
|
262
403
|
>
|
|
263
404
|
{@render renderable(column)?.(args[0], args[1])}
|
|
264
|
-
</
|
|
405
|
+
</svelte:element>
|
|
265
406
|
{/if}
|
|
266
407
|
{/each}
|
|
267
408
|
{/snippet}
|
|
268
409
|
|
|
269
|
-
<
|
|
410
|
+
<table
|
|
411
|
+
id={id}
|
|
412
|
+
class='table svelte-tably'
|
|
413
|
+
style='--t: {virtualTop}px; --b: {virtualBottom}px;'
|
|
414
|
+
aria-rowcount='{data.length}'
|
|
415
|
+
>
|
|
270
416
|
|
|
271
|
-
<
|
|
417
|
+
<thead class='headers' bind:this={elements.headers}>
|
|
272
418
|
{@render columnsSnippet((column) => table.columns[column]?.header, () => [true], true)}
|
|
273
|
-
</
|
|
274
|
-
|
|
275
|
-
<
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
{
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
)}
|
|
308
|
-
</a>
|
|
309
|
-
{/each}
|
|
310
|
-
</div>
|
|
311
|
-
<div class='virtual bottom' style='height: {virtualBottom}px'>
|
|
312
|
-
{@render columnsSnippet(() => undefined)}
|
|
313
|
-
</div>
|
|
314
|
-
</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>
|
|
315
453
|
|
|
316
|
-
<
|
|
317
|
-
|
|
318
|
-
|
|
454
|
+
<tfoot class='statusbar' bind:this={elements.statusbar}>
|
|
455
|
+
<tr>
|
|
456
|
+
{@render columnsSnippet((column) => table.columns[column]?.statusbar)}
|
|
457
|
+
</tr>
|
|
458
|
+
</tfoot>
|
|
319
459
|
|
|
320
|
-
<
|
|
460
|
+
<caption class='panel' style='width: {(panelTween.current)}px;' style:overflow={panelTween.transitioning ? 'hidden' : 'auto'}>
|
|
321
461
|
{#if panel && panel in table.panels}
|
|
322
462
|
<div
|
|
323
463
|
class='panel-content'
|
|
@@ -328,16 +468,75 @@
|
|
|
328
468
|
{@render table.panels[panel].content({ get table() { return table }, get data() { return data } })}
|
|
329
469
|
</div>
|
|
330
470
|
{/if}
|
|
331
|
-
</
|
|
332
|
-
<
|
|
471
|
+
</caption>
|
|
472
|
+
<caption
|
|
333
473
|
class='backdrop'
|
|
334
|
-
aria-label='Panel backdrop'
|
|
335
|
-
tabindex='-1'
|
|
336
474
|
aria-hidden={panel && table.panels[panel]?.backdrop ? false : true}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
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}
|
|
340
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}
|
|
341
540
|
|
|
342
541
|
{@render content?.({ Column, Panel, get table() { return table }, get data() { return data } })}
|
|
343
542
|
|
|
@@ -346,42 +545,61 @@
|
|
|
346
545
|
<!---------------------------------------------------->
|
|
347
546
|
<style>
|
|
348
547
|
|
|
349
|
-
.
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
opacity: 1;
|
|
355
|
-
left: 2px;
|
|
356
|
-
overflow: visible;
|
|
357
|
-
background-color: transparent;
|
|
358
|
-
transition: .15s ease;
|
|
359
|
-
> input {
|
|
360
|
-
width: 18px;
|
|
361
|
-
height: 18px;
|
|
362
|
-
border-radius: 1rem;
|
|
363
|
-
cursor: pointer;
|
|
364
|
-
}
|
|
548
|
+
.svelte-tably *, .svelte-tably {
|
|
549
|
+
all: unset;
|
|
550
|
+
box-sizing: border-box;
|
|
551
|
+
background-color: inherit;
|
|
552
|
+
}
|
|
365
553
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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;
|
|
564
|
+
}
|
|
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);
|
|
373
582
|
}
|
|
374
|
-
|
|
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
|
+
}
|
|
375
596
|
|
|
376
597
|
a.row {
|
|
377
598
|
color: inherit;
|
|
378
599
|
text-decoration: inherit;
|
|
379
600
|
}
|
|
380
601
|
|
|
381
|
-
|
|
382
|
-
box-sizing: border-box;
|
|
383
|
-
background-color: inherit;
|
|
384
|
-
}
|
|
602
|
+
|
|
385
603
|
|
|
386
604
|
.backdrop {
|
|
387
605
|
position: absolute;
|
|
@@ -397,6 +615,14 @@
|
|
|
397
615
|
outline: none;
|
|
398
616
|
cursor: pointer;
|
|
399
617
|
|
|
618
|
+
> button {
|
|
619
|
+
position: absolute;
|
|
620
|
+
left: 0px;
|
|
621
|
+
top: 0px;
|
|
622
|
+
bottom: 0px;
|
|
623
|
+
right: 0px;
|
|
624
|
+
}
|
|
625
|
+
|
|
400
626
|
&[aria-hidden='true'] {
|
|
401
627
|
opacity: 0;
|
|
402
628
|
pointer-events: none;
|
|
@@ -465,19 +691,13 @@
|
|
|
465
691
|
}
|
|
466
692
|
|
|
467
693
|
.content {
|
|
468
|
-
grid-area: rows;
|
|
469
694
|
display: grid;
|
|
695
|
+
grid-auto-rows: max-content;
|
|
696
|
+
|
|
697
|
+
grid-area: rows;
|
|
470
698
|
scrollbar-width: thin;
|
|
471
699
|
overflow: auto;
|
|
472
|
-
height: 100%;
|
|
473
|
-
grid-template-rows: auto auto 1fr;
|
|
474
|
-
|
|
475
|
-
> .rows, > .virtual.bottom {
|
|
476
|
-
display: grid;
|
|
477
|
-
}
|
|
478
|
-
> .virtual.bottom {
|
|
479
|
-
min-height: 100%;
|
|
480
|
-
}
|
|
700
|
+
/* height: 100%; */
|
|
481
701
|
}
|
|
482
702
|
|
|
483
703
|
.statusbar {
|
|
@@ -486,12 +706,12 @@
|
|
|
486
706
|
background-color: var(--tably-statusbar, hsl(0, 0%, 98%));
|
|
487
707
|
}
|
|
488
708
|
|
|
489
|
-
.statusbar > .column {
|
|
709
|
+
.statusbar > tr > .column {
|
|
490
710
|
border-top: 1px solid var(--tably-border, hsl(0, 0%, 90%));
|
|
491
711
|
padding: calc(var(--tably-padding-y, .5rem) / 2) 0;
|
|
492
712
|
}
|
|
493
713
|
|
|
494
|
-
.headers, .row, .statusbar {
|
|
714
|
+
.headers, .row, .statusbar > tr {
|
|
495
715
|
position: relative;
|
|
496
716
|
display: grid;
|
|
497
717
|
width: 100%;
|
|
@@ -540,8 +760,4 @@
|
|
|
540
760
|
}
|
|
541
761
|
}
|
|
542
762
|
|
|
543
|
-
.statusbar {
|
|
544
|
-
grid-area: statusbar;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
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
|
+
}
|