svelte-tably 1.2.0 → 1.3.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/README.md +2 -1
- package/dist/row/row-state.svelte.d.ts +10 -1
- package/dist/row/row-state.svelte.js +3 -2
- package/dist/table/Table.svelte +170 -39
- package/dist/table/table-state.svelte.js +67 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
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
4
|
|
|
5
|
-
Simple example on [Svelte 5 Playground](https://svelte.dev/playground/f79124e8473546d29433a95a68440d6d?version=5
|
|
5
|
+
Simple example on [Svelte 5 Playground](https://svelte.dev/playground/f79124e8473546d29433a95a68440d6d?version=5)
|
|
6
6
|
<br>
|
|
7
7
|
Fledged out example on [Svelte 5 Playground](https://svelte.dev/playground/a16d71c97445455e80a55b77ec1cf915?version=5)
|
|
8
8
|
|
|
@@ -255,6 +255,7 @@ This component can add a context-menu on the side of each row, as well as provid
|
|
|
255
255
|
| - | - | - |
|
|
256
256
|
| hover? | Only show when hovering? | `boolean` |
|
|
257
257
|
| width? | The width for the context-column | `string` |
|
|
258
|
+
| alignHeaderToRows? | If enabled, the header and row context cells share the same measured width | `boolean` |
|
|
258
259
|
|
|
259
260
|
<br>
|
|
260
261
|
|
|
@@ -10,9 +10,17 @@ type ContextOptions<T> = {
|
|
|
10
10
|
*/
|
|
11
11
|
hover?: boolean;
|
|
12
12
|
/**
|
|
13
|
-
* @
|
|
13
|
+
* @default 'max-content'
|
|
14
14
|
*/
|
|
15
15
|
width?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Align the header context cell (if any) with the row context cell.
|
|
18
|
+
*
|
|
19
|
+
* When enabled, the table measures the rendered context cell width
|
|
20
|
+
* (from header and rows) and uses a shared fixed width so they line up.
|
|
21
|
+
* @default false
|
|
22
|
+
*/
|
|
23
|
+
alignHeaderToRows?: boolean;
|
|
16
24
|
};
|
|
17
25
|
export interface RowProps<T> {
|
|
18
26
|
/**
|
|
@@ -38,6 +46,7 @@ export declare class RowState<T> {
|
|
|
38
46
|
context: {
|
|
39
47
|
hover: boolean;
|
|
40
48
|
width: string;
|
|
49
|
+
alignHeaderToRows: boolean;
|
|
41
50
|
};
|
|
42
51
|
};
|
|
43
52
|
constructor(props: RowProps<T>);
|
|
@@ -13,14 +13,15 @@ export class RowState {
|
|
|
13
13
|
options = $derived({
|
|
14
14
|
context: {
|
|
15
15
|
hover: this.#props.contextOptions?.hover ?? true,
|
|
16
|
-
width: this.#props.contextOptions?.width ?? 'max-content'
|
|
16
|
+
width: this.#props.contextOptions?.width ?? 'max-content',
|
|
17
|
+
alignHeaderToRows: this.#props.contextOptions?.alignHeaderToRows ?? false
|
|
17
18
|
}
|
|
18
19
|
});
|
|
19
20
|
constructor(props) {
|
|
20
21
|
this.#props = props;
|
|
21
22
|
this.#table = TableState.getContext();
|
|
22
23
|
if (!this.#table) {
|
|
23
|
-
throw new Error('svelte-tably:
|
|
24
|
+
throw new Error('svelte-tably: Row must be associated with a Table');
|
|
24
25
|
}
|
|
25
26
|
this.#table.row = this;
|
|
26
27
|
$effect(() => () => this.#table.row === this && (this.#table.row = undefined));
|
package/dist/table/Table.svelte
CHANGED
|
@@ -149,6 +149,65 @@
|
|
|
149
149
|
const getWidth = (key: string, def: number = 150) =>
|
|
150
150
|
table.columnWidths[key] ??= table.columns[key]?.defaults.width ?? def
|
|
151
151
|
|
|
152
|
+
const measureContextCellWidth = (cell: HTMLElement | null) => {
|
|
153
|
+
if (!cell) return 0
|
|
154
|
+
const inner = cell.querySelector(':scope > .context-inner') as HTMLElement | null
|
|
155
|
+
const content = inner?.firstElementChild as HTMLElement | null
|
|
156
|
+
const candidates = [cell, inner, content].filter(Boolean) as HTMLElement[]
|
|
157
|
+
let width = 0
|
|
158
|
+
for (const el of candidates) {
|
|
159
|
+
width = Math.max(
|
|
160
|
+
width,
|
|
161
|
+
Math.ceil(el.getBoundingClientRect().width),
|
|
162
|
+
Math.ceil(el.scrollWidth)
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
return width
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
let contextWidth = $state(0)
|
|
169
|
+
let contextWidthRaf = 0
|
|
170
|
+
$effect(() => {
|
|
171
|
+
if (!mount.isMounted) {
|
|
172
|
+
contextWidth = 0
|
|
173
|
+
if (contextWidthRaf) cancelAnimationFrame(contextWidthRaf)
|
|
174
|
+
return
|
|
175
|
+
}
|
|
176
|
+
if (!table.row?.snippets.context) {
|
|
177
|
+
contextWidth = 0
|
|
178
|
+
if (contextWidthRaf) cancelAnimationFrame(contextWidthRaf)
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
if (!table.row?.options.context.alignHeaderToRows) {
|
|
182
|
+
contextWidth = 0
|
|
183
|
+
if (contextWidthRaf) cancelAnimationFrame(contextWidthRaf)
|
|
184
|
+
return
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
virtualization.topIndex
|
|
188
|
+
if (contextWidthRaf) cancelAnimationFrame(contextWidthRaf)
|
|
189
|
+
contextWidthRaf = requestAnimationFrame(() => {
|
|
190
|
+
const headerCell = elements.headers?.querySelector(
|
|
191
|
+
'[data-tably-context-measure="header"]'
|
|
192
|
+
) as HTMLElement | null
|
|
193
|
+
const rowCell = virtualization.viewport.element?.querySelector(
|
|
194
|
+
'[data-tably-context-measure="row"]'
|
|
195
|
+
) as HTMLElement | null
|
|
196
|
+
|
|
197
|
+
const width = Math.max(
|
|
198
|
+
measureContextCellWidth(headerCell),
|
|
199
|
+
measureContextCellWidth(rowCell)
|
|
200
|
+
)
|
|
201
|
+
if (width > 0 && width !== contextWidth) {
|
|
202
|
+
contextWidth = width
|
|
203
|
+
}
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
return () => {
|
|
207
|
+
if (contextWidthRaf) cancelAnimationFrame(contextWidthRaf)
|
|
208
|
+
}
|
|
209
|
+
})
|
|
210
|
+
|
|
152
211
|
/** grid-template-columns for widths */
|
|
153
212
|
let style = $state('')
|
|
154
213
|
$effect(() => {
|
|
@@ -157,7 +216,7 @@
|
|
|
157
216
|
return
|
|
158
217
|
}
|
|
159
218
|
|
|
160
|
-
const context = table.row?.snippets.context ?
|
|
219
|
+
const context = table.row?.snippets.context ? ' var(--tably-context-width)' : ''
|
|
161
220
|
|
|
162
221
|
const templateColumns =
|
|
163
222
|
columns
|
|
@@ -177,6 +236,7 @@
|
|
|
177
236
|
|
|
178
237
|
const tbodyTemplateColumns = `
|
|
179
238
|
[data-area-class='${table.cssId}'] tr.row,
|
|
239
|
+
[data-area-class='${table.cssId}'] tr.expandable,
|
|
180
240
|
[data-area-class='${table.cssId}'] tr.filler,
|
|
181
241
|
[data-svelte-tably="${table.cssId}"] > tbody::after {
|
|
182
242
|
grid-template-columns: ${templateColumns};
|
|
@@ -242,7 +302,8 @@
|
|
|
242
302
|
}
|
|
243
303
|
|
|
244
304
|
let tbody = $state({
|
|
245
|
-
scrollbar: 0
|
|
305
|
+
scrollbar: 0,
|
|
306
|
+
viewportWidth: 0
|
|
246
307
|
})
|
|
247
308
|
|
|
248
309
|
function observeScrollbar(node: HTMLElement) {
|
|
@@ -251,6 +312,7 @@
|
|
|
251
312
|
const update = () => {
|
|
252
313
|
// Reserve the same gutter in header/footer as the scrollable body
|
|
253
314
|
tbody.scrollbar = Math.max(0, node.offsetWidth - node.clientWidth)
|
|
315
|
+
tbody.viewportWidth = node.clientWidth
|
|
254
316
|
}
|
|
255
317
|
|
|
256
318
|
update()
|
|
@@ -590,9 +652,16 @@
|
|
|
590
652
|
return table.selected?.includes(item)
|
|
591
653
|
},
|
|
592
654
|
set selected(value) {
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
655
|
+
const current = table.selected
|
|
656
|
+
if (value) {
|
|
657
|
+
if (!current.includes(item)) {
|
|
658
|
+
table.selected = [...current, item]
|
|
659
|
+
}
|
|
660
|
+
return
|
|
661
|
+
}
|
|
662
|
+
if (current.includes(item)) {
|
|
663
|
+
table.selected = current.filter((v) => v !== item)
|
|
664
|
+
}
|
|
596
665
|
},
|
|
597
666
|
get itemState() {
|
|
598
667
|
return itemState
|
|
@@ -619,10 +688,8 @@
|
|
|
619
688
|
use:addRowEvents={ctx}
|
|
620
689
|
onclick={(e) => {
|
|
621
690
|
if (table.expandable?.options.click === true) {
|
|
622
|
-
|
|
623
|
-
if (
|
|
624
|
-
return
|
|
625
|
-
}
|
|
691
|
+
const target = e.target
|
|
692
|
+
if (target instanceof Element && target.closest('input, textarea, button, a')) return
|
|
626
693
|
ctx.expanded = !ctx.expanded
|
|
627
694
|
}
|
|
628
695
|
}}
|
|
@@ -651,8 +718,16 @@
|
|
|
651
718
|
<td
|
|
652
719
|
class="context-col"
|
|
653
720
|
class:hidden={table.row?.options.context.hover && hoveredRow !== item}
|
|
721
|
+
data-tably-context-measure={
|
|
722
|
+
table.row?.options.context.alignHeaderToRows &&
|
|
723
|
+
index === virtualization.topIndex ?
|
|
724
|
+
'row'
|
|
725
|
+
: undefined
|
|
726
|
+
}
|
|
654
727
|
>
|
|
655
|
-
|
|
728
|
+
<div class="context-inner">
|
|
729
|
+
{@render table.row?.snippets.context?.(item, ctx)}
|
|
730
|
+
</div>
|
|
656
731
|
</td>
|
|
657
732
|
{/if}
|
|
658
733
|
</tr>
|
|
@@ -668,25 +743,28 @@
|
|
|
668
743
|
{@const expandLabelId = `${expandId}-label`}
|
|
669
744
|
<tr class="expandable">
|
|
670
745
|
<td
|
|
746
|
+
class="expandable-cell"
|
|
671
747
|
colspan={columns.length + (table.row?.snippets.context ? 1 : 0)}
|
|
672
748
|
style="padding: 0"
|
|
673
749
|
>
|
|
674
|
-
<div
|
|
675
|
-
class="expandable-clip"
|
|
676
|
-
style="height: {Math.round(expandableTween.current)}px"
|
|
677
|
-
id={expandId}
|
|
678
|
-
role="region"
|
|
679
|
-
aria-labelledby={expandLabelId}
|
|
680
|
-
aria-hidden={!expanded}
|
|
681
|
-
>
|
|
682
|
-
<span class="sr-only" id={expandLabelId}>
|
|
683
|
-
Expanded content for {getRowLabel(item, index)}
|
|
684
|
-
</span>
|
|
750
|
+
<div class="expandable-sticky">
|
|
685
751
|
<div
|
|
686
|
-
class="expandable-
|
|
687
|
-
|
|
752
|
+
class="expandable-clip"
|
|
753
|
+
style="height: {Math.round(expandableTween.current)}px"
|
|
754
|
+
id={expandId}
|
|
755
|
+
role="region"
|
|
756
|
+
aria-labelledby={expandLabelId}
|
|
757
|
+
aria-hidden={!expanded}
|
|
688
758
|
>
|
|
689
|
-
|
|
759
|
+
<span class="sr-only" id={expandLabelId}>
|
|
760
|
+
Expanded content for {getRowLabel(item, index)}
|
|
761
|
+
</span>
|
|
762
|
+
<div
|
|
763
|
+
class="expandable-content"
|
|
764
|
+
bind:offsetHeight={expandableTween.size}
|
|
765
|
+
>
|
|
766
|
+
{@render table.expandable?.snippets.content?.(item, ctx)}
|
|
767
|
+
</div>
|
|
690
768
|
</div>
|
|
691
769
|
</div>
|
|
692
770
|
</td>
|
|
@@ -698,7 +776,7 @@
|
|
|
698
776
|
id={table.id}
|
|
699
777
|
data-svelte-tably={table.cssId}
|
|
700
778
|
class="table svelte-tably"
|
|
701
|
-
style="--t: {virtualization.virtualTop}px; --b: {virtualization.virtualBottom}px; --scrollbar: {tbody.scrollbar}px;"
|
|
779
|
+
style="--t: {virtualization.virtualTop}px; --b: {virtualization.virtualBottom}px; --scrollbar: {tbody.scrollbar}px; --viewport-width: {tbody.viewportWidth}px; --tably-context-width: {table.row?.options.context.alignHeaderToRows && contextWidth > 0 ? `${contextWidth}px` : (table.row?.options.context.width ?? 'max-content')};"
|
|
702
780
|
aria-rowcount={table.data.length}
|
|
703
781
|
>
|
|
704
782
|
{#if columns.some((v) => v.snippets.header)}
|
|
@@ -721,11 +799,14 @@
|
|
|
721
799
|
{#if table.row?.snippets.context}
|
|
722
800
|
<th
|
|
723
801
|
class="context-col"
|
|
802
|
+
data-tably-context-measure={table.row?.options.context.alignHeaderToRows ? 'header' : undefined}
|
|
724
803
|
aria-hidden={table.row?.snippets.contextHeader ? undefined : true}
|
|
725
804
|
role={table.row?.snippets.contextHeader ? undefined : 'presentation'}
|
|
726
805
|
>
|
|
727
806
|
{#if table.row?.snippets.contextHeader}
|
|
728
|
-
|
|
807
|
+
<div class="context-inner">
|
|
808
|
+
{@render table.row?.snippets.contextHeader()}
|
|
809
|
+
</div>
|
|
729
810
|
{/if}
|
|
730
811
|
</th>
|
|
731
812
|
{/if}
|
|
@@ -963,7 +1044,7 @@
|
|
|
963
1044
|
{#each autoSchema.keys as key}
|
|
964
1045
|
<Column
|
|
965
1046
|
id={key}
|
|
966
|
-
|
|
1047
|
+
value={(r) => (r as any)?.[key]}
|
|
967
1048
|
header={capitalize(segmentize(key))}
|
|
968
1049
|
sort={typeof autoSchema.sample?.[key] === 'number' ?
|
|
969
1050
|
(a, b) => a - b
|
|
@@ -996,19 +1077,28 @@
|
|
|
996
1077
|
justify-content: center;
|
|
997
1078
|
position: sticky;
|
|
998
1079
|
right: 0;
|
|
999
|
-
height: 100%;
|
|
1000
1080
|
z-index: 3;
|
|
1001
1081
|
padding: 0;
|
|
1082
|
+
border-left: 1px solid var(--tably-border);
|
|
1002
1083
|
&.hidden {
|
|
1003
1084
|
pointer-events: none;
|
|
1004
1085
|
user-select: none;
|
|
1005
|
-
|
|
1006
|
-
>
|
|
1007
|
-
|
|
1086
|
+
border-left: none;
|
|
1087
|
+
> .context-inner {
|
|
1088
|
+
visibility: hidden;
|
|
1008
1089
|
}
|
|
1009
1090
|
}
|
|
1010
1091
|
}
|
|
1011
1092
|
|
|
1093
|
+
.context-inner {
|
|
1094
|
+
display: flex;
|
|
1095
|
+
align-items: center;
|
|
1096
|
+
justify-content: center;
|
|
1097
|
+
padding: calc(var(--tably-padding-y) / 2) 0;
|
|
1098
|
+
overflow: clip;
|
|
1099
|
+
width: 100%;
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1012
1102
|
.table::before {
|
|
1013
1103
|
content: '';
|
|
1014
1104
|
grid-area: headers;
|
|
@@ -1016,6 +1106,9 @@
|
|
|
1016
1106
|
align-self: stretch;
|
|
1017
1107
|
width: var(--scrollbar, 0px);
|
|
1018
1108
|
background-color: var(--tably-bg);
|
|
1109
|
+
border-bottom: 1px solid var(--tably-border);
|
|
1110
|
+
border-right: 1px solid var(--tably-border);
|
|
1111
|
+
margin-right: -1px;
|
|
1019
1112
|
pointer-events: none;
|
|
1020
1113
|
position: relative;
|
|
1021
1114
|
z-index: 4;
|
|
@@ -1028,6 +1121,8 @@
|
|
|
1028
1121
|
align-self: stretch;
|
|
1029
1122
|
width: var(--scrollbar, 0px);
|
|
1030
1123
|
background-color: var(--tably-statusbar);
|
|
1124
|
+
border-right: 1px solid var(--tably-border);
|
|
1125
|
+
margin-right: -1px;
|
|
1031
1126
|
pointer-events: none;
|
|
1032
1127
|
position: relative;
|
|
1033
1128
|
z-index: 4;
|
|
@@ -1061,11 +1156,28 @@
|
|
|
1061
1156
|
}
|
|
1062
1157
|
}
|
|
1063
1158
|
|
|
1159
|
+
.expandable-cell {
|
|
1160
|
+
grid-column: 1 / -1;
|
|
1161
|
+
display: block;
|
|
1162
|
+
min-width: 0;
|
|
1163
|
+
width: 100%;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
.expandable-sticky {
|
|
1167
|
+
position: sticky;
|
|
1168
|
+
left: 0;
|
|
1169
|
+
width: var(--viewport-width, 100%);
|
|
1170
|
+
min-width: 0;
|
|
1171
|
+
display: block;
|
|
1172
|
+
background-color: var(--tably-bg);
|
|
1173
|
+
z-index: 1;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1064
1176
|
.expandable-clip {
|
|
1065
1177
|
overflow: hidden;
|
|
1066
1178
|
width: 100%;
|
|
1067
1179
|
background-color: var(--tably-bg);
|
|
1068
|
-
|
|
1180
|
+
border-bottom: 1px solid var(--tably-border-grid);
|
|
1069
1181
|
}
|
|
1070
1182
|
|
|
1071
1183
|
.expandable-content {
|
|
@@ -1073,6 +1185,7 @@
|
|
|
1073
1185
|
width: 100%;
|
|
1074
1186
|
background-color: var(--tably-bg);
|
|
1075
1187
|
box-sizing: border-box;
|
|
1188
|
+
min-width: 0;
|
|
1076
1189
|
}
|
|
1077
1190
|
|
|
1078
1191
|
.expand-row {
|
|
@@ -1128,6 +1241,7 @@
|
|
|
1128
1241
|
align-items: center;
|
|
1129
1242
|
justify-content: center;
|
|
1130
1243
|
gap: 0.25rem;
|
|
1244
|
+
background-color: transparent;
|
|
1131
1245
|
position: absolute;
|
|
1132
1246
|
top: 0;
|
|
1133
1247
|
left: 0;
|
|
@@ -1136,6 +1250,10 @@
|
|
|
1136
1250
|
width: 100%;
|
|
1137
1251
|
}
|
|
1138
1252
|
|
|
1253
|
+
.__fixed > * {
|
|
1254
|
+
background-color: transparent;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1139
1257
|
thead {
|
|
1140
1258
|
position: relative;
|
|
1141
1259
|
}
|
|
@@ -1247,19 +1365,18 @@
|
|
|
1247
1365
|
overflow: hidden;
|
|
1248
1366
|
min-width: 0;
|
|
1249
1367
|
padding-right: var(--scrollbar, 0px);
|
|
1368
|
+
border-bottom: 1px solid var(--tably-border);
|
|
1250
1369
|
}
|
|
1251
1370
|
|
|
1252
1371
|
.headers > tr > .column {
|
|
1253
1372
|
width: auto !important;
|
|
1254
|
-
border-bottom: 1px solid var(--tably-border);
|
|
1255
1373
|
}
|
|
1256
|
-
.headers > tr > .column
|
|
1257
|
-
.headers > tr > .context-col {
|
|
1258
|
-
border-bottom: 1px solid var(--tably-border);
|
|
1374
|
+
.headers > tr > .column:not(:first-child) {
|
|
1259
1375
|
border-left: 1px solid var(--tably-border-grid);
|
|
1260
1376
|
}
|
|
1261
1377
|
|
|
1262
1378
|
.headers > tr > .context-col {
|
|
1379
|
+
border-left: 1px solid var(--tably-border);
|
|
1263
1380
|
background-color: var(--tably-bg);
|
|
1264
1381
|
}
|
|
1265
1382
|
|
|
@@ -1300,7 +1417,7 @@
|
|
|
1300
1417
|
}
|
|
1301
1418
|
.statusbar > tr > .context-col {
|
|
1302
1419
|
border-top: 1px solid var(--tably-border);
|
|
1303
|
-
border-left: 1px solid var(--tably-border
|
|
1420
|
+
border-left: 1px solid var(--tably-border);
|
|
1304
1421
|
}
|
|
1305
1422
|
|
|
1306
1423
|
.statusbar > tr > .context-col {
|
|
@@ -1345,14 +1462,28 @@
|
|
|
1345
1462
|
}
|
|
1346
1463
|
}
|
|
1347
1464
|
|
|
1465
|
+
.row > .context-col {
|
|
1466
|
+
background-color: var(--tably-bg);
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
.row > .context-col.hidden {
|
|
1470
|
+
background-color: transparent;
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1348
1473
|
:global(#runic-drag .row) {
|
|
1349
1474
|
border: 1px solid var(--tably-border-grid);
|
|
1350
1475
|
border-top: 2px solid var(--tably-border-grid);
|
|
1351
1476
|
}
|
|
1352
1477
|
|
|
1353
|
-
.
|
|
1354
|
-
.
|
|
1478
|
+
.headers > tr > .column:not(:first-child),
|
|
1479
|
+
.row > .column:not(:first-child),
|
|
1480
|
+
.filler > .column:not(:first-child),
|
|
1481
|
+
.statusbar > tr > .column:not(:first-child) {
|
|
1355
1482
|
border-left: 1px solid var(--tably-border-grid);
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
.row,
|
|
1486
|
+
.filler {
|
|
1356
1487
|
border-bottom: 1px solid var(--tably-border-grid);
|
|
1357
1488
|
}
|
|
1358
1489
|
|
|
@@ -104,7 +104,23 @@ export class TableState {
|
|
|
104
104
|
}
|
|
105
105
|
/** Width of each column */
|
|
106
106
|
columnWidths = $state({});
|
|
107
|
+
#storageKey() {
|
|
108
|
+
if (!this.id)
|
|
109
|
+
return null;
|
|
110
|
+
return `svelte-tably:${this.id}`;
|
|
111
|
+
}
|
|
112
|
+
#getStorage() {
|
|
113
|
+
try {
|
|
114
|
+
return localStorage;
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
107
120
|
#save() {
|
|
121
|
+
const key = this.#storageKey();
|
|
122
|
+
if (!key)
|
|
123
|
+
return;
|
|
108
124
|
const content = {
|
|
109
125
|
columnWidths: this.columnWidths,
|
|
110
126
|
positions: {
|
|
@@ -116,24 +132,56 @@ export class TableState {
|
|
|
116
132
|
sortby: this.dataState.sortby,
|
|
117
133
|
sortReverse: this.dataState.sortReverse
|
|
118
134
|
};
|
|
119
|
-
|
|
135
|
+
const storage = this.#getStorage();
|
|
136
|
+
if (!storage)
|
|
137
|
+
return;
|
|
138
|
+
try {
|
|
139
|
+
storage.setItem(key, JSON.stringify(content));
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
120
144
|
}
|
|
121
145
|
#saving = false;
|
|
146
|
+
#saveTimeout = null;
|
|
122
147
|
#scheduleSave() {
|
|
123
148
|
if (this.#saving)
|
|
124
149
|
return;
|
|
125
|
-
if (
|
|
150
|
+
if (!this.#storageKey())
|
|
151
|
+
return;
|
|
152
|
+
if (!this.#getStorage())
|
|
126
153
|
return;
|
|
127
154
|
this.#saving = true;
|
|
128
|
-
|
|
155
|
+
if (this.#saveTimeout)
|
|
156
|
+
clearTimeout(this.#saveTimeout);
|
|
157
|
+
this.#saveTimeout = setTimeout(() => {
|
|
129
158
|
this.#saving = false;
|
|
130
159
|
this.#save();
|
|
131
160
|
}, 1000);
|
|
132
161
|
}
|
|
133
162
|
#load() {
|
|
134
|
-
|
|
163
|
+
const key = this.#storageKey();
|
|
164
|
+
if (!key)
|
|
165
|
+
return null;
|
|
166
|
+
const storage = this.#getStorage();
|
|
167
|
+
if (!storage)
|
|
135
168
|
return null;
|
|
136
|
-
|
|
169
|
+
let raw = null;
|
|
170
|
+
try {
|
|
171
|
+
raw = storage.getItem(key);
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
let item;
|
|
177
|
+
try {
|
|
178
|
+
item = JSON.parse(raw || '{}');
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
if (!item || typeof item !== 'object')
|
|
184
|
+
item = {};
|
|
137
185
|
item.columnWidths ??= {};
|
|
138
186
|
item.positions ??= {};
|
|
139
187
|
item.positions.fixed ??= [];
|
|
@@ -163,9 +211,20 @@ export class TableState {
|
|
|
163
211
|
this.dataState.sortReverse = saved.sortReverse;
|
|
164
212
|
}
|
|
165
213
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
214
|
+
$effect(() => {
|
|
215
|
+
if (typeof window === 'undefined')
|
|
216
|
+
return;
|
|
217
|
+
const handler = () => this.#save();
|
|
218
|
+
window.addEventListener('beforeunload', handler);
|
|
219
|
+
return () => window.removeEventListener('beforeunload', handler);
|
|
220
|
+
});
|
|
221
|
+
$effect(() => {
|
|
222
|
+
return () => {
|
|
223
|
+
if (this.#saveTimeout)
|
|
224
|
+
clearTimeout(this.#saveTimeout);
|
|
225
|
+
this.#saveTimeout = null;
|
|
226
|
+
};
|
|
227
|
+
});
|
|
169
228
|
$effect(() => {
|
|
170
229
|
Object.keys(this.columnWidths);
|
|
171
230
|
// Track order changes by observing the id sequences
|