@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,203 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
export type Entry = {
|
|
3
|
+
id: string;
|
|
4
|
+
label?: string;
|
|
5
|
+
shortcut?: string;
|
|
6
|
+
onClick?: () => void;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
children?: Entry[];
|
|
9
|
+
kind?: 'item' | 'divider' | 'label';
|
|
10
|
+
};
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<script lang="ts">
|
|
14
|
+
type Props = {
|
|
15
|
+
items?: Entry[];
|
|
16
|
+
x?: number;
|
|
17
|
+
y?: number;
|
|
18
|
+
open?: boolean;
|
|
19
|
+
title?: string;
|
|
20
|
+
searchable?: boolean;
|
|
21
|
+
context?: any;
|
|
22
|
+
};
|
|
23
|
+
let {
|
|
24
|
+
items = [],
|
|
25
|
+
x = 0,
|
|
26
|
+
y = 0,
|
|
27
|
+
open = $bindable(false),
|
|
28
|
+
title = '',
|
|
29
|
+
searchable = true,
|
|
30
|
+
context = null
|
|
31
|
+
}: Props = $props();
|
|
32
|
+
|
|
33
|
+
let stack = $state<{ label: string; items: Entry[] }[]>([]);
|
|
34
|
+
let q = $state('');
|
|
35
|
+
|
|
36
|
+
const current = $derived(stack.length ? stack[stack.length - 1] : { label: title, items });
|
|
37
|
+
|
|
38
|
+
function close() {
|
|
39
|
+
open = false;
|
|
40
|
+
stack = [];
|
|
41
|
+
q = '';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function hasChildren(it: Entry) {
|
|
45
|
+
return !!(it.children && it.children.length);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function clickItem(it: Entry) {
|
|
49
|
+
if (it.disabled) return;
|
|
50
|
+
if (hasChildren(it)) {
|
|
51
|
+
stack.push({ label: it.label ?? '', items: it.children! });
|
|
52
|
+
q = '';
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (it.kind !== 'divider' && it.kind !== 'label') {
|
|
56
|
+
it.onClick?.();
|
|
57
|
+
close();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function matches(it: Entry, query: string): boolean {
|
|
62
|
+
if (it.kind === 'divider') return true;
|
|
63
|
+
const lbl = (it.label ?? '').toLowerCase();
|
|
64
|
+
if (lbl.includes(query)) return true;
|
|
65
|
+
if (hasChildren(it)) return it.children!.some((c) => matches(c, query));
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const filtered = $derived.by(() => {
|
|
70
|
+
const list = current.items ?? [];
|
|
71
|
+
const query = q.trim().toLowerCase();
|
|
72
|
+
let arr = query ? list.filter((it) => matches(it, query)) : list.slice();
|
|
73
|
+
|
|
74
|
+
// limpiar divisores (sin duplicados, ni al principio/fin)
|
|
75
|
+
const out: Entry[] = [];
|
|
76
|
+
let prevDiv = false;
|
|
77
|
+
for (const it of arr) {
|
|
78
|
+
if (it.kind === 'divider') {
|
|
79
|
+
if (out.length === 0 || prevDiv) continue;
|
|
80
|
+
prevDiv = true;
|
|
81
|
+
out.push(it);
|
|
82
|
+
} else {
|
|
83
|
+
prevDiv = false;
|
|
84
|
+
out.push(it);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (out.length && out[out.length - 1].kind === 'divider') out.pop();
|
|
88
|
+
return out;
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
function back() {
|
|
92
|
+
if (stack.length) {
|
|
93
|
+
stack.pop();
|
|
94
|
+
q = '';
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function onKey(e: KeyboardEvent) {
|
|
99
|
+
if (e.key === 'Escape') {
|
|
100
|
+
if (stack.length) back();
|
|
101
|
+
else close();
|
|
102
|
+
} else if (e.key === 'Backspace' && q === '' && stack.length) {
|
|
103
|
+
back();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
$effect(() => {
|
|
108
|
+
if (!open) return;
|
|
109
|
+
const handler = (e: KeyboardEvent) => onKey(e);
|
|
110
|
+
document.addEventListener('keydown', handler);
|
|
111
|
+
return () => document.removeEventListener('keydown', handler);
|
|
112
|
+
});
|
|
113
|
+
</script>
|
|
114
|
+
|
|
115
|
+
{#if open}
|
|
116
|
+
<div
|
|
117
|
+
role="dialog"
|
|
118
|
+
class="fixed inset-0 z-40"
|
|
119
|
+
onclick={() => close()}
|
|
120
|
+
oncontextmenu={(e) => e.preventDefault()}
|
|
121
|
+
aria-modal="true"
|
|
122
|
+
tabindex="0"
|
|
123
|
+
/>
|
|
124
|
+
|
|
125
|
+
<div
|
|
126
|
+
class="fixed z-50 w-72 rounded-2xl bg-white p-2 shadow-xl ring-1 ring-black/5 dark:bg-gray-900"
|
|
127
|
+
style={`left:${x}px; top:${y}px`}
|
|
128
|
+
oncontextmenu={(e) => e.preventDefault()}
|
|
129
|
+
>
|
|
130
|
+
<div class="flex items-center gap-1 px-1 py-1">
|
|
131
|
+
{#if stack.length > 0}
|
|
132
|
+
<button
|
|
133
|
+
class="rounded-lg p-1 hover:bg-gray-100 dark:hover:bg-gray-800"
|
|
134
|
+
role="dialog"
|
|
135
|
+
aria-label="Atrás"
|
|
136
|
+
onclick={back}
|
|
137
|
+
>
|
|
138
|
+
<svg
|
|
139
|
+
width="16"
|
|
140
|
+
height="16"
|
|
141
|
+
viewBox="0 0 24 24"
|
|
142
|
+
fill="none"
|
|
143
|
+
stroke="currentColor"
|
|
144
|
+
stroke-width="2"
|
|
145
|
+
stroke-linecap="round"
|
|
146
|
+
stroke-linejoin="round"><polyline points="15 18 9 12 15 6" /></svg
|
|
147
|
+
>
|
|
148
|
+
</button>
|
|
149
|
+
{/if}
|
|
150
|
+
<div class="min-w-0 flex-1 truncate px-1 text-xs font-medium opacity-70">
|
|
151
|
+
{current.label || title}
|
|
152
|
+
</div>
|
|
153
|
+
{#if searchable}
|
|
154
|
+
<input
|
|
155
|
+
class="w-28 rounded-lg border border-gray-200 px-2 py-1 text-xs dark:border-gray-800 dark:bg-gray-950"
|
|
156
|
+
placeholder="Buscar…"
|
|
157
|
+
bind:value={q}
|
|
158
|
+
/>
|
|
159
|
+
{/if}
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
<div
|
|
163
|
+
class="max-h-72 overflow-auto rounded-xl border border-gray-200 p-1 dark:border-gray-800 dark:bg-gray-950"
|
|
164
|
+
>
|
|
165
|
+
{#each filtered as it}
|
|
166
|
+
{#if it.kind === 'divider'}
|
|
167
|
+
<div class="my-1 border-t border-gray-200 dark:border-gray-800"></div>
|
|
168
|
+
{:else if it.kind === 'label'}
|
|
169
|
+
<div class="px-3 py-1 text-[11px] tracking-wide uppercase opacity-60">{it.label}</div>
|
|
170
|
+
{:else}
|
|
171
|
+
<button
|
|
172
|
+
class="flex w-full items-center justify-between gap-3 px-3 py-2 text-left text-sm hover:bg-gray-100 disabled:opacity-50 dark:hover:bg-gray-800"
|
|
173
|
+
role="dialog"
|
|
174
|
+
disabled={it.disabled}
|
|
175
|
+
onclick={() => clickItem(it)}
|
|
176
|
+
>
|
|
177
|
+
<span class="truncate">{it.label}</span>
|
|
178
|
+
<span class="flex items-center gap-2">
|
|
179
|
+
{#if it.shortcut}
|
|
180
|
+
<kbd class="rounded bg-gray-100 px-1 text-[10px] dark:bg-gray-800"
|
|
181
|
+
>{it.shortcut}</kbd
|
|
182
|
+
>
|
|
183
|
+
{/if}
|
|
184
|
+
{#if hasChildren(it)}
|
|
185
|
+
<svg
|
|
186
|
+
width="14"
|
|
187
|
+
height="14"
|
|
188
|
+
viewBox="0 0 24 24"
|
|
189
|
+
class="opacity-60"
|
|
190
|
+
fill="none"
|
|
191
|
+
stroke="currentColor"
|
|
192
|
+
stroke-width="2"
|
|
193
|
+
stroke-linecap="round"
|
|
194
|
+
stroke-linejoin="round"><polyline points="9 18 15 12 9 6" /></svg
|
|
195
|
+
>
|
|
196
|
+
{/if}
|
|
197
|
+
</span>
|
|
198
|
+
</button>
|
|
199
|
+
{/if}
|
|
200
|
+
{/each}
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
{/if}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type Entry = {
|
|
2
|
+
id: string;
|
|
3
|
+
label?: string;
|
|
4
|
+
shortcut?: string;
|
|
5
|
+
onClick?: () => void;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
children?: Entry[];
|
|
8
|
+
kind?: 'item' | 'divider' | 'label';
|
|
9
|
+
};
|
|
10
|
+
type Props = {
|
|
11
|
+
items?: Entry[];
|
|
12
|
+
x?: number;
|
|
13
|
+
y?: number;
|
|
14
|
+
open?: boolean;
|
|
15
|
+
title?: string;
|
|
16
|
+
searchable?: boolean;
|
|
17
|
+
context?: any;
|
|
18
|
+
};
|
|
19
|
+
declare const ContextMenu: import("svelte").Component<Props, {}, "open">;
|
|
20
|
+
type ContextMenu = ReturnType<typeof ContextMenu>;
|
|
21
|
+
export default ContextMenu;
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Input from '@UI/Input/Input.svelte';
|
|
3
|
+
import type { FilterField } from '../core/filters/types';
|
|
4
|
+
import { buildFilterDefs } from '../core/filters/utils';
|
|
5
|
+
import Checkbox from '@UI/Checkbox/Checkbox.svelte';
|
|
6
|
+
import Button from '@UI/buttons/Button.svelte';
|
|
7
|
+
import { Check, X } from 'lucide-svelte';
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
fields: FilterField<any>[];
|
|
11
|
+
values?: Record<string, any>;
|
|
12
|
+
onapply?: (defs: any[], values: Record<string, any>) => void;
|
|
13
|
+
onclear?: () => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const { fields, values = $bindable<Record<string, any>>({}), onapply, onclear }: Props = $props();
|
|
17
|
+
|
|
18
|
+
let open = $state(false);
|
|
19
|
+
|
|
20
|
+
const count = $derived(
|
|
21
|
+
Object.values(values).reduce(
|
|
22
|
+
(n, v) =>
|
|
23
|
+
n +
|
|
24
|
+
(v == null || v === ''
|
|
25
|
+
? 0
|
|
26
|
+
: Array.isArray(v)
|
|
27
|
+
? v.length > 0
|
|
28
|
+
? 1
|
|
29
|
+
: 0
|
|
30
|
+
: typeof v === 'object'
|
|
31
|
+
? v.min != null || v.max != null
|
|
32
|
+
? 1
|
|
33
|
+
: 0
|
|
34
|
+
: 1),
|
|
35
|
+
0
|
|
36
|
+
)
|
|
37
|
+
);
|
|
38
|
+
function setValue(id: string, v: any) {
|
|
39
|
+
values[id] = v;
|
|
40
|
+
}
|
|
41
|
+
function toggleArray(id: string, v: any) {
|
|
42
|
+
const arr = Array.isArray(values[id]) ? [...values[id]] : [];
|
|
43
|
+
const i = arr.findIndex((x) => x === v);
|
|
44
|
+
if (i >= 0) arr.splice(i, 1);
|
|
45
|
+
else arr.push(v);
|
|
46
|
+
values[id] = arr;
|
|
47
|
+
}
|
|
48
|
+
function apply() {
|
|
49
|
+
const defs = buildFilterDefs(fields, values);
|
|
50
|
+
onapply?.(defs, values);
|
|
51
|
+
open = false;
|
|
52
|
+
}
|
|
53
|
+
function clear() {
|
|
54
|
+
for (const k of Object.keys(values)) delete values[k];
|
|
55
|
+
onclear?.();
|
|
56
|
+
}
|
|
57
|
+
</script>
|
|
58
|
+
|
|
59
|
+
<div class="relative">
|
|
60
|
+
<button
|
|
61
|
+
class="inline-flex cursor-pointer items-center gap-2 rounded-xl border border-gray-200 px-3 py-2 text-sm hover:bg-gray-50 dark:border-gray-800 dark:hover:bg-gray-900"
|
|
62
|
+
onclick={() => (open = !open)}
|
|
63
|
+
>
|
|
64
|
+
<svg
|
|
65
|
+
width="16"
|
|
66
|
+
height="16"
|
|
67
|
+
viewBox="0 0 24 24"
|
|
68
|
+
fill="none"
|
|
69
|
+
stroke="currentColor"
|
|
70
|
+
stroke-width="2"><polygon points="3 5 21 5 14 13 14 19 10 21 10 13 3 5" /></svg
|
|
71
|
+
>
|
|
72
|
+
Filtros
|
|
73
|
+
{#if count > 0}
|
|
74
|
+
<span class="rounded bg-purple-600 px-1 text-[11px] text-white">{count}</span>
|
|
75
|
+
{/if}
|
|
76
|
+
</button>
|
|
77
|
+
{#if open}
|
|
78
|
+
<div
|
|
79
|
+
role="dialog"
|
|
80
|
+
class="fixed inset-0 z-40"
|
|
81
|
+
onclick={() => (open = false)}
|
|
82
|
+
oncontextmenu={(e) => e.preventDefault()}
|
|
83
|
+
aria-modal="true"
|
|
84
|
+
tabindex="0"
|
|
85
|
+
/>
|
|
86
|
+
<div
|
|
87
|
+
class="absolute z-50 mt-2 w-80 rounded-2xl border border-gray-200 bg-white p-4 shadow-xl ring-1 ring-black/5 dark:border-gray-800 dark:bg-gray-900"
|
|
88
|
+
>
|
|
89
|
+
<div class="mb-3 text-sm font-medium">Filtros</div>
|
|
90
|
+
<div class="space-y-3">
|
|
91
|
+
{#each fields as f}
|
|
92
|
+
{#if f.type === 'text'}
|
|
93
|
+
<Input
|
|
94
|
+
label={f.label}
|
|
95
|
+
type="text"
|
|
96
|
+
placeholder={f.placeholder}
|
|
97
|
+
value={values[f.id]}
|
|
98
|
+
onchange={(e) => (values[f.id] = e)}
|
|
99
|
+
/>
|
|
100
|
+
{:else if f.type === 'number'}
|
|
101
|
+
<Input
|
|
102
|
+
label={f.label}
|
|
103
|
+
type="number"
|
|
104
|
+
min={f.min}
|
|
105
|
+
max={f.max}
|
|
106
|
+
step={f.step}
|
|
107
|
+
value={values[f.id]}
|
|
108
|
+
onchange={(e) => (values[f.id] = e)}
|
|
109
|
+
/>
|
|
110
|
+
{:else if f.type === 'date'}
|
|
111
|
+
<Input
|
|
112
|
+
label={f.label}
|
|
113
|
+
type="date"
|
|
114
|
+
value={values[f.id]}
|
|
115
|
+
onchange={(e) => (values[f.id] = e)}
|
|
116
|
+
/>
|
|
117
|
+
{:else if f.type === 'checkbox'}
|
|
118
|
+
<Checkbox checked={values[f.id]} label={f.label} onchange={(e) => (values[f.id] = e)} />
|
|
119
|
+
{:else if f.type === 'select'}
|
|
120
|
+
<div>
|
|
121
|
+
<label class="mb-1 block text-xs opacity-70">{f.label}</label>
|
|
122
|
+
<select
|
|
123
|
+
class="w-full rounded-xl border border-gray-200 px-3 py-2 dark:border-gray-800"
|
|
124
|
+
bind:value={values[f.id]}
|
|
125
|
+
>
|
|
126
|
+
<option value="">Todos</option>
|
|
127
|
+
{#each f.options ?? [] as opt}
|
|
128
|
+
<option value={opt.value}>{opt.label}</option>
|
|
129
|
+
{/each}
|
|
130
|
+
</select>
|
|
131
|
+
</div>
|
|
132
|
+
{:else if f.type === 'multiselect'}
|
|
133
|
+
<div>
|
|
134
|
+
<label class="mb-1 block text-xs opacity-70">{f.label}</label>
|
|
135
|
+
<div class="max-h-40 overflow-auto rounded-xl border p-2">
|
|
136
|
+
{#each f.options ?? [] as opt}
|
|
137
|
+
<Checkbox
|
|
138
|
+
checked={(values[f.id] ?? []).includes(opt.value)}
|
|
139
|
+
onchange={() => toggleArray(f.id, opt.value)}
|
|
140
|
+
label={opt.label}
|
|
141
|
+
/>
|
|
142
|
+
{/each}
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
{:else if f.type === 'range'}
|
|
146
|
+
<div>
|
|
147
|
+
<label class="mb-1 block text-xs opacity-70">{f.label}</label>
|
|
148
|
+
<div class="grid grid-cols-2 gap-2">
|
|
149
|
+
<Input
|
|
150
|
+
type="number"
|
|
151
|
+
placeholder="Min"
|
|
152
|
+
min={f.min}
|
|
153
|
+
max={f.max}
|
|
154
|
+
step={f.step}
|
|
155
|
+
value={values[f.id]?.min ?? 0}
|
|
156
|
+
onchange={(e) => (values[f.id].min = e)}
|
|
157
|
+
/>
|
|
158
|
+
<Input
|
|
159
|
+
type="number"
|
|
160
|
+
placeholder="Max"
|
|
161
|
+
min={f.min}
|
|
162
|
+
max={f.max}
|
|
163
|
+
step={f.step}
|
|
164
|
+
value={values[f.id]?.max ?? 0}
|
|
165
|
+
onchange={(e) => (values[f.id].max = e)}
|
|
166
|
+
/>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
{:else if f.type === 'rating'}
|
|
170
|
+
<div>
|
|
171
|
+
<label class="mb-1 block text-xs opacity-70">{f.label}</label>
|
|
172
|
+
<select class="w-full rounded-xl border px-3 py-2" bind:value={values[f.id]}>
|
|
173
|
+
<option value="">Cualquiera</option>
|
|
174
|
+
<option value="4">4+</option>
|
|
175
|
+
<option value="3">3+</option>
|
|
176
|
+
<option value="2">2+</option>
|
|
177
|
+
<option value="1">1+</option>
|
|
178
|
+
</select>
|
|
179
|
+
</div>
|
|
180
|
+
{/if}
|
|
181
|
+
{/each}
|
|
182
|
+
</div>
|
|
183
|
+
<div class="mt-4 flex gap-2">
|
|
184
|
+
<Button variant="outline" onclick={() => clear()}>
|
|
185
|
+
<X class="h-4 w-4" />
|
|
186
|
+
Limpiar
|
|
187
|
+
</Button>
|
|
188
|
+
<Button onclick={() => apply()}>
|
|
189
|
+
<Check class="h-4 w-4" />
|
|
190
|
+
Aplicar
|
|
191
|
+
</Button>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
{/if}
|
|
195
|
+
</div>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { FilterField } from '../core/filters/types';
|
|
2
|
+
interface Props {
|
|
3
|
+
fields: FilterField<any>[];
|
|
4
|
+
values?: Record<string, any>;
|
|
5
|
+
onapply?: (defs: any[], values: Record<string, any>) => void;
|
|
6
|
+
onclear?: () => void;
|
|
7
|
+
}
|
|
8
|
+
declare const FilterPanel: import("svelte").Component<Props, {}, "values">;
|
|
9
|
+
type FilterPanel = ReturnType<typeof FilterPanel>;
|
|
10
|
+
export default FilterPanel;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-svelte';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
page: number;
|
|
6
|
+
perPage: number;
|
|
7
|
+
total: number;
|
|
8
|
+
perPageOptions?: number[];
|
|
9
|
+
onchange?: (p: number) => void;
|
|
10
|
+
onperpage?: (n: number) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let {
|
|
14
|
+
page,
|
|
15
|
+
perPage = $bindable(10),
|
|
16
|
+
total,
|
|
17
|
+
perPageOptions = [10, 20, 50, 100],
|
|
18
|
+
onchange,
|
|
19
|
+
onperpage
|
|
20
|
+
}: Props = $props();
|
|
21
|
+
|
|
22
|
+
const totalPages = $derived(Math.max(1, Math.ceil(total / perPage)));
|
|
23
|
+
|
|
24
|
+
function go(n: number) {
|
|
25
|
+
if (n < 1 || n > totalPages) return;
|
|
26
|
+
onchange?.(n);
|
|
27
|
+
}
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<div class="flex flex-col items-center justify-between gap-3 sm:flex-row">
|
|
31
|
+
<div class="text-sm opacity-70">Página {page} de {totalPages} — {total} filas</div>
|
|
32
|
+
<div class="flex items-center gap-2">
|
|
33
|
+
<select
|
|
34
|
+
class="rounded-xl border border-gray-200 px-2 py-1 pr-6 dark:border-gray-800"
|
|
35
|
+
bind:value={perPage}
|
|
36
|
+
onchange={(e) => onperpage?.(Number((e.target as HTMLSelectElement).value))}
|
|
37
|
+
>
|
|
38
|
+
{#each perPageOptions as n}<option value={n}>{n} / pág</option>{/each}
|
|
39
|
+
</select>
|
|
40
|
+
<div class="inline-flex overflow-hidden rounded-xl border border-gray-200 dark:border-gray-800">
|
|
41
|
+
<button
|
|
42
|
+
class="flex items-center gap-2 px-3 py-1.5 disabled:opacity-40"
|
|
43
|
+
disabled={page <= 1}
|
|
44
|
+
onclick={() => go(page - 1)}
|
|
45
|
+
>
|
|
46
|
+
<ChevronLeftIcon class="h-4 w-4" />
|
|
47
|
+
Prev
|
|
48
|
+
</button>
|
|
49
|
+
<button
|
|
50
|
+
class="flex items-center gap-2 px-3 py-1.5 disabled:opacity-40"
|
|
51
|
+
disabled={page >= totalPages}
|
|
52
|
+
onclick={() => go(page + 1)}
|
|
53
|
+
>
|
|
54
|
+
Next
|
|
55
|
+
<ChevronRightIcon class="h-4 w-4" />
|
|
56
|
+
</button>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
page: number;
|
|
3
|
+
perPage: number;
|
|
4
|
+
total: number;
|
|
5
|
+
perPageOptions?: number[];
|
|
6
|
+
onchange?: (p: number) => void;
|
|
7
|
+
onperpage?: (n: number) => void;
|
|
8
|
+
}
|
|
9
|
+
declare const Pagination: import("svelte").Component<Props, {}, "perPage">;
|
|
10
|
+
type Pagination = ReturnType<typeof Pagination>;
|
|
11
|
+
export default Pagination;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { FilterDef } from '../core/types';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
filters: FilterDef<any>[];
|
|
6
|
+
onapply: (filters: FilterDef<any>[]) => void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const { filters, onapply }: Props = $props();
|
|
10
|
+
|
|
11
|
+
let text = $state('');
|
|
12
|
+
|
|
13
|
+
function submit() {
|
|
14
|
+
const base = filters.filter((f) => f.meta?.kind !== 'quick');
|
|
15
|
+
const q: FilterDef<any> = {
|
|
16
|
+
id: 'q',
|
|
17
|
+
label: 'Búsqueda',
|
|
18
|
+
op: 'contains',
|
|
19
|
+
value: text,
|
|
20
|
+
meta: { kind: 'quick', column: 'all' }
|
|
21
|
+
};
|
|
22
|
+
onapply([...base, q]);
|
|
23
|
+
}
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<div class="flex items-center gap-2">
|
|
27
|
+
<input
|
|
28
|
+
class="w-64 rounded-xl border px-3 py-2"
|
|
29
|
+
placeholder="Buscar…"
|
|
30
|
+
bind:value={text}
|
|
31
|
+
onkeydown={(e) => e.key === 'Enter' && submit()}
|
|
32
|
+
/>
|
|
33
|
+
<button
|
|
34
|
+
type="button"
|
|
35
|
+
class="rounded-xl bg-black px-3 py-2 text-white dark:bg-white dark:text-black"
|
|
36
|
+
onclick={submit}>Aplicar</button
|
|
37
|
+
>
|
|
38
|
+
</div>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { FilterDef } from '../core/types';
|
|
2
|
+
interface Props {
|
|
3
|
+
filters: FilterDef<any>[];
|
|
4
|
+
onapply: (filters: FilterDef<any>[]) => void;
|
|
5
|
+
}
|
|
6
|
+
declare const QuickFilters: import("svelte").Component<Props, {}, "">;
|
|
7
|
+
type QuickFilters = ReturnType<typeof QuickFilters>;
|
|
8
|
+
export default QuickFilters;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { SvelteSet } from 'svelte/reactivity';
|
|
2
|
+
import type { TableOptions, TableState, VisibilityPlan } from './types.js';
|
|
3
|
+
export declare class DataTableManager<T extends {
|
|
4
|
+
id?: any;
|
|
5
|
+
} = any> {
|
|
6
|
+
options: TableOptions<T>;
|
|
7
|
+
state: TableState<T>;
|
|
8
|
+
measured: Record<string, number>;
|
|
9
|
+
reservedWidth: number;
|
|
10
|
+
forcedVisible: SvelteSet<string>;
|
|
11
|
+
forcedHidden: SvelteSet<string>;
|
|
12
|
+
expanded: SvelteSet<any>;
|
|
13
|
+
lastWidth: number | null;
|
|
14
|
+
constructor(opts: TableOptions<T>);
|
|
15
|
+
setMeasuredWidths(map: Record<string, number>): void;
|
|
16
|
+
get columns(): import("./types.js").ColumnDef<T>[];
|
|
17
|
+
getColumn(id: string): import("./types.js").ColumnDef<T>;
|
|
18
|
+
isExpanded(id: any): boolean;
|
|
19
|
+
toggleExpand(id: any): void;
|
|
20
|
+
setColumnVisibility(id: string, show: boolean): void;
|
|
21
|
+
setReservedWidth(n: number): void;
|
|
22
|
+
clearColumnOverrides(): void;
|
|
23
|
+
visibilityPlan(containerWidth: number): VisibilityPlan;
|
|
24
|
+
private mergeWithOverrides;
|
|
25
|
+
applyVisibility(plan: VisibilityPlan): void;
|
|
26
|
+
reflowForWidth(width: number): void;
|
|
27
|
+
reflow(): void;
|
|
28
|
+
setPerPage(n: number): Promise<void>;
|
|
29
|
+
setPage(p: number): Promise<void>;
|
|
30
|
+
setSort(id: string): Promise<void>;
|
|
31
|
+
setFilters(filters: typeof this.state.filters): Promise<void>;
|
|
32
|
+
clearFilters(): Promise<void>;
|
|
33
|
+
toggleSelect(rowId: any): void;
|
|
34
|
+
clearSelection(): void;
|
|
35
|
+
selectAllCurrentPage(getId?: (row: T) => any): void;
|
|
36
|
+
load(): Promise<void>;
|
|
37
|
+
private loadRemote;
|
|
38
|
+
private loadLocal;
|
|
39
|
+
}
|