svelte-tably 1.0.0-next.8 → 1.0.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Svelte Tably
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,92 +1,299 @@
1
- # svelte-tably
2
-
3
- Work in progress. I needed a break from my primary project, so here's a little side-project exploring the amazing capabilities of Svelte 5 with a Dynamic table!
4
-
5
- Example on [Svelte 5 Playground](https://svelte.dev/playground/a16d71c97445455e80a55b77ec1cf915?version=5)
6
-
7
- A high performant dynamic table
8
-
9
- - [x] Sticky columns
10
- - [x] Show/hide columns
11
- - [x] Re-order columns
12
- - [x] Resize columns
13
- - [x] Statusbar
14
- - [x] "Virtual" data (for sorting/filtering)
15
- - [x] Panels
16
- - [x] Virtual elements
17
- - [ ] sorting
18
- - [x] select
19
- - [ ] filtering
20
- - [ ] orderable table
21
- - [ ] row context-menu
22
- - [ ] dropout section
23
-
24
- ### Usage Notes
25
-
26
- ```html
27
- <script lang='ts'>
28
- import Table from 'svelte-tably'
29
-
30
- const data = $state([
31
- { name: 'John Doe', age: 30, email: 'johndoe@example.com' },
32
- { name: 'Jane Doe', age: 25, email: 'janedoe@example.com' },
33
- ])
34
-
35
- let activePanel = $state('columns') as string | undefined
36
- let selected = $state([]) as typeof data
37
- </script>
38
-
39
- <Table {data} panel={activePanel} select bind:selected>
40
- {#snippet content({ Column, Panel, state, data })}
41
- <Column id='name' sticky>
42
- {#snippet header()}
43
- Name
44
- {/snippet}
45
- {#snippet row(row)}
46
- {row.name}
47
- {/snippet}
48
-
49
- <!-- Optional per column. -->
50
- {#snippet statusbar()}
51
- {data.length}
52
- {/snippet}
53
- </Column>
54
- <Column ...>
55
- ...
56
- </Column>
57
- <!-- If you want to sort/filter a virtual value, that does not exist in the data -->
58
- <Column id='virtual' value={row => row.age > 18}>
59
- ...
60
- {#snippet row(row, virtual)}
61
- {virtual ? 'Adult' : 'Adolescent'}
62
- {/snippet}
63
- ...
64
- </Column>
65
-
66
- <Panel id='columns'>
67
- <!-- Anything you might like -->
68
- </Panel>
69
- <Panel ... backdrop={false}>
70
- ...
71
- </Panel>
72
- {/snippet}
73
- </Table>
74
- ```
75
-
76
- #### Styling
77
-
78
- For quick styling
79
-
80
- | CSS Variable | Description | Default |
81
- | - | - | - |
82
- | --tably-bg | background-color | `hsl(0, 0%, 100%)` |
83
- | --tably-color | color | `hsl(0, 0%, 0%)` |
84
- | --tably-border | border | `hsl(0, 0%, 90%)` |
85
- | --tably-statusbar | background-color for the statusbar | `hsl(0, 0%, 98%)` |
86
- | --tably-padding-y | Padding above/below each column | `.5rem` |
87
- | --tably-padding-x | Padding left of each column | `1rem` |
88
- | --tably-radius | Table radius | `.25rem` |
89
-
90
- Advanced styling can be done via `:global .svelte-tably`
91
-
92
-
1
+ # Svelte Tably
2
+
3
+ Via the amazing capabilities braught to us by Svelte 5 — a performant, dynamic, flexible, feature rich table. It's as simple, or as flexible as you need it to be.
4
+
5
+ Simple example on [Svelte 5 Playground](https://svelte.dev/playground/f79124e8473546d29433a95a68440d6d?version=5.16.0)
6
+ <br>
7
+ Fledged out example on [Svelte 5 Playground](https://svelte.dev/playground/a16d71c97445455e80a55b77ec1cf915?version=5)
8
+
9
+ - [x] Columns
10
+ - [x] Sticky
11
+ - [x] Show/hide
12
+ - [x] Re-order
13
+ - [x] Resize
14
+ - [x] Data manipulation
15
+ - [x] "Virtual" data
16
+ - [x] Sorting
17
+ - [x] Select
18
+ - [x] Filtering
19
+ - [x] Reorderable
20
+ - [x] Statusbar
21
+ - [x] Panels
22
+ - [x] Row context
23
+ - [x] Expandable rows
24
+ - [x] Virtual rendering
25
+ - [x] To CSV
26
+ - [x] Auto: Create columns based on data
27
+
28
+ On top of that, the library API is extensive, so the table can meet your needs.
29
+
30
+ ## Usage Notes
31
+
32
+ `bun add -D svelte-tably`
33
+
34
+ ```html
35
+ <script lang='ts'>
36
+ import Table from 'svelte-tably'
37
+
38
+ const data = $state([
39
+ { name: 'Giraffe', age: 26, email: 'giraffe@example.com' },
40
+ { name: 'Shiboo', age: 21, email: 'shiboo@example.com' }
41
+ ])
42
+
43
+ let activePanel = $state('columns') as string | undefined
44
+ let selected = $state([]) as typeof data
45
+ </script>
46
+
47
+ <!-- Auto: Generate Columns based on data properties -->
48
+ <Table auto {data} resizeable={false} filters={[...]} />
49
+
50
+ <Table {data} panel={activePanel} select bind:selected>
51
+ {#snippet content({ Column, Panel, Expandable, Row, state, table })}
52
+ <Column id='name' sticky sort value={r => r.name} filter={v => v.includes('Giraffe')}>
53
+ {#snippet header(ctx)}
54
+ Name
55
+ {/snippet}
56
+ {#snippet row(row, ctx)}
57
+ {row.name}
58
+ {/snippet}
59
+ {#snippet statusbar(ctx)}
60
+ {table.data.length}
61
+ {/snippet}
62
+ </Column>
63
+
64
+ <!-- Simplified -->
65
+ <Column id='age' header='Age' value={r => r.age} sort={(a,b) => a - b} />
66
+
67
+ <Expandable click={false}>
68
+ {#snippet content(item, ctx)}
69
+ ...
70
+ {/snippet}
71
+ </Expandable>
72
+
73
+ <Row onclick={...} oncontextmenu={...}>
74
+ {#snippet contextHeader()}
75
+ <button ...> <Icon icon='add' /> </button>
76
+ {/snippet}
77
+ {#snippet context(item, ctx)}
78
+ <button ...> <Icon icon='menu' /> </button>
79
+ {/snippet}
80
+ </Row>
81
+
82
+ <Panel id='columns'>
83
+ <!-- Anything you might like -->
84
+ </Panel>
85
+ <Panel ... backdrop={false}>
86
+ ...
87
+ </Panel>
88
+ {/snippet}
89
+ </Table>
90
+ ```
91
+
92
+ ### Styling
93
+
94
+ For quick styling
95
+
96
+ | CSS Variable | Description | Default |
97
+ | - | - | - |
98
+ | --tably-bg | Background color | `hsl(0, 0%, 100%)` |
99
+ | --tably-color | Text color | `hsl(0, 0%, 0%)` |
100
+ | --tably-border | Border for sticky columns and header | `hsl(0, 0%, 90%)` |
101
+ | --tably-border-grid | Border for the table-grid | `hsl(0, 0%, 98%)` |
102
+ | --tably-statusbar | background-color for the statusbar | `hsl(0, 0%, 98%)` |
103
+ | --tably-padding-y | Padding above/below each column | `.5rem` |
104
+ | --tably-padding-x | Padding left of each column | `1rem` |
105
+ | --tably-radius | Table radius | `.25rem` |
106
+
107
+ > [!TIP]
108
+ > For the CSS variables, apply them to `:global(:root) { ... }`
109
+
110
+ > [!NOTE]
111
+ > Advanced styling can be done via `:global(.svelte-tably)`
112
+ > `table > thead > tr > th, table > tbody > tr > td, table > tfoot > tr > td`
113
+
114
+ <br>
115
+ <br>
116
+
117
+ ## Components
118
+
119
+ All components except Table are meant to be children of the `Table` component.
120
+
121
+ However, you can safely create a `Component.svelte` and use these components,
122
+ and then provide `<Component/>` as a child to `<Table>`.
123
+
124
+ ```ts
125
+ import Table from 'svelte-tably'
126
+ ```
127
+
128
+ ### Table
129
+
130
+ The table component.
131
+
132
+ ```html
133
+ <Table auto {data} />
134
+
135
+ <Table {data} ...>
136
+ {#snippet content?({ Column, Row, Expandable, Panel, table })}
137
+ ...
138
+ {/snippet}
139
+ </Table>
140
+ ```
141
+
142
+ Where `table` is `TableState<T>` and the rest are typed; `Component<T>`.
143
+
144
+ | Attribute | Description | Type |
145
+ | - | - | - |
146
+ | content? | The contents of the table | `Snippet<[ctx: ContentCtx<T>]>?` |
147
+ |   | | |
148
+ | id? | The `#id` for the table | `string` |
149
+ | data | An array of objects for the table | `T[]` |
150
+ | bind:selected? | The currently selected items | `T[]` |
151
+ | bind:panel? | The currently open panel | `string` |
152
+ | filters? | An array of filters applied to the table | `((item: T) => boolean)[]` |
153
+ | reorderable? | Whether the rows can be re-ordered (via [runic-reorder](https://github.com/Refzlund/runic-reorder)) | `boolean` |
154
+ | resizeable? | Whether or not the columns can be resized | `boolean` |
155
+ | select? | Whether ot not the rows items can be selected | `boolean \| SelectOptions<T>` |
156
+ | auto? | Create missing columns automatically? | `boolean` |
157
+
158
+ #### SelectOptions
159
+
160
+ | Properties | Description | Type |
161
+ | - | - | - |
162
+ | show? | When to show the row-select when not selected | `'hover' \| 'always' \| 'never'` |
163
+ | headerSnippet? | Custom snippet for the header select-input | `Snippet<[context: HeaderSelectCtx]>` |
164
+ | rowSnippet? | Custom snippet for the row select-input | `Snippet<[context: RowSelectCtx<T>]>` |
165
+
166
+ <br>
167
+
168
+ ### Column
169
+
170
+ ```ts
171
+ import { Column } from 'svelte-tably'
172
+ ```
173
+
174
+ This component designates a column where options like sorting, filtering etc. are provided.
175
+
176
+ ```html
177
+ <Column id='...' header='...' value={row => row.value} />
178
+
179
+ <Column id='...' ...>
180
+ {#snippet header?(ctx: HeaderCtx<T>)}
181
+ ...
182
+ {/snippet}
183
+ {#snippet row?(item: T, ctx: RowColumnCtx<T>)}
184
+ ...
185
+ {/snippet}
186
+ {#snippet statusbar?(ctx: StatusbarCtx<T>)}
187
+ ...
188
+ {/snippet}
189
+ </Column>
190
+ ```
191
+
192
+ | Attribute | Description | Type |
193
+ | - | - | - |
194
+ | header? | The header element/contents | `string \| Snippet<[ctx: HeaderCtx<T>]>` |
195
+ | row? | The row element. If not provided, `value: V` will be used. | `Snippet<[item: T, ctx: RowColumnCtx<T, V>]>` |
196
+ | statusbar? | The statusbar element | `Snippet<[ctx: StatusbarCtx<T>]>` |
197
+ |   | | |
198
+ | sticky? | Should be sticky by default | `boolean` |
199
+ | show? | Should be visible by default | `boolean` |
200
+ | sortby? | Should sort by this by default | `boolean` |
201
+ | width? | Default width | `number` |
202
+ | value? | The value this column contains | `(item: T) => V` |
203
+ | sort? | A boolean (`localeCompare`) or sorting function | `boolean \| ((a: V, b: V) => number)` |
204
+ | resizeable? | Whether this column is resizeable | `boolean` |
205
+ | filter? | A filter for this columns value | `(item: V) => boolean` |
206
+ | style? | Styling the `td` (row-column) element | `string` |
207
+ | pad? | Apply padding to the child-element of `td`/`th` instead of the column element itself | `'row' \| 'header' \| 'both'` |
208
+ | onclick? | When the column is clicked | `(event: MouseEvent, ctx: RowColumnCtx<T, V>) => void` |
209
+
210
+ <br>
211
+
212
+ ### Row
213
+
214
+ ```ts
215
+ import { Row } from 'svelte-tably'
216
+ ```
217
+
218
+ This component can add a context-menu on the side of each row, as well as provide event handlers to the row element.
219
+
220
+ ```html
221
+ <Row ... />
222
+
223
+ <Row ...>
224
+ {#snippet context?(item: T, ctx: RowCtx<T>)}
225
+ ...
226
+ {/snippet}
227
+ {#snippet contextHeader?()}
228
+ ...
229
+ {/snippet}
230
+ </Row>
231
+ ```
232
+
233
+ | Attribute | Description | Type |
234
+ | - | - | - |
235
+ | context? | A sticky column on the right for each row | `Snippet<[item: T, ctx: RowCtx<T>]>` |
236
+ | contextHeader? | A sticky column on the right for the header | `Snippet<[item: T, ctx: RowCtx<T>]>` |
237
+ |   | | |
238
+ | contextOptions? | Options for the Context-column | `ContextOptions<T>` |
239
+ | onclick? | When row is clicked | `(event: MouseEvent, ctx: RowCtx<T>) => void` |
240
+ | oncontextmenu? | When row is right-clicked | `(event: MouseEvent, ctx: RowCtx<T>) => void` |
241
+
242
+ #### ContextOptions
243
+
244
+ | Properties | Description | Type |
245
+ | - | - | - |
246
+ | hover? | Only show when hovering? | `boolean` |
247
+ | width? | The width for the context-column | `string` |
248
+
249
+ <br>
250
+
251
+ ### Expandable
252
+
253
+ ```ts
254
+ import { Expandable } from 'svelte-tably'
255
+ ```
256
+
257
+ This component gives your rows the ability to be expanded.
258
+
259
+ ```html
260
+ <Expandable ...>
261
+ {#snippet content(item: T, ctx: RowCtx<T>)}
262
+ ...
263
+ {/snippet}
264
+ </Expandable>
265
+ ```
266
+
267
+ | Attribute | Description | Type |
268
+ | - | - | - |
269
+ | content | The contents of the expanded row. | `Snippet<[item: T, ctx: RowCtx<T>]>` |
270
+ |   | | |
271
+ | slide? | Options for sliding the expanding part | `{ duration?: number, easing?: EasingFunction }` |
272
+ | click? | Whether you can click on a row to expand/collapse it | `boolean` |
273
+ | chevron? | Whether to show the chevron on the left fixed column | `'always' \| 'hover' \| 'never'` |
274
+ | multiple? | Can multiple rows be open at the same time? | `boolean` |
275
+
276
+ <br>
277
+
278
+ ### Panel
279
+
280
+ ```ts
281
+ import { Panel } from 'svelte-tably'
282
+ ```
283
+
284
+ This component creates a panel that can be opened on the side of the table.
285
+
286
+ ```html
287
+ <Panel id='...' ...>
288
+ {#snippet children(ctx: PanelCtx<T>)}
289
+ ...
290
+ {/snippet}
291
+ </Panel>
292
+ ```
293
+
294
+ | Attribute | Description | Type |
295
+ | - | - | - |
296
+ | children | The contents of the panel | `Snippet<[ctx: PanelCtx<T>]>` |
297
+ |   | | |
298
+ | id | The id for the panel that determines whether it's open or closed, from the Table attribute | `string` |
299
+ | backdrop? | Whether there should be a backdrop or not | `boolean` |
@@ -0,0 +1,41 @@
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 { 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'
16
+
17
+ type T = $$Generic<Record<PropertyKey, any>>
18
+ type V = $$Generic
19
+
20
+ let {...props}: ColumnProps<T, V> = $props()
21
+ const properties = fromProps(props)
22
+
23
+ new ColumnState<T, V>(properties)
24
+
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}
@@ -0,0 +1,27 @@
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';
4
+ declare class __sveltets_Render<T extends Record<PropertyKey, any>, V> {
5
+ props(): ColumnProps<T_1, V_1>;
6
+ events(): {};
7
+ slots(): {};
8
+ bindings(): "";
9
+ exports(): {};
10
+ }
11
+ interface $$IsomorphicComponent {
12
+ new <T extends Record<PropertyKey, any>, V>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T, V>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T, V>['props']>, ReturnType<__sveltets_Render<T, V>['events']>, ReturnType<__sveltets_Render<T, V>['slots']>> & {
13
+ $$bindings?: ReturnType<__sveltets_Render<T, V>['bindings']>;
14
+ } & ReturnType<__sveltets_Render<T, V>['exports']>;
15
+ <T extends Record<PropertyKey, any>, V>(internal: unknown, props: ReturnType<__sveltets_Render<T, V>['props']> & {}): ReturnType<__sveltets_Render<T, V>['exports']>;
16
+ z_$$bindings?: ReturnType<__sveltets_Render<any, any>['bindings']>;
17
+ }
18
+ /**
19
+ * This is a description, \
20
+ * on how to use this.
21
+ *
22
+ * @example
23
+ * <Component />
24
+ */
25
+ declare const Column: $$IsomorphicComponent;
26
+ type Column<T extends Record<PropertyKey, any>, V> = InstanceType<typeof Column<T, V>>;
27
+ export default Column;
@@ -0,0 +1,64 @@
1
+ import { TableState } from '../table/table.svelte.js';
2
+ import { getDefaultHeader } from './Column.svelte';
3
+ export class ColumnState {
4
+ #props = {};
5
+ id = $derived(this.#props.id);
6
+ /**
7
+ * Associated table
8
+ */
9
+ table;
10
+ snippets = $derived({
11
+ header: typeof this.#props.header === 'string' ? getDefaultHeader(this.#props.header) : this.#props.header,
12
+ /** Title is the header-snippet, with header-ctx: `{ header: false }` */
13
+ title: (...args) => {
14
+ const getData = () => this.table.dataState.current;
15
+ return this.snippets.header?.(...[args[0], () => ({
16
+ get header() { return false; },
17
+ get data() {
18
+ return getData();
19
+ }
20
+ })]);
21
+ },
22
+ row: this.#props.row,
23
+ statusbar: this.#props.statusbar
24
+ });
25
+ /**
26
+ * Variables that can be saved (e.g. localStorage)
27
+ * and re-provided, where these are default-fallbacks
28
+ */
29
+ defaults = $derived({
30
+ sticky: this.#props.sticky ?? false,
31
+ show: this.#props.show ?? true,
32
+ sortby: this.#props.sortby ?? false,
33
+ width: this.#props.width ?? 150
34
+ });
35
+ /** Static options */
36
+ options = $derived({
37
+ fixed: this.#props.fixed ?? false,
38
+ sort: this.#props.sort ?? false,
39
+ filter: this.#props.filter,
40
+ value: this.#props.value,
41
+ resizeable: this.#props.resizeable ?? true,
42
+ style: this.#props.style,
43
+ class: this.#props.class,
44
+ onclick: this.#props.onclick,
45
+ padRow: this.#props.pad === 'row' || this.#props.pad === 'both',
46
+ padHeader: this.#props.pad === 'header' || this.#props.pad === 'both'
47
+ });
48
+ toggleVisiblity() {
49
+ const index = this.table.positions.hidden.indexOf(this);
50
+ if (index > -1)
51
+ this.table.positions.hidden.splice(index, 1);
52
+ else
53
+ this.table.positions.hidden.push(this);
54
+ }
55
+ constructor(props) {
56
+ this.#props = props;
57
+ this.table = props.table ?? TableState.getContext();
58
+ if (!this.table) {
59
+ throw new Error('svelte-tably: Column must be associated with a Table');
60
+ }
61
+ const remove = this.table.add(this);
62
+ $effect(() => remove);
63
+ }
64
+ }
@@ -0,0 +1,10 @@
1
+ type Actionable<A extends unknown | unknown[]> = {
2
+ destroy?: () => void;
3
+ update?: (args: A) => void;
4
+ };
5
+ export declare function conditional<A extends unknown | unknown[]>(node: HTMLElement, arg: [
6
+ condition: (() => boolean) | boolean,
7
+ action: ((node: HTMLElement, args?: A) => void | Actionable<A>),
8
+ args?: A
9
+ ]): void;
10
+ export {};
@@ -0,0 +1,26 @@
1
+ import { untrack } from 'svelte';
2
+ /*
3
+ use:conditional={[condition, action, arg]}
4
+ use:conditional={[condition, (node) => action(node, arg), updatedArg]}
5
+ */
6
+ export function conditional(node, arg) {
7
+ let prev;
8
+ let clean;
9
+ $effect(() => {
10
+ prev?.destroy?.();
11
+ clean?.();
12
+ if (typeof arg[0] === 'function' ? arg[0]() : arg[0]) {
13
+ untrack(() => {
14
+ prev = arg[1](node, arg[2]);
15
+ if (prev?.update) {
16
+ clean = $effect.root(() => {
17
+ $effect(() => {
18
+ prev.update(arg[2]);
19
+ });
20
+ });
21
+ }
22
+ });
23
+ return clean;
24
+ }
25
+ });
26
+ }
@@ -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 { ExpandableState, type ExpandableProps } from './expandable.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 }: ExpandableProps<T> = $props()
20
+
21
+ const properties = fromProps(restProps)
22
+ new ExpandableState<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(): ExpandableProps<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 Expandable: $$IsomorphicComponent;
24
+ type Expandable<T extends AnyRecord> = InstanceType<typeof Expandable<T>>;
25
+ export default Expandable;
@@ -0,0 +1,27 @@
1
+ import { TableState } from '../table/table.svelte.js';
2
+ import { sineInOut } from 'svelte/easing';
3
+ export class ExpandableState {
4
+ #table;
5
+ #props = {};
6
+ snippets = $derived({
7
+ content: this.#props.content
8
+ });
9
+ options = $derived({
10
+ slide: {
11
+ duration: this.#props.slide?.duration ?? 150,
12
+ easing: this.#props.slide?.easing ?? sineInOut
13
+ },
14
+ click: this.#props.click ?? true,
15
+ chevron: this.#props.chevron ?? 'hover',
16
+ multiple: this.#props.multiple ?? false
17
+ });
18
+ constructor(props) {
19
+ this.#props = props;
20
+ this.#table = TableState.getContext();
21
+ if (!this.#table) {
22
+ throw new Error('svelte-tably: Expandable must be associated with a Table');
23
+ }
24
+ this.#table.expandable = this;
25
+ $effect(() => () => this.#table.expandable === this && (this.#table.expandable = undefined));
26
+ }
27
+ }