@r2digisolutions/ui 0.21.3 → 0.22.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/dist/components/container/DataTable/DataTable.svelte +380 -0
- package/dist/components/container/DataTable/DataTable.svelte.d.ts +51 -0
- package/dist/components/container/DataTable/components/Cell.svelte +90 -0
- package/dist/components/container/DataTable/components/Cell.svelte.d.ts +30 -0
- package/dist/components/container/DataTable/components/ColumnVisibilityToggle.svelte +118 -0
- package/dist/components/container/DataTable/components/ColumnVisibilityToggle.svelte.d.ts +10 -0
- package/dist/components/container/DataTable/components/ContextMenu.svelte +203 -0
- package/dist/components/container/DataTable/components/ContextMenu.svelte.d.ts +21 -0
- package/dist/components/container/DataTable/components/FilterPanel.svelte +195 -0
- package/dist/components/container/DataTable/components/FilterPanel.svelte.d.ts +10 -0
- package/dist/components/container/DataTable/components/Pagination.svelte +59 -0
- package/dist/components/container/DataTable/components/Pagination.svelte.d.ts +11 -0
- package/dist/components/container/DataTable/components/QuickFilters.svelte +38 -0
- package/dist/components/container/DataTable/components/QuickFilters.svelte.d.ts +8 -0
- package/dist/components/container/DataTable/core/DataTableManager.svelte.d.ts +39 -0
- package/dist/components/container/DataTable/core/DataTableManager.svelte.js +245 -0
- package/dist/components/container/DataTable/core/filters/types.d.ts +18 -0
- package/dist/components/container/DataTable/core/filters/types.js +1 -0
- package/dist/components/container/DataTable/core/filters/utils.d.ts +3 -0
- package/dist/components/container/DataTable/core/filters/utils.js +43 -0
- package/dist/components/container/DataTable/core/types.d.ts +101 -0
- package/dist/components/container/DataTable/core/types.js +1 -0
- package/dist/components/container/DataTable/core/utils.d.ts +5 -0
- package/dist/components/container/DataTable/core/utils.js +66 -0
- package/dist/components/container/index.d.ts +2 -0
- package/dist/components/container/index.js +2 -0
- package/dist/components/ui/Card/Card.svelte +8 -8
- package/dist/components/ui/Card/CardContent.svelte +11 -3
- package/dist/components/ui/Card/CardContent.svelte.d.ts +8 -10
- package/dist/components/ui/Card/CardFooter.svelte +12 -2
- package/dist/components/ui/Card/CardFooter.svelte.d.ts +7 -3
- package/dist/components/ui/Card/CardHeader.svelte +10 -2
- package/dist/components/ui/Card/CardHeader.svelte.d.ts +7 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +27 -27
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { SvelteSet } from 'svelte/reactivity';
|
|
2
|
+
import { normalize, defaultAccessor, compareValues, applyFilterOp } from './utils.js';
|
|
3
|
+
export class DataTableManager {
|
|
4
|
+
options = $state({
|
|
5
|
+
perPage: 10,
|
|
6
|
+
perPageOptions: [10, 20, 50, 100],
|
|
7
|
+
multiSelect: true,
|
|
8
|
+
columns: [],
|
|
9
|
+
loadMode: 'local',
|
|
10
|
+
data: [],
|
|
11
|
+
});
|
|
12
|
+
state = $state({
|
|
13
|
+
ready: false,
|
|
14
|
+
items: [],
|
|
15
|
+
page: 1,
|
|
16
|
+
perPage: this.options.perPage,
|
|
17
|
+
total: 0,
|
|
18
|
+
sortBy: null,
|
|
19
|
+
sortDir: null,
|
|
20
|
+
filters: [],
|
|
21
|
+
visibleColumns: [],
|
|
22
|
+
hiddenColumns: [],
|
|
23
|
+
selected: new SvelteSet(),
|
|
24
|
+
loading: false,
|
|
25
|
+
error: undefined
|
|
26
|
+
});
|
|
27
|
+
measured = $state({});
|
|
28
|
+
reservedWidth = $state(0);
|
|
29
|
+
forcedVisible = new SvelteSet();
|
|
30
|
+
forcedHidden = new SvelteSet();
|
|
31
|
+
expanded = new SvelteSet();
|
|
32
|
+
lastWidth = $state(null);
|
|
33
|
+
constructor(opts) {
|
|
34
|
+
const columns = normalize(opts.columns);
|
|
35
|
+
this.options = ({
|
|
36
|
+
...opts,
|
|
37
|
+
columns
|
|
38
|
+
});
|
|
39
|
+
this.state = ({
|
|
40
|
+
...this.state,
|
|
41
|
+
perPage: this.options.perPage,
|
|
42
|
+
sortBy: opts.initialSortBy ?? null,
|
|
43
|
+
sortDir: opts.initialSortDir ?? null,
|
|
44
|
+
filters: opts.initialFilters ?? [],
|
|
45
|
+
visibleColumns: columns.map((c) => c.id),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
setMeasuredWidths(map) {
|
|
49
|
+
this.measured = map;
|
|
50
|
+
this.reflow();
|
|
51
|
+
}
|
|
52
|
+
get columns() { return this.options.columns; }
|
|
53
|
+
getColumn(id) { return this.columns.find((c) => c.id === id); }
|
|
54
|
+
isExpanded(id) { return this.expanded.has(id); }
|
|
55
|
+
toggleExpand(id) { this.isExpanded(id) ? this.expanded.delete(id) : this.expanded.add(id); }
|
|
56
|
+
setColumnVisibility(id, show) {
|
|
57
|
+
if (show) {
|
|
58
|
+
this.forcedVisible.add(id);
|
|
59
|
+
this.forcedHidden.delete(id);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
this.forcedHidden.add(id);
|
|
63
|
+
this.forcedVisible.delete(id);
|
|
64
|
+
}
|
|
65
|
+
this.reflow();
|
|
66
|
+
}
|
|
67
|
+
setReservedWidth(n) {
|
|
68
|
+
if (this.reservedWidth === n)
|
|
69
|
+
return;
|
|
70
|
+
this.reservedWidth = n;
|
|
71
|
+
this.reflow();
|
|
72
|
+
}
|
|
73
|
+
clearColumnOverrides() {
|
|
74
|
+
this.forcedVisible.clear();
|
|
75
|
+
this.forcedHidden.clear();
|
|
76
|
+
this.reflow();
|
|
77
|
+
}
|
|
78
|
+
visibilityPlan(containerWidth) {
|
|
79
|
+
const available = Math.max(0, Math.floor(containerWidth) - (this.reservedWidth ?? 0));
|
|
80
|
+
const cols = this.columns;
|
|
81
|
+
const origOrder = cols.map((c) => c.id);
|
|
82
|
+
const needOf = (id) => {
|
|
83
|
+
const c = this.getColumn(id);
|
|
84
|
+
const measured = this.measured[id];
|
|
85
|
+
const fallback = Math.max(c.minWidth ?? 0, c.width ?? 0, 160);
|
|
86
|
+
return Math.ceil(measured ?? fallback);
|
|
87
|
+
};
|
|
88
|
+
// columnas que siempre se ocultan en móvil, aunque quepan
|
|
89
|
+
const mustHide = new Set();
|
|
90
|
+
if (available < 640) {
|
|
91
|
+
for (const c of cols)
|
|
92
|
+
if (c.hideOnMobile)
|
|
93
|
+
mustHide.add(c.id);
|
|
94
|
+
}
|
|
95
|
+
// empezamos conservando el orden original
|
|
96
|
+
let visible = origOrder.filter((id) => !mustHide.has(id));
|
|
97
|
+
const totalNeed = () => visible.reduce((sum, id) => sum + needOf(id), 0);
|
|
98
|
+
if (totalNeed() > available) {
|
|
99
|
+
// hay que ocultar: orden de descarte por prioridad (más alta => se quita antes)
|
|
100
|
+
// empate: quitamos antes las columnas que están más a la derecha (idx más alto)
|
|
101
|
+
const dropOrder = cols
|
|
102
|
+
.map((c, idx) => ({ id: c.id, pr: c.priority ?? 999, idx }))
|
|
103
|
+
.filter((x) => !mustHide.has(x.id))
|
|
104
|
+
.sort((a, b) => (b.pr - a.pr) || (b.idx - a.idx));
|
|
105
|
+
const hidden = new Set([...mustHide]);
|
|
106
|
+
for (const d of dropOrder) {
|
|
107
|
+
if (totalNeed() <= available)
|
|
108
|
+
break;
|
|
109
|
+
const k = visible.indexOf(d.id);
|
|
110
|
+
if (k !== -1) {
|
|
111
|
+
visible.splice(k, 1);
|
|
112
|
+
hidden.add(d.id);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// overrides manuales
|
|
116
|
+
const visSet = new Set(visible);
|
|
117
|
+
for (const id of this.forcedHidden)
|
|
118
|
+
visSet.delete(id);
|
|
119
|
+
for (const id of this.forcedVisible)
|
|
120
|
+
if (!mustHide.has(id))
|
|
121
|
+
visSet.add(id);
|
|
122
|
+
const finalVisible = origOrder.filter((id) => visSet.has(id) && !mustHide.has(id));
|
|
123
|
+
const finalHidden = origOrder.filter((id) => !finalVisible.includes(id));
|
|
124
|
+
return { visible: finalVisible, hidden: finalHidden };
|
|
125
|
+
}
|
|
126
|
+
// TODO cabe: respetamos tu orden original
|
|
127
|
+
const visSet = new Set(visible);
|
|
128
|
+
for (const id of this.forcedHidden)
|
|
129
|
+
visSet.delete(id);
|
|
130
|
+
for (const id of this.forcedVisible)
|
|
131
|
+
if (!mustHide.has(id))
|
|
132
|
+
visSet.add(id);
|
|
133
|
+
const finalVisible = origOrder.filter((id) => visSet.has(id) && !mustHide.has(id));
|
|
134
|
+
const finalHidden = origOrder.filter((id) => !finalVisible.includes(id));
|
|
135
|
+
return { visible: finalVisible, hidden: finalHidden };
|
|
136
|
+
}
|
|
137
|
+
mergeWithOverrides(plan) {
|
|
138
|
+
const vis = new Set(plan.visible);
|
|
139
|
+
const hid = new Set(plan.hidden);
|
|
140
|
+
for (const id of this.forcedVisible) {
|
|
141
|
+
vis.add(id);
|
|
142
|
+
hid.delete(id);
|
|
143
|
+
}
|
|
144
|
+
for (const id of this.forcedHidden) {
|
|
145
|
+
hid.add(id);
|
|
146
|
+
vis.delete(id);
|
|
147
|
+
}
|
|
148
|
+
return { visible: [...vis], hidden: [...hid] };
|
|
149
|
+
}
|
|
150
|
+
applyVisibility(plan) {
|
|
151
|
+
this.state.visibleColumns = plan.visible;
|
|
152
|
+
this.state.hiddenColumns = plan.hidden;
|
|
153
|
+
}
|
|
154
|
+
// NUEVO: para usar desde el ResizeObserver
|
|
155
|
+
reflowForWidth(width) {
|
|
156
|
+
this.lastWidth = width;
|
|
157
|
+
const base = this.visibilityPlan(width);
|
|
158
|
+
this.applyVisibility(this.mergeWithOverrides(base));
|
|
159
|
+
}
|
|
160
|
+
// NUEVO: reflow con el último ancho conocido (o infinito si no hay)
|
|
161
|
+
reflow() {
|
|
162
|
+
const width = this.lastWidth ?? Number.POSITIVE_INFINITY;
|
|
163
|
+
const base = this.visibilityPlan(width);
|
|
164
|
+
this.applyVisibility(this.mergeWithOverrides(base));
|
|
165
|
+
}
|
|
166
|
+
// Selección / paginación / sort / filtros (sin cambios relevantes)
|
|
167
|
+
setPerPage(n) { this.state.perPage = n; this.state.page = 1; return this.load(); }
|
|
168
|
+
setPage(p) { this.state.page = p; return this.load(); }
|
|
169
|
+
setSort(id) {
|
|
170
|
+
if (this.state.sortBy !== id) {
|
|
171
|
+
this.state.sortBy = id;
|
|
172
|
+
this.state.sortDir = 'asc';
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
this.state.sortDir = this.state.sortDir === 'asc' ? 'desc' : (this.state.sortDir === 'desc' ? null : 'asc');
|
|
176
|
+
}
|
|
177
|
+
this.state.page = 1;
|
|
178
|
+
return this.load();
|
|
179
|
+
}
|
|
180
|
+
setFilters(filters) { this.state.filters = filters; this.state.page = 1; return this.load(); }
|
|
181
|
+
clearFilters() { this.state.filters = []; this.state.page = 1; return this.load(); }
|
|
182
|
+
toggleSelect(rowId) { const s = this.state.selected; s.has(rowId) ? s.delete(rowId) : s.add(rowId); }
|
|
183
|
+
clearSelection() { this.state.selected.clear(); }
|
|
184
|
+
selectAllCurrentPage(getId = (r) => r.id) { this.state.items.forEach((r) => this.state.selected.add(getId(r))); }
|
|
185
|
+
async load() {
|
|
186
|
+
const { loadMode } = this.options;
|
|
187
|
+
this.state.loading = true;
|
|
188
|
+
this.state.error = undefined;
|
|
189
|
+
try {
|
|
190
|
+
if (loadMode === 'local')
|
|
191
|
+
return await this.loadLocal();
|
|
192
|
+
else
|
|
193
|
+
return await this.loadRemote();
|
|
194
|
+
}
|
|
195
|
+
catch (e) {
|
|
196
|
+
this.state.error = e?.message ?? 'Error al cargar';
|
|
197
|
+
}
|
|
198
|
+
finally {
|
|
199
|
+
this.state.loading = false;
|
|
200
|
+
this.state.ready = true;
|
|
201
|
+
}
|
|
202
|
+
this.reflow(); // asegura visibilidad coherente tras cargar
|
|
203
|
+
}
|
|
204
|
+
async loadRemote() {
|
|
205
|
+
const { fetcher } = this.options;
|
|
206
|
+
if (!fetcher)
|
|
207
|
+
throw new Error('fetcher requerido para modo remoto/cursor');
|
|
208
|
+
const params = {
|
|
209
|
+
page: this.state.page,
|
|
210
|
+
perPage: this.state.perPage,
|
|
211
|
+
cursor: this.state.cursor,
|
|
212
|
+
sortBy: this.state.sortBy,
|
|
213
|
+
sortDir: this.state.sortDir,
|
|
214
|
+
filters: this.state.filters
|
|
215
|
+
};
|
|
216
|
+
const res = await fetcher(params);
|
|
217
|
+
this.state.items = res.items;
|
|
218
|
+
this.state.total = res.total;
|
|
219
|
+
this.state.cursor = res.nextCursor;
|
|
220
|
+
}
|
|
221
|
+
async loadLocal() {
|
|
222
|
+
const data = this.options.data ?? [];
|
|
223
|
+
let rows = [...data];
|
|
224
|
+
for (const f of this.state.filters) {
|
|
225
|
+
const col = f.columnId ? this.getColumn(f.columnId) : undefined;
|
|
226
|
+
rows = rows.filter((r) => {
|
|
227
|
+
const value = col ? (col.accessor ? col.accessor(r) : r[col.id]) : r;
|
|
228
|
+
return applyFilterOp(value, f.op, f.value);
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
if (this.state.sortBy && this.state.sortDir) {
|
|
232
|
+
const col = this.getColumn(this.state.sortBy);
|
|
233
|
+
rows.sort((a, b) => {
|
|
234
|
+
const va = col.accessor ? col.accessor(a) : defaultAccessor(a, col.id);
|
|
235
|
+
const vb = col.accessor ? col.accessor(b) : defaultAccessor(b, col.id);
|
|
236
|
+
const cmp = compareValues(va, vb);
|
|
237
|
+
return this.state.sortDir === 'asc' ? cmp : -cmp;
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
const start = (this.state.page - 1) * this.state.perPage;
|
|
241
|
+
const end = start + this.state.perPage;
|
|
242
|
+
this.state.total = rows.length;
|
|
243
|
+
this.state.items = rows.slice(start, end);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { FilterOp } from "../types.js";
|
|
2
|
+
export type FilterInputType = 'text' | 'select' | 'number' | 'range' | 'checkbox' | 'multiselect' | 'date' | 'rating';
|
|
3
|
+
export type SelectOption = {
|
|
4
|
+
label: string;
|
|
5
|
+
value: any;
|
|
6
|
+
};
|
|
7
|
+
export type FilterField<T> = {
|
|
8
|
+
id: string;
|
|
9
|
+
label: string;
|
|
10
|
+
type: FilterInputType;
|
|
11
|
+
columnId?: string;
|
|
12
|
+
options?: SelectOption[];
|
|
13
|
+
op?: FilterOp;
|
|
14
|
+
placeholder?: string;
|
|
15
|
+
min?: number;
|
|
16
|
+
max?: number;
|
|
17
|
+
step?: number;
|
|
18
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export function buildFilterDefs(fields, values) {
|
|
2
|
+
const defs = [];
|
|
3
|
+
for (const f of fields) {
|
|
4
|
+
const v = values[f.id];
|
|
5
|
+
if (v == null || v === '' || (Array.isArray(v) && v.length === 0))
|
|
6
|
+
continue;
|
|
7
|
+
if (f.type === 'range') {
|
|
8
|
+
const { min, max } = (v ?? {});
|
|
9
|
+
if (min != null)
|
|
10
|
+
defs.push({ id: `${f.id}_min`, label: f.label, columnId: f.columnId, op: 'gte', value: min, meta: { field: f.id, part: 'min' } });
|
|
11
|
+
if (max != null)
|
|
12
|
+
defs.push({ id: `${f.id}_max`, label: f.label, columnId: f.columnId, op: 'lte', value: max, meta: { field: f.id, part: 'max' } });
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
switch (f.type) {
|
|
16
|
+
case 'text':
|
|
17
|
+
defs.push({ id: f.id, label: f.label, columnId: f.columnId, op: f.op ?? 'contains', value: String(v) });
|
|
18
|
+
break;
|
|
19
|
+
case 'number':
|
|
20
|
+
defs.push({ id: f.id, label: f.label, columnId: f.columnId, op: f.op ?? 'equals', value: Number(v) });
|
|
21
|
+
break;
|
|
22
|
+
case 'date':
|
|
23
|
+
defs.push({ id: f.id, label: f.label, columnId: f.columnId, op: f.op ?? 'equals', value: v });
|
|
24
|
+
break;
|
|
25
|
+
case 'checkbox':
|
|
26
|
+
if (v === true)
|
|
27
|
+
defs.push({ id: f.id, label: f.label, columnId: f.columnId, op: f.op ?? 'equals', value: true });
|
|
28
|
+
break;
|
|
29
|
+
case 'select':
|
|
30
|
+
defs.push({ id: f.id, label: f.label, columnId: f.columnId, op: f.op ?? 'equals', value: v });
|
|
31
|
+
break;
|
|
32
|
+
case 'multiselect':
|
|
33
|
+
defs.push({ id: f.id, label: f.label, columnId: f.columnId, op: f.op ?? 'in', value: Array.isArray(v) ? v : [v] });
|
|
34
|
+
break;
|
|
35
|
+
case 'rating':
|
|
36
|
+
defs.push({ id: f.id, label: f.label, columnId: f.columnId, op: f.op ?? 'gte', value: Number(v) });
|
|
37
|
+
break;
|
|
38
|
+
default:
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return defs;
|
|
43
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
export type RowId = string | number;
|
|
2
|
+
export type SortDir = 'asc' | 'desc' | null;
|
|
3
|
+
export type ColumnKey<T> = Extract<keyof T, string>;
|
|
4
|
+
export type Accessor<T, R = any> = (row: T) => R;
|
|
5
|
+
export type ColumnType = 'text' | 'number' | 'currency' | 'date' | 'datetime' | 'boolean' | 'badge' | 'link' | 'code';
|
|
6
|
+
type BaseColumn<T> = {
|
|
7
|
+
header: string;
|
|
8
|
+
width?: number;
|
|
9
|
+
minWidth?: number;
|
|
10
|
+
priority?: number;
|
|
11
|
+
align?: 'left' | 'center' | 'right';
|
|
12
|
+
sortable?: boolean;
|
|
13
|
+
hideOnMobile?: boolean;
|
|
14
|
+
responsiveLabel?: string;
|
|
15
|
+
class?: string;
|
|
16
|
+
type?: ColumnType;
|
|
17
|
+
format?: Intl.NumberFormatOptions | Intl.DateTimeFormatOptions;
|
|
18
|
+
trueLabel?: string;
|
|
19
|
+
falseLabel?: string;
|
|
20
|
+
};
|
|
21
|
+
/** Columna ligada a una key existente de T (autocomplete de keys) */
|
|
22
|
+
export type KeyColumn<T, K extends ColumnKey<T> = ColumnKey<T>> = BaseColumn<T> & {
|
|
23
|
+
id: K;
|
|
24
|
+
accessor?: (row: T) => T[K];
|
|
25
|
+
renderCell?: (row: T) => any;
|
|
26
|
+
renderCollapsed?: (row: T) => any;
|
|
27
|
+
};
|
|
28
|
+
/** Columna virtual (id libre), requiere accessor explícito */
|
|
29
|
+
export type VirtualColumn<T> = BaseColumn<T> & {
|
|
30
|
+
id: string;
|
|
31
|
+
accessor: Accessor<T>;
|
|
32
|
+
renderCell?: (row: T) => any;
|
|
33
|
+
renderCollapsed?: (row: T) => any;
|
|
34
|
+
};
|
|
35
|
+
export type ColumnDef<T> = KeyColumn<T>;
|
|
36
|
+
export type FilterOp = 'equals' | 'not_equals' | 'contains' | 'starts_with' | 'ends_with' | 'gt' | 'lt' | 'gte' | 'lte' | 'in' | 'not_in' | 'is_empty' | 'is_not_empty';
|
|
37
|
+
export type FilterDef<T> = {
|
|
38
|
+
id: string;
|
|
39
|
+
label: string;
|
|
40
|
+
columnId?: string;
|
|
41
|
+
op: FilterOp;
|
|
42
|
+
value?: any;
|
|
43
|
+
meta?: Record<string, any>;
|
|
44
|
+
};
|
|
45
|
+
export type FetchResult<T> = {
|
|
46
|
+
items: T[];
|
|
47
|
+
total: number;
|
|
48
|
+
nextCursor?: string;
|
|
49
|
+
};
|
|
50
|
+
export type FetchParams = {
|
|
51
|
+
page: number;
|
|
52
|
+
perPage: number;
|
|
53
|
+
cursor?: string;
|
|
54
|
+
sortBy?: string | null;
|
|
55
|
+
sortDir?: SortDir;
|
|
56
|
+
filters: FilterDef<any>[];
|
|
57
|
+
};
|
|
58
|
+
export type LoadMode = 'local' | 'remote' | 'cursor';
|
|
59
|
+
export type TableOptions<T> = {
|
|
60
|
+
id?: string;
|
|
61
|
+
columns: ColumnDef<T>[];
|
|
62
|
+
loadMode: LoadMode;
|
|
63
|
+
data?: T[];
|
|
64
|
+
fetcher?: (params: FetchParams) => Promise<FetchResult<T>>;
|
|
65
|
+
perPage?: number;
|
|
66
|
+
perPageOptions?: number[];
|
|
67
|
+
multiSelect?: boolean;
|
|
68
|
+
keepSelectionOnPageChange?: boolean;
|
|
69
|
+
initialSortBy?: string | null;
|
|
70
|
+
initialSortDir?: SortDir;
|
|
71
|
+
initialFilters?: FilterDef<T>[];
|
|
72
|
+
};
|
|
73
|
+
export type TableState<T> = {
|
|
74
|
+
ready: boolean;
|
|
75
|
+
items: T[];
|
|
76
|
+
page: number;
|
|
77
|
+
perPage: number;
|
|
78
|
+
total: number;
|
|
79
|
+
cursor?: string;
|
|
80
|
+
sortBy: string | null;
|
|
81
|
+
sortDir: SortDir;
|
|
82
|
+
filters: FilterDef<T>[];
|
|
83
|
+
visibleColumns: string[];
|
|
84
|
+
hiddenColumns: string[];
|
|
85
|
+
selected: Set<RowId>;
|
|
86
|
+
loading: boolean;
|
|
87
|
+
error?: string;
|
|
88
|
+
};
|
|
89
|
+
export type VisibilityPlan = {
|
|
90
|
+
visible: string[];
|
|
91
|
+
hidden: string[];
|
|
92
|
+
};
|
|
93
|
+
export type CellContext<T> = {
|
|
94
|
+
row: T | null;
|
|
95
|
+
rowIndex: number | null;
|
|
96
|
+
columnId: string | null;
|
|
97
|
+
column: ColumnDef<T> | null;
|
|
98
|
+
columnIndex: number | null;
|
|
99
|
+
event: MouseEvent;
|
|
100
|
+
};
|
|
101
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ColumnDef, FilterOp } from './types.js';
|
|
2
|
+
export declare function normalize<T>(columns: ColumnDef<T>[]): ColumnDef<T>[];
|
|
3
|
+
export declare function defaultAccessor<T>(row: any, id: string): any;
|
|
4
|
+
export declare function compareValues(a: any, b: any): number;
|
|
5
|
+
export declare function applyFilterOp(value: any, op: FilterOp, target: any): boolean;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export function normalize(columns) {
|
|
2
|
+
return columns.map((c, i) => ({
|
|
3
|
+
width: 160,
|
|
4
|
+
minWidth: 96,
|
|
5
|
+
priority: i,
|
|
6
|
+
align: 'left',
|
|
7
|
+
sortable: true,
|
|
8
|
+
...c
|
|
9
|
+
}));
|
|
10
|
+
}
|
|
11
|
+
export function defaultAccessor(row, id) {
|
|
12
|
+
return row?.[id];
|
|
13
|
+
}
|
|
14
|
+
export function compareValues(a, b) {
|
|
15
|
+
if (a == null && b == null)
|
|
16
|
+
return 0;
|
|
17
|
+
if (a == null)
|
|
18
|
+
return -1;
|
|
19
|
+
if (b == null)
|
|
20
|
+
return 1;
|
|
21
|
+
if (typeof a === 'string' && typeof b === 'string')
|
|
22
|
+
return a.localeCompare(b);
|
|
23
|
+
if (a > b)
|
|
24
|
+
return 1;
|
|
25
|
+
if (a < b)
|
|
26
|
+
return -1;
|
|
27
|
+
return 0;
|
|
28
|
+
}
|
|
29
|
+
export function applyFilterOp(value, op, target) {
|
|
30
|
+
switch (op) {
|
|
31
|
+
case 'equals':
|
|
32
|
+
return value === target;
|
|
33
|
+
case 'not_equals':
|
|
34
|
+
return value !== target;
|
|
35
|
+
case 'contains':
|
|
36
|
+
return String(value ?? '')
|
|
37
|
+
.toLowerCase()
|
|
38
|
+
.includes(String(target ?? '').toLowerCase());
|
|
39
|
+
case 'starts_with':
|
|
40
|
+
return String(value ?? '')
|
|
41
|
+
.toLowerCase()
|
|
42
|
+
.startsWith(String(target ?? '').toLowerCase());
|
|
43
|
+
case 'ends_with':
|
|
44
|
+
return String(value ?? '')
|
|
45
|
+
.toLowerCase()
|
|
46
|
+
.endsWith(String(target ?? '').toLowerCase());
|
|
47
|
+
case 'gt':
|
|
48
|
+
return value > target;
|
|
49
|
+
case 'lt':
|
|
50
|
+
return value < target;
|
|
51
|
+
case 'gte':
|
|
52
|
+
return value >= target;
|
|
53
|
+
case 'lte':
|
|
54
|
+
return value <= target;
|
|
55
|
+
case 'in':
|
|
56
|
+
return Array.isArray(target) ? target.includes(value) : false;
|
|
57
|
+
case 'not_in':
|
|
58
|
+
return Array.isArray(target) ? !target.includes(value) : false;
|
|
59
|
+
case 'is_empty':
|
|
60
|
+
return value == null || value === '';
|
|
61
|
+
case 'is_not_empty':
|
|
62
|
+
return !(value == null || value === '');
|
|
63
|
+
default:
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Props } from './type.js';
|
|
3
|
+
import CardFooter from './CardFooter.svelte';
|
|
4
|
+
import CardHeader from './CardHeader.svelte';
|
|
3
5
|
|
|
4
|
-
const { children, footer, header, onclick, ...props }: Props = $props();
|
|
6
|
+
const { children, footer, header, onclick, body_class, ...props }: Props = $props();
|
|
5
7
|
</script>
|
|
6
8
|
|
|
7
9
|
<svelte:element
|
|
@@ -18,20 +20,18 @@
|
|
|
18
20
|
]}
|
|
19
21
|
>
|
|
20
22
|
{#if header}
|
|
21
|
-
<
|
|
23
|
+
<CardHeader>
|
|
22
24
|
{@render header()}
|
|
23
|
-
</
|
|
25
|
+
</CardHeader>
|
|
24
26
|
{/if}
|
|
25
27
|
|
|
26
|
-
<section class={['flex flex-1 flex-col',
|
|
28
|
+
<section class={['flex flex-1 flex-col', body_class]}>
|
|
27
29
|
{@render children()}
|
|
28
30
|
</section>
|
|
29
31
|
|
|
30
32
|
{#if footer}
|
|
31
|
-
<
|
|
32
|
-
class="flex justify-between rounded-b-xl border-t border-gray-200 bg-gray-50 p-3 pt-2 dark:border-neutral-700 dark:bg-neutral-800"
|
|
33
|
-
>
|
|
33
|
+
<CardFooter>
|
|
34
34
|
{@render footer()}
|
|
35
|
-
</
|
|
35
|
+
</CardFooter>
|
|
36
36
|
{/if}
|
|
37
37
|
</svelte:element>
|
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
<script>
|
|
2
|
-
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { ClassValue } from 'svelte/elements';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
children: Snippet;
|
|
7
|
+
class?: ClassValue;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { children, ...props }: Props = $props();
|
|
3
11
|
</script>
|
|
4
12
|
|
|
5
|
-
<div class=
|
|
13
|
+
<div class={['flex flex-1 flex-col p-3', props.class]}>
|
|
6
14
|
{@render children()}
|
|
7
15
|
</div>
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { ClassValue } from 'svelte/elements';
|
|
3
|
+
interface Props {
|
|
4
|
+
children: Snippet;
|
|
5
|
+
class?: ClassValue;
|
|
6
|
+
}
|
|
7
|
+
declare const CardContent: import("svelte").Component<Props, {}, "">;
|
|
8
|
+
type CardContent = ReturnType<typeof CardContent>;
|
|
1
9
|
export default CardContent;
|
|
2
|
-
type CardContent = {
|
|
3
|
-
$on?(type: string, callback: (e: any) => void): () => void;
|
|
4
|
-
$set?(props: Partial<$$ComponentProps>): void;
|
|
5
|
-
};
|
|
6
|
-
declare const CardContent: import("svelte").Component<{
|
|
7
|
-
children: any;
|
|
8
|
-
}, {}, "">;
|
|
9
|
-
type $$ComponentProps = {
|
|
10
|
-
children: any;
|
|
11
|
-
};
|
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { ClassValue } from 'svelte/elements';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
children: Snippet;
|
|
7
|
+
class?: ClassValue;
|
|
8
|
+
}
|
|
9
|
+
const { children, ...props }: Props = $props();
|
|
3
10
|
</script>
|
|
4
11
|
|
|
5
12
|
<footer
|
|
6
|
-
class=
|
|
13
|
+
class={[
|
|
14
|
+
'flex justify-between rounded-b-xl border-t border-gray-200 bg-gray-50 p-3 pt-2 dark:border-neutral-700 dark:bg-neutral-800',
|
|
15
|
+
props.class
|
|
16
|
+
]}
|
|
7
17
|
>
|
|
8
18
|
{@render children()}
|
|
9
19
|
</footer>
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { ClassValue } from 'svelte/elements';
|
|
3
|
+
interface Props {
|
|
4
|
+
children: Snippet;
|
|
5
|
+
class?: ClassValue;
|
|
6
|
+
}
|
|
7
|
+
declare const CardFooter: import("svelte").Component<Props, {}, "">;
|
|
4
8
|
type CardFooter = ReturnType<typeof CardFooter>;
|
|
5
9
|
export default CardFooter;
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { ClassValue } from 'svelte/elements';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
children: Snippet;
|
|
7
|
+
class?: ClassValue;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { children, ...props }: Props = $props();
|
|
3
11
|
</script>
|
|
4
12
|
|
|
5
|
-
<header class=
|
|
13
|
+
<header class={['flex flex-row gap-2 p-3 pb-2', props.class]}>
|
|
6
14
|
{@render children()}
|
|
7
15
|
</header>
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { ClassValue } from 'svelte/elements';
|
|
3
|
+
interface Props {
|
|
4
|
+
children: Snippet;
|
|
5
|
+
class?: ClassValue;
|
|
6
|
+
}
|
|
7
|
+
declare const CardHeader: import("svelte").Component<Props, {}, "">;
|
|
4
8
|
type CardHeader = ReturnType<typeof CardHeader>;
|
|
5
9
|
export default CardHeader;
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED