@r2digisolutions/ui 0.26.11 → 0.27.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.
@@ -44,6 +44,7 @@
44
44
  filterFields
45
45
  }: Props<T> = $props();
46
46
 
47
+ // Layout constants
47
48
  const CHECK_W = 64;
48
49
  const ACTION_W = 56;
49
50
  const EXPAND_W = 40;
@@ -60,18 +61,21 @@
60
61
  density === 'compact' ? 'py-2' : density === 'comfortable' ? 'py-4' : 'py-3'
61
62
  );
62
63
 
63
- await manager.load();
64
+ // Importante: dejamos que el propio manager se encargue de la carga
65
+ // vía su efecto interno en el constructor. No hacemos `await manager.load()` aquí.
64
66
 
67
+ // Ajustar ancho reservado para checkbox / expand / actions
65
68
  $effect(() => {
66
69
  const hasActions = !!rowActions;
67
70
  const reserved =
68
71
  CHECK_W +
69
72
  (expandIconPosition === 'end' && !hasActions ? EXPAND_W : 0) +
70
73
  (hasActions ? ACTION_W : 0);
74
+
71
75
  manager.setReservedWidth(reserved);
72
76
  });
73
77
 
74
- // Reflow ancho
78
+ // Reflow ancho por ResizeObserver
75
79
  $effect(() => {
76
80
  if (!container) return;
77
81
  const ro = new ResizeObserver((entries) => {
@@ -82,29 +86,42 @@
82
86
  return () => ro.disconnect();
83
87
  });
84
88
 
85
- // Medir DOM
89
+ // Medir DOM para calcular anchos de columnas
86
90
  const SAMPLE_ROWS = 10;
91
+
87
92
  async function measureColumns() {
88
93
  await tick();
89
94
  if (!container) return;
95
+
90
96
  const widths: Record<string, number> = {};
97
+
91
98
  for (const c of manager.columns) {
92
99
  const head = container.querySelector(`[data-dt-head="${c.id}"]`) as HTMLElement | null;
100
+
93
101
  let maxW = head ? head.offsetWidth : 0;
102
+
94
103
  const cells = Array.from(
95
104
  container.querySelectorAll(`[data-dt-cell="1"][data-col-id="${c.id}"]`)
96
105
  ).slice(0, SAMPLE_ROWS) as HTMLElement[];
97
- for (const el of cells) maxW = Math.max(maxW, el.offsetWidth);
106
+
107
+ for (const el of cells) {
108
+ maxW = Math.max(maxW, el.offsetWidth);
109
+ }
110
+
98
111
  if (c.minWidth != null) maxW = Math.max(maxW, c.minWidth);
99
112
  if (c.width != null) maxW = Math.max(maxW, c.width);
113
+
100
114
  widths[c.id] = Math.ceil(maxW + 16);
101
115
  }
116
+
102
117
  manager.setMeasuredWidths(widths);
118
+
103
119
  const rect = container.getBoundingClientRect();
104
120
  manager.reflowForWidth(Math.floor(rect.width));
105
121
  measuring = false;
106
122
  }
107
123
 
124
+ // Lanzar medición cuando ya haya datos cargados
108
125
  $effect(() => {
109
126
  if (!manager.state.ready) return;
110
127
  measuring = true;
@@ -124,8 +141,10 @@
124
141
  ) {
125
142
  e.preventDefault();
126
143
  const columnIndex = columnId ? manager.state.visibleColumns.indexOf(columnId) : null;
127
- // Reset menu state and set new coordinates
144
+
145
+ // Reset del menú para evitar glitches de posición
128
146
  rightMenu = { open: false, x: 0, y: 0 };
147
+
129
148
  tick().then(() => {
130
149
  rightMenu = { open: true, x: e.clientX, y: e.clientY };
131
150
  rightClickContext = {
@@ -144,12 +163,10 @@
144
163
  return manager.state.items.filter((r) => ids.has(rowId(r)));
145
164
  }
146
165
 
147
- const selectedRowsItems = $derived.by(() => {
148
- return selectedRows();
149
- });
166
+ const selectedRowsItems = $derived.by(() => selectedRows());
150
167
 
151
- function colTrack(cId: string, measuring: boolean) {
152
- if (measuring) return 'max-content';
168
+ function colTrack(cId: string, isMeasuring: boolean) {
169
+ if (isMeasuring) return 'max-content';
153
170
  const c = manager.getColumn(cId);
154
171
  const w = manager.measured[cId] ?? c.width ?? c.minWidth ?? 160;
155
172
  return `${Math.max(40, Math.ceil(Number(w)))}px`;
@@ -167,42 +184,64 @@
167
184
  const colsForRender = $derived(
168
185
  measuring ? manager.columns.map((c) => c.id) : manager.state.visibleColumns
169
186
  );
187
+
170
188
  const endExtras = $derived(
171
189
  expandIconPosition === 'end' && !rowActions && manager.state.hiddenColumns.length > 0
172
190
  );
191
+
192
+ const allSelected = $derived.by(() => {
193
+ const items = manager.state.items;
194
+ if (items.length === 0) return false;
195
+ return items.every((r) => manager.state.selected.has(rowId(r)));
196
+ });
197
+
198
+ const hasHiddenColumns = $derived.by(() => manager.state.hiddenColumns.length > 0);
173
199
  </script>
174
200
 
175
- <div class={`space-y-3 ${measuring ? 'overflow-x-hidden' : ''}`} bind:this={container}>
201
+ <div bind:this={container} class={`space-y-3 ${measuring ? 'overflow-x-hidden' : ''}`}>
202
+ <!-- Toolbar filtros / acciones -->
176
203
  <div class="flex flex-wrap items-center justify-between gap-3">
177
- {@render filters?.()}
178
- {#if filterFields && filterFields.length}
179
- <FilterPanel
180
- fields={filterFields}
181
- values={filterValues}
182
- onapply={(defs) => manager.setFilters(defs)}
183
- onclear={() => manager.clearFilters()}
184
- />
185
- {/if}
186
- {#if showColumnToggle}
187
- <ColumnVisibilityToggle
188
- columns={manager.columns}
189
- visible={manager.state.visibleColumns}
190
- onToggle={(id, show) => manager.setColumnVisibility(id, show)}
191
- />
192
- {/if}
204
+ <div class="flex flex-wrap items-center gap-2">
205
+ {@render filters?.()}
206
+ {#if filterFields && filterFields.length}
207
+ <FilterPanel
208
+ fields={filterFields}
209
+ values={filterValues}
210
+ onapply={(defs) => manager.setFilters(defs)}
211
+ onclear={() => manager.clearFilters()}
212
+ />
213
+ {/if}
214
+ </div>
215
+
216
+ <div class="flex items-center gap-2">
217
+ {#if showColumnToggle}
218
+ <ColumnVisibilityToggle
219
+ columns={manager.columns}
220
+ visible={manager.state.visibleColumns}
221
+ onToggle={(id, show) => manager.setColumnVisibility(id, show)}
222
+ />
223
+ {/if}
224
+ </div>
193
225
  </div>
194
226
 
195
- <div class="rounded-2xl border border-gray-200 shadow-sm dark:border-gray-800">
227
+ <!-- Tabla -->
228
+ <div
229
+ class="rounded-2xl border border-neutral-200/80 bg-white/70 shadow-sm shadow-black/5 backdrop-blur-sm
230
+ dark:border-neutral-800/80 dark:bg-neutral-950/80"
231
+ >
196
232
  <!-- HEADER -->
197
233
  <div
198
- class={`grid items-center border-b border-gray-200 text-sm font-medium dark:border-gray-800 ${stickyHeader ? 'sticky top-0 z-10 bg-white/90 backdrop-blur dark:bg-gray-950/80' : ''}`}
234
+ class={`grid items-center border-b border-neutral-200/80 text-xs font-semibold tracking-wide text-neutral-500
235
+ uppercase dark:border-neutral-800/80 dark:text-neutral-400
236
+ ${stickyHeader ? 'sticky top-0 z-10 bg-white/90 backdrop-blur-md dark:bg-neutral-950/90' : ''}`}
199
237
  style={`grid-template-columns:${headerTemplateCols(colsForRender, endExtras)};`}
200
238
  >
201
- <div class="flex h-12 items-center px-3">
239
+ <!-- checkbox global -->
240
+ <div class="flex h-11 items-center px-3">
202
241
  <input
203
242
  type="checkbox"
204
- checked={manager.state.items.length > 0 &&
205
- manager.state.items.every((r) => manager.state.selected.has(rowId(r)))}
243
+ class="h-4 w-4 rounded border-neutral-300 text-neutral-800 shadow-sm focus-visible:ring-2 focus-visible:ring-neutral-500 focus-visible:outline-none dark:border-neutral-700 dark:bg-neutral-900"
244
+ checked={allSelected}
206
245
  onclick={(e) =>
207
246
  (e.currentTarget as HTMLInputElement).checked
208
247
  ? manager.selectAllCurrentPage(rowId)
@@ -210,23 +249,21 @@
210
249
  />
211
250
  </div>
212
251
 
252
+ <!-- headers -->
213
253
  {#each colsForRender as cid}
254
+ {@const col = manager.getColumn(cid)}
214
255
  <div
215
256
  data-dt-head={cid}
216
- class="flex h-12 items-center px-3 select-none"
217
- class:cursor-pointer={manager.getColumn(cid).sortable}
218
- onclick={() => headerClick(manager.getColumn(cid))}
257
+ class="flex h-11 items-center px-3 select-none"
258
+ class:cursor-pointer={col.sortable}
259
+ onclick={() => headerClick(col)}
219
260
  oncontextmenu={(e) => onCellContext(e, null, cid, null)}
220
261
  >
221
- <div class="truncate">
222
- {manager.getColumn(cid).header}
223
- {#if manager.state.sortBy === cid}
224
- <span class="ml-1 text-xs opacity-60">
225
- {manager.state.sortDir === 'asc'
226
- ? '▲'
227
- : manager.state.sortDir === 'desc'
228
- ? '▼'
229
- : ''}
262
+ <div class="flex items-center gap-1 truncate">
263
+ <span>{col.header}</span>
264
+ {#if manager.state.sortBy === cid && manager.state.sortDir}
265
+ <span class="text-[10px] opacity-70">
266
+ {manager.state.sortDir === 'asc' ? '▲' : '▼'}
230
267
  </span>
231
268
  {/if}
232
269
  </div>
@@ -234,48 +271,57 @@
234
271
  {/each}
235
272
 
236
273
  {#if rowActions}
237
- <div class="h-12 px-3"></div>
274
+ <div class="h-11 px-3"></div>
238
275
  {:else if endExtras}
239
- <div class="h-12 px-3"></div>
276
+ <div class="h-11 px-3"></div>
240
277
  {/if}
241
278
  </div>
242
279
 
243
280
  <!-- BODY -->
244
281
  <div>
245
282
  {#if manager.state.loading}
246
- <div class="p-6 text-center opacity-70">Cargando…</div>
283
+ <div class="p-6 text-center text-sm text-neutral-500 dark:text-neutral-400">Cargando…</div>
247
284
  {:else if manager.state.error}
248
- <div class="p-6 text-center text-red-600">{manager.state.error}</div>
285
+ <div class="p-6 text-center text-sm text-red-600 dark:text-red-400">
286
+ {manager.state.error}
287
+ </div>
249
288
  {:else if manager.state.items.length === 0}
250
- <div class="p-6 text-center opacity-70">Sin resultados</div>
289
+ <div class="p-6 text-center text-sm text-neutral-500 dark:text-neutral-400">
290
+ Sin resultados
291
+ </div>
251
292
  {:else}
252
293
  {#each manager.state.items as row, i (rowId(row))}
253
294
  <!-- ROW -->
254
295
  <div
255
- class={`grid items-center border-b border-gray-100 last:border-b-0 dark:border-gray-900 ${sizeRow}`}
296
+ class={`grid items-stretch border-b border-neutral-100/60 last:border-b-0
297
+ dark:border-neutral-900 ${sizeRow}
298
+ transition-colors hover:bg-neutral-50/80 dark:hover:bg-neutral-900/60`}
256
299
  style={`grid-template-columns:${headerTemplateCols(colsForRender, endExtras)};`}
257
300
  >
258
- <!-- col 0: check + expand -->
301
+ <!-- col 0: check + expand (start) -->
259
302
  <div class="px-3 py-2">
260
303
  <div class="flex items-center gap-2">
261
304
  <input
262
305
  type="checkbox"
306
+ class="h-4 w-4 rounded border-neutral-300 text-neutral-800 shadow-sm focus-visible:ring-2 focus-visible:ring-neutral-500 focus-visible:outline-none dark:border-neutral-700 dark:bg-neutral-900"
263
307
  checked={manager.state.selected.has(rowId(row))}
264
308
  onclick={() => manager.toggleSelect(rowId(row))}
265
309
  oncontextmenu={(e) => onCellContext(e, row, '_check', i)}
266
310
  />
267
- {#if manager.state.hiddenColumns.length > 0}
268
- {#if expandIconPosition === 'start'}
269
- <button
270
- class="cursor-pointer rounded-lg p-1 hover:bg-gray-100 dark:hover:bg-gray-800"
271
- title={manager.isExpanded(rowId(row)) ? 'Ocultar detalles' : 'Ver detalles'}
272
- onclick={() => manager.toggleExpand(rowId(row))}
273
- >
274
- {#if manager.isExpanded(rowId(row))}<ChevronDown
275
- class="h-4 w-4"
276
- />{:else}<ChevronRight class="h-4 w-4" />{/if}
277
- </button>
278
- {/if}
311
+
312
+ {#if hasHiddenColumns && expandIconPosition === 'start'}
313
+ <button
314
+ type="button"
315
+ class="inline-flex h-7 w-7 items-center justify-center rounded-lg border border-transparent text-neutral-500 hover:bg-neutral-100 dark:text-neutral-400 dark:hover:bg-neutral-800"
316
+ title={manager.isExpanded(rowId(row)) ? 'Ocultar detalles' : 'Ver detalles'}
317
+ onclick={() => manager.toggleExpand(rowId(row))}
318
+ >
319
+ {#if manager.isExpanded(rowId(row))}
320
+ <ChevronDown class="h-4 w-4" />
321
+ {:else}
322
+ <ChevronRight class="h-4 w-4" />
323
+ {/if}
324
+ </button>
279
325
  {/if}
280
326
  </div>
281
327
  </div>
@@ -284,13 +330,12 @@
284
330
  {#each colsForRender as cid}
285
331
  {@const col = manager.getColumn(cid)}
286
332
  <div
287
- onkeydown={(e) => console.log('KEYDOWN', e)}
288
- tabindex="0"
289
- role="button"
290
333
  data-dt-cell="1"
291
334
  data-col-id={cid}
292
335
  data-row-index={i}
293
- class="flex h-full w-full items-center px-3"
336
+ tabindex="0"
337
+ role="button"
338
+ class="flex h-full w-full items-center px-3 text-sm text-neutral-800 outline-none focus-visible:ring-2 focus-visible:ring-neutral-500/70 dark:text-neutral-100"
294
339
  onclick={() => onRowClick?.(row)}
295
340
  oncontextmenu={(e) => onCellContext(e, row, cid, i)}
296
341
  >
@@ -306,15 +351,18 @@
306
351
  {#if rowActions}
307
352
  <div class="px-3 text-right">
308
353
  <div class="inline-flex items-center gap-2">
309
- {#if expandIconPosition === 'end' && manager.state.hiddenColumns.length > 0}
354
+ {#if expandIconPosition === 'end' && hasHiddenColumns}
310
355
  <button
311
- class="cursor-pointer rounded-lg p-1 hover:bg-gray-100 dark:hover:bg-gray-800"
356
+ type="button"
357
+ class="inline-flex h-7 w-7 items-center justify-center rounded-lg border border-transparent text-neutral-500 hover:bg-neutral-100 dark:text-neutral-400 dark:hover:bg-neutral-800"
312
358
  title={manager.isExpanded(rowId(row)) ? 'Ocultar detalles' : 'Ver detalles'}
313
359
  onclick={() => manager.toggleExpand(rowId(row))}
314
360
  >
315
- {#if manager.isExpanded(rowId(row))}<ChevronDown
316
- class="h-4 w-4"
317
- />{:else}<ChevronRight class="h-4 w-4" />{/if}
361
+ {#if manager.isExpanded(rowId(row))}
362
+ <ChevronDown class="h-4 w-4" />
363
+ {:else}
364
+ <ChevronRight class="h-4 w-4" />
365
+ {/if}
318
366
  </button>
319
367
  {/if}
320
368
  {@render rowActions(row)}
@@ -323,7 +371,8 @@
323
371
  {:else if endExtras}
324
372
  <div class="px-3 text-right">
325
373
  <button
326
- class="rounded-lg p-1 hover:bg-gray-100 dark:hover:bg-gray-800"
374
+ type="button"
375
+ class="inline-flex h-7 w-7 items-center justify-center rounded-lg border border-transparent text-neutral-500 hover:bg-neutral-100 dark:text-neutral-400 dark:hover:bg-neutral-800"
327
376
  title={manager.isExpanded(rowId(row)) ? 'Ocultar detalles' : 'Ver detalles'}
328
377
  onclick={() => manager.toggleExpand(rowId(row))}
329
378
  >
@@ -337,17 +386,23 @@
337
386
  {/if}
338
387
 
339
388
  {#if manager.isExpanded(rowId(row))}
340
- <div class="col-span-full px-3 pt-1 pb-3">
389
+ <!-- Detalles colapsados en modo "cards" -->
390
+ <div class="col-span-full bg-neutral-50/60 px-3 pt-1 pb-3 dark:bg-neutral-950/60">
341
391
  <div class="grid gap-3 sm:grid-cols-2 md:grid-cols-3">
342
392
  {#each manager.state.hiddenColumns as hid}
343
393
  {#key hid}
344
394
  {@const col = manager.columns.find((cc) => cc.id === hid)}
345
395
  {#if col}
346
- <div class="rounded-xl border border-gray-200 p-3 dark:border-gray-800">
347
- <div class="mb-1 text-[11px] tracking-wide uppercase opacity-60">
396
+ <div
397
+ class="rounded-xl border border-neutral-200/70 bg-white/70 p-3 text-sm
398
+ dark:border-neutral-800/70 dark:bg-neutral-900/70"
399
+ >
400
+ <div
401
+ class="mb-1 text-[11px] font-medium tracking-wide text-neutral-500 uppercase dark:text-neutral-400"
402
+ >
348
403
  {col.responsiveLabel ?? col.header}
349
404
  </div>
350
- <div class="text-sm">
405
+ <div class="text-sm text-neutral-800 dark:text-neutral-100">
351
406
  {#if col.renderCollapsed}
352
407
  {@render col.renderCollapsed(row)}
353
408
  {:else if col.renderCell}
@@ -369,6 +424,7 @@
369
424
  </div>
370
425
  </div>
371
426
 
427
+ <!-- Paginación -->
372
428
  <Pagination
373
429
  page={manager.state.page}
374
430
  perPage={manager.state.perPage}
@@ -378,6 +434,7 @@
378
434
  onperpage={(n) => manager.setPerPage(n)}
379
435
  />
380
436
 
437
+ <!-- Context menu -->
381
438
  <ContextMenu
382
439
  bind:open={rightMenu.open}
383
440
  x={rightMenu.x}
@@ -16,21 +16,21 @@ interface Props<T> {
16
16
  }
17
17
  declare function $$render<T extends {
18
18
  id?: any;
19
- }>(): Promise<{
19
+ }>(): {
20
20
  props: Props<T>;
21
21
  exports: {};
22
22
  bindings: "";
23
23
  slots: {};
24
24
  events: {};
25
- }>;
25
+ };
26
26
  declare class __sveltets_Render<T extends {
27
27
  id?: any;
28
28
  }> {
29
- props(): Awaited<ReturnType<typeof $$render<T>>>['props'];
30
- events(): Awaited<ReturnType<typeof $$render<T>>>['events'];
31
- slots(): Awaited<ReturnType<typeof $$render<T>>>['slots'];
29
+ props(): ReturnType<typeof $$render<T>>['props'];
30
+ events(): ReturnType<typeof $$render<T>>['events'];
31
+ slots(): ReturnType<typeof $$render<T>>['slots'];
32
32
  bindings(): "";
33
- exports(): Promise<{}>;
33
+ exports(): {};
34
34
  }
35
35
  interface $$IsomorphicComponent {
36
36
  new <T extends {
@@ -8,7 +8,7 @@ export class DataTableManager {
8
8
  multiSelect: true,
9
9
  columns: [],
10
10
  loadMode: 'local',
11
- data: [],
11
+ data: []
12
12
  });
13
13
  state = $state({
14
14
  ready: false,
@@ -34,18 +34,19 @@ export class DataTableManager {
34
34
  constructor(options) {
35
35
  const opts = options();
36
36
  const columns = normalize(opts.columns);
37
- this.options = ({
37
+ this.options = {
38
38
  ...opts,
39
39
  columns
40
- });
41
- this.state = ({
40
+ };
41
+ this.state = {
42
42
  ...this.state,
43
43
  perPage: this.options.perPage,
44
44
  sortBy: opts.initialSortBy ?? null,
45
45
  sortDir: opts.initialSortDir ?? null,
46
46
  filters: opts.initialFilters ?? [],
47
- visibleColumns: columns.map((c) => c.id),
48
- });
47
+ visibleColumns: columns.map((c) => c.id)
48
+ };
49
+ // Sincroniza con cambios en options() (columnas, fetcher, etc.)
49
50
  $effect(() => {
50
51
  const new_options = options();
51
52
  untrack(() => {
@@ -54,6 +55,7 @@ export class DataTableManager {
54
55
  ...new_options,
55
56
  columns
56
57
  };
58
+ // Lógica: si cambian opciones, recargamos data con los nuevos ajustes
57
59
  this.load();
58
60
  });
59
61
  });
@@ -62,10 +64,21 @@ export class DataTableManager {
62
64
  this.measured = map;
63
65
  this.reflow();
64
66
  }
65
- get columns() { return this.options.columns; }
66
- getColumn(id) { return this.columns.find((c) => c.id === id); }
67
- isExpanded(id) { return this.expanded.has(id); }
68
- toggleExpand(id) { this.isExpanded(id) ? this.expanded.delete(id) : this.expanded.add(id); }
67
+ get columns() {
68
+ return this.options.columns;
69
+ }
70
+ getColumn(id) {
71
+ return this.columns.find((c) => c.id === id);
72
+ }
73
+ isExpanded(id) {
74
+ return this.expanded.has(id);
75
+ }
76
+ toggleExpand(id) {
77
+ if (this.isExpanded(id))
78
+ this.expanded.delete(id);
79
+ else
80
+ this.expanded.add(id);
81
+ }
69
82
  setColumnVisibility(id, show) {
70
83
  if (show) {
71
84
  this.forcedVisible.add(id);
@@ -101,20 +114,20 @@ export class DataTableManager {
101
114
  // columnas que siempre se ocultan en móvil, aunque quepan
102
115
  const mustHide = new Set();
103
116
  if (available < 640) {
104
- for (const c of cols)
117
+ for (const c of cols) {
105
118
  if (c.hideOnMobile)
106
119
  mustHide.add(c.id);
120
+ }
107
121
  }
108
122
  // empezamos conservando el orden original
109
123
  let visible = origOrder.filter((id) => !mustHide.has(id));
110
124
  const totalNeed = () => visible.reduce((sum, id) => sum + needOf(id), 0);
125
+ // Caso: no caben todas → ocultamos por prioridad
111
126
  if (totalNeed() > available) {
112
- // hay que ocultar: orden de descarte por prioridad (más alta => se quita antes)
113
- // empate: quitamos antes las columnas que están más a la derecha (idx más alto)
114
127
  const dropOrder = cols
115
128
  .map((c, idx) => ({ id: c.id, pr: c.priority ?? 999, idx }))
116
129
  .filter((x) => !mustHide.has(x.id))
117
- .sort((a, b) => (b.pr - a.pr) || (b.idx - a.idx));
130
+ .sort((a, b) => b.pr - a.pr || b.idx - a.idx);
118
131
  const hidden = new Set([...mustHide]);
119
132
  for (const d of dropOrder) {
120
133
  if (totalNeed() <= available)
@@ -125,7 +138,6 @@ export class DataTableManager {
125
138
  hidden.add(d.id);
126
139
  }
127
140
  }
128
- // overrides manuales
129
141
  const visSet = new Set(visible);
130
142
  for (const id of this.forcedHidden)
131
143
  visSet.delete(id);
@@ -136,7 +148,7 @@ export class DataTableManager {
136
148
  const finalHidden = origOrder.filter((id) => !finalVisible.includes(id));
137
149
  return { visible: finalVisible, hidden: finalHidden };
138
150
  }
139
- // TODO cabe: respetamos tu orden original
151
+ // Caso: caben todas → respetamos orden original + overrides manuales
140
152
  const visSet = new Set(visible);
141
153
  for (const id of this.forcedHidden)
142
154
  visSet.delete(id);
@@ -164,46 +176,79 @@ export class DataTableManager {
164
176
  this.state.visibleColumns = plan.visible;
165
177
  this.state.hiddenColumns = plan.hidden;
166
178
  }
167
- // NUEVO: para usar desde el ResizeObserver
168
179
  reflowForWidth(width) {
169
180
  this.lastWidth = width;
170
181
  const base = this.visibilityPlan(width);
171
182
  this.applyVisibility(this.mergeWithOverrides(base));
172
183
  }
173
- // NUEVO: reflow con el último ancho conocido (o infinito si no hay)
174
184
  reflow() {
175
185
  const width = this.lastWidth ?? Number.POSITIVE_INFINITY;
176
186
  const base = this.visibilityPlan(width);
177
187
  this.applyVisibility(this.mergeWithOverrides(base));
178
188
  }
179
- // Selección / paginación / sort / filtros (sin cambios relevantes)
180
- setPerPage(n) { this.state.perPage = n; this.state.page = 1; return this.load(); }
181
- setPage(p) { this.state.page = p; return this.load(); }
189
+ // Selección / paginación / sort / filtros
190
+ setPerPage(n) {
191
+ this.state.perPage = n;
192
+ this.state.page = 1;
193
+ return this.load();
194
+ }
195
+ setPage(p) {
196
+ this.state.page = p;
197
+ return this.load();
198
+ }
182
199
  setSort(id) {
183
200
  if (this.state.sortBy !== id) {
184
201
  this.state.sortBy = id;
185
202
  this.state.sortDir = 'asc';
186
203
  }
187
204
  else {
188
- this.state.sortDir = this.state.sortDir === 'asc' ? 'desc' : (this.state.sortDir === 'desc' ? null : 'asc');
205
+ this.state.sortDir =
206
+ this.state.sortDir === 'asc'
207
+ ? 'desc'
208
+ : this.state.sortDir === 'desc'
209
+ ? null
210
+ : 'asc';
189
211
  }
190
212
  this.state.page = 1;
191
213
  return this.load();
192
214
  }
193
- setFilters(filters) { this.state.filters = filters; this.state.page = 1; return this.load(); }
194
- clearFilters() { this.state.filters = []; this.state.page = 1; return this.load(); }
195
- toggleSelect(rowId) { const s = this.state.selected; s.has(rowId) ? s.delete(rowId) : s.add(rowId); }
196
- clearSelection() { this.state.selected.clear(); }
197
- selectAllCurrentPage(getId = (r) => r.id) { this.state.items.forEach((r) => this.state.selected.add(getId(r))); }
215
+ setFilters(filters) {
216
+ this.state.filters = filters;
217
+ this.state.page = 1;
218
+ return this.load();
219
+ }
220
+ clearFilters() {
221
+ this.state.filters = [];
222
+ this.state.page = 1;
223
+ return this.load();
224
+ }
225
+ toggleSelect(rowId) {
226
+ const s = this.state.selected;
227
+ if (s.has(rowId))
228
+ s.delete(rowId);
229
+ else
230
+ s.add(rowId);
231
+ }
232
+ clearSelection() {
233
+ this.state.selected.clear();
234
+ }
235
+ selectAllCurrentPage(getId = (r) => r.id) {
236
+ this.state.items.forEach((r) => this.state.selected.add(getId(r)));
237
+ }
238
+ // --------- CARGA DE DATOS ---------
198
239
  async load() {
199
240
  const { loadMode } = this.options;
200
241
  this.state.loading = true;
201
242
  this.state.error = undefined;
202
243
  try {
203
- if (loadMode === 'local')
204
- return await this.loadLocal();
205
- else
206
- return await this.loadRemote();
244
+ if (loadMode === 'local') {
245
+ await this.loadLocal();
246
+ }
247
+ else {
248
+ await this.loadRemote();
249
+ }
250
+ // Después de tener items, recalculamos visibilidad de columnas
251
+ this.reflow();
207
252
  }
208
253
  catch (e) {
209
254
  this.state.error = e?.message ?? 'Error al cargar';
@@ -212,7 +257,6 @@ export class DataTableManager {
212
257
  this.state.loading = false;
213
258
  this.state.ready = true;
214
259
  }
215
- this.reflow(); // asegura visibilidad coherente tras cargar
216
260
  }
217
261
  async loadRemote() {
218
262
  const { fetcher } = this.options;
@@ -234,13 +278,19 @@ export class DataTableManager {
234
278
  async loadLocal() {
235
279
  const data = this.options.data ?? [];
236
280
  let rows = [...data];
281
+ // Filtros locales
237
282
  for (const f of this.state.filters) {
238
283
  const col = f.columnId ? this.getColumn(f.columnId) : undefined;
239
284
  rows = rows.filter((r) => {
240
- const value = col ? (col.accessor ? col.accessor(r) : r[col.id]) : r;
285
+ const value = col
286
+ ? col.accessor
287
+ ? col.accessor(r)
288
+ : r[col.id]
289
+ : r;
241
290
  return applyFilterOp(value, f.op, f.value);
242
291
  });
243
292
  }
293
+ // Orden
244
294
  if (this.state.sortBy && this.state.sortDir) {
245
295
  const col = this.getColumn(this.state.sortBy);
246
296
  rows.sort((a, b) => {
@@ -250,6 +300,7 @@ export class DataTableManager {
250
300
  return this.state.sortDir === 'asc' ? cmp : -cmp;
251
301
  });
252
302
  }
303
+ // Paginación
253
304
  const start = (this.state.page - 1) * this.state.perPage;
254
305
  const end = start + this.state.perPage;
255
306
  this.state.total = rows.length;
@@ -4,43 +4,74 @@ export type TDataTableColumnKey<T> = Extract<keyof T, string>;
4
4
  export type TDataTableAccessor<T, R = any> = (row: T) => R;
5
5
  export type TDataTableColumnType = 'text' | 'number' | 'currency' | 'date' | 'datetime' | 'boolean' | 'badge' | 'link' | 'code';
6
6
  type TDataTableBaseColumn<T> = {
7
+ /** Título mostrado en el header */
7
8
  header: string;
9
+ /** Ancho fijo en px (si lo quieres forzar) */
8
10
  width?: number;
11
+ /** Ancho mínimo en px (usado también como fallback para auto-medición) */
9
12
  minWidth?: number;
13
+ /** Prioridad para ocultar en responsive (más alto = se oculta antes) */
10
14
  priority?: number;
15
+ /** Alineación del contenido de la celda */
11
16
  align?: 'left' | 'center' | 'right';
17
+ /** Si la columna es ordenable */
12
18
  sortable?: boolean;
19
+ /** Forzar ocultar en mobile, incluso si cabe */
13
20
  hideOnMobile?: boolean;
21
+ /** Label a usar cuando la columna se muestra en el panel colapsado (expand) */
14
22
  responsiveLabel?: string;
23
+ /** Clases extra para esta columna (celdas) */
15
24
  class?: string;
25
+ /** Tipo semántico de columna (para Cell: formateo, badge, boolean, etc.) */
16
26
  type?: TDataTableColumnType;
27
+ /** Opciones de Intl para number/date según el type */
17
28
  format?: Intl.NumberFormatOptions | Intl.DateTimeFormatOptions;
29
+ /** Labels personalizados para boolean true/false */
18
30
  trueLabel?: string;
19
31
  falseLabel?: string;
20
32
  };
21
33
  /** Columna ligada a una key existente de T (autocomplete de keys) */
22
34
  export type TDataTableKeyColumn<T, K extends TDataTableColumnKey<T> = TDataTableColumnKey<T>> = TDataTableBaseColumn<T> & {
35
+ /** ID de la columna, atado a una key real de T */
23
36
  id: K;
37
+ /** Accessor opcional, si quieres derivar el valor a partir de la key */
24
38
  accessor?: (row: T) => T[K];
39
+ /** Render personalizado para la celda en vista normal */
25
40
  renderCell?: (row: T) => any;
41
+ /** Render personalizado para la celda en vista colapsada (expand) */
26
42
  renderCollapsed?: (row: T) => any;
27
43
  };
28
- /** Columna virtual (id libre), requiere accessor explícito */
44
+ /** Columna virtual (id libre), pensada para valores derivados o acciones */
29
45
  export type TDataTableVirtualColumn<T> = TDataTableBaseColumn<T> & {
46
+ /** ID de la columna, no está restringido a keyof T */
30
47
  id: string;
48
+ /** Accessor requerido: cómo obtener el valor a mostrar en esta columna */
31
49
  accessor: TDataTableAccessor<T>;
50
+ /** Render personalizado para la celda en vista normal */
32
51
  renderCell?: (row: T) => any;
52
+ /** Render personalizado para la celda en vista colapsada (expand) */
33
53
  renderCollapsed?: (row: T) => any;
34
54
  };
35
- export type TDataTableColumnDef<T> = TDataTableKeyColumn<T>;
55
+ /**
56
+ * Definición de columna: puede ser una columna ligada a una key real de T
57
+ * o una columna virtual con accessor obligatorio.
58
+ */
59
+ export type TDataTableColumnDef<T> = TDataTableKeyColumn<T> | TDataTableVirtualColumn<T>;
36
60
  export type TDataTableFilterOp = 'equals' | 'not_equals' | 'contains' | 'starts_with' | 'ends_with' | 'gt' | 'lt' | 'gte' | 'lte' | 'in' | 'not_in' | 'is_empty' | 'is_not_empty';
37
61
  export type TDataTableFilterDef<T> = {
62
+ /** ID interno del filtro (para guardarlo, etc.) */
38
63
  id: string;
64
+ /** Label visible del filtro (UI) */
39
65
  label: string;
40
- columnId?: string;
66
+ /**
67
+ * ID de columna a la que aplica el filtro.
68
+ * Puede ser una key de T o una columna virtual (string).
69
+ */
70
+ columnId?: TDataTableColumnKey<T> | string;
41
71
  op: TDataTableFilterOp;
42
72
  value?: any;
43
- meta?: Record<string, any>;
73
+ /** Metadata auxiliar para la UI del filtro (select options, etc.) */
74
+ meta?: Record<string, unknown>;
44
75
  };
45
76
  export type TDataTableFetchResult<T> = {
46
77
  items: T[];
@@ -57,17 +88,29 @@ export type TDataTableFetchParams = {
57
88
  };
58
89
  export type TDataTableLoadMode = 'local' | 'remote' | 'cursor';
59
90
  export type TDataTableTableOptions<T> = {
91
+ /** ID lógico de la tabla (por si quieres guardar preferencias de usuario, etc.) */
60
92
  id?: string;
93
+ /** Definición de columnas */
61
94
  columns: TDataTableColumnDef<T>[];
95
+ /** Modo de carga de datos: local / remoto / cursor */
62
96
  loadMode: TDataTableLoadMode;
97
+ /** Datos en memoria para loadMode = 'local' */
63
98
  data?: T[];
99
+ /** Función fetcher para loadMode = 'remote' | 'cursor' */
64
100
  fetcher?: (params: TDataTableFetchParams) => Promise<TDataTableFetchResult<T>>;
101
+ /** Tamaño de página por defecto */
65
102
  perPage?: number;
103
+ /** Opciones disponibles de tamaño de página */
66
104
  perPageOptions?: number[];
105
+ /** Permitir selección múltiple */
67
106
  multiSelect?: boolean;
107
+ /** Mantener selección al cambiar de página */
68
108
  keepSelectionOnPageChange?: boolean;
109
+ /** Sort inicial (id de columna) */
69
110
  initialSortBy?: string | null;
111
+ /** Dirección de sort inicial */
70
112
  initialSortDir?: TDataTableSortDir;
113
+ /** Filtros iniciales */
71
114
  initialFilters?: TDataTableFilterDef<T>[];
72
115
  };
73
116
  export type TDataTableTableState<T> = {
@@ -23,7 +23,7 @@
23
23
  canGoPrev?: (idx: number) => Promise<boolean> | boolean;
24
24
 
25
25
  mode?: Mode;
26
- forwardTabBehavior?: ForwardTabBehavior; // NUEVO
26
+ forwardTabBehavior?: ForwardTabBehavior;
27
27
  stickyFooter?: boolean;
28
28
  showHeader?: boolean;
29
29
  showProgressBar?: boolean;
@@ -51,12 +51,10 @@
51
51
 
52
52
  let maxReached = $state(current);
53
53
 
54
- // Si avanzamos, actualizamos maxReached
55
54
  $effect(() => {
56
55
  if (current > maxReached) maxReached = current;
57
56
  });
58
57
 
59
- // Ajustar si la cantidad de pasos cambia
60
58
  $effect(() => {
61
59
  if (maxReached > steps.length) maxReached = steps.length;
62
60
  if (current > steps.length) current = steps.length;
@@ -146,12 +144,12 @@
146
144
  <button
147
145
  type="button"
148
146
  class={[
149
- 'group relative inline-flex items-center gap-3 rounded-xl border px-3 py-2 transition',
147
+ 'group relative inline-flex items-center gap-3 rounded-xl border px-3 py-2 text-sm transition-all duration-150',
150
148
  state === 'active'
151
- ? 'border-purple-600 bg-purple-50 text-purple-700 shadow-sm'
149
+ ? 'border-indigo-500/80 bg-white/95 text-neutral-900 shadow-sm shadow-indigo-500/20 dark:border-indigo-400/80 dark:bg-neutral-900/95 dark:text-neutral-50'
152
150
  : state === 'done'
153
- ? 'border-emerald-500/50 bg-emerald-50 text-emerald-700'
154
- : 'border-gray-300 bg-white text-gray-600 hover:bg-gray-50',
151
+ ? 'border-neutral-300 bg-neutral-50 text-neutral-800 dark:border-neutral-700 dark:bg-neutral-900 dark:text-neutral-100'
152
+ : 'border-transparent bg-transparent text-neutral-500 hover:border-neutral-300 hover:bg-neutral-50 dark:text-neutral-400 dark:hover:border-neutral-700 dark:hover:bg-neutral-900/80',
155
153
  allowed ? 'cursor-pointer' : 'pointer-events-none cursor-not-allowed opacity-60'
156
154
  ].join(' ')}
157
155
  aria-current={state === 'active' ? 'step' : undefined}
@@ -162,46 +160,51 @@
162
160
  >
163
161
  <div
164
162
  class={[
165
- 'flex h-8 w-8 shrink-0 items-center justify-center rounded-full border text-sm font-semibold',
163
+ 'flex h-8 w-8 shrink-0 items-center justify-center rounded-full border text-xs font-semibold',
166
164
  state === 'active'
167
- ? 'border-purple-600 bg-white text-purple-700'
165
+ ? 'border-indigo-500 bg-indigo-500/10 text-indigo-600 dark:border-indigo-400 dark:bg-indigo-500/15 dark:text-indigo-300'
168
166
  : state === 'done'
169
- ? 'border-emerald-500 bg-white text-emerald-700'
170
- : 'border-gray-300 bg-white text-gray-600'
167
+ ? 'border-emerald-500 bg-emerald-500/5 text-emerald-600 dark:border-emerald-400 dark:bg-emerald-500/10 dark:text-emerald-300'
168
+ : 'border-neutral-300 bg-white text-neutral-500 dark:border-neutral-600 dark:bg-neutral-900 dark:text-neutral-400'
171
169
  ].join(' ')}
172
170
  >
173
171
  {#if state === 'done'}
174
172
  <svg viewBox="0 0 20 20" class="h-4 w-4" aria-hidden="true">
175
- <path d="M7.5 11.5l-2-2 -1 1 3 3 7-7 -1-1 -6 6z" />
173
+ <path d="M7.5 11.5l-2-2 -1 1 3 3 7-7 -1-1 -6 6z" class="fill-current" />
176
174
  </svg>
177
175
  {:else}
178
176
  {idx}
179
177
  {/if}
180
178
  </div>
181
179
 
182
- <!-- Texto -->
183
180
  <div class="min-w-0 text-left">
184
- <div class="truncate text-sm font-medium">{s.label}</div>
181
+ <div class="truncate text-sm font-medium text-neutral-900 dark:text-neutral-50">
182
+ {s.label}
183
+ </div>
185
184
  {#if state === 'active'}
186
- <div class="truncate text-xs text-purple-700/70">En curso</div>
185
+ <div class="truncate text-xs text-indigo-500 dark:text-indigo-300">En curso</div>
187
186
  {:else if state === 'done'}
188
- <div class="truncate text-xs text-emerald-700/70">Completado</div>
187
+ <div class="truncate text-xs text-emerald-600 dark:text-emerald-300">
188
+ Completado
189
+ </div>
189
190
  {:else}
190
- <div class="truncate text-xs text-gray-500">Pendiente</div>
191
+ <div class="truncate text-xs text-neutral-500 dark:text-neutral-500">Pendiente</div>
191
192
  {/if}
192
193
  </div>
193
194
  </button>
194
195
 
195
196
  {#if showConnectors && i < steps.length - 1}
196
- <div class="h-px w-6 bg-gradient-to-r from-gray-300 to-gray-200 sm:w-8"></div>
197
+ <div
198
+ class="hidden h-px w-6 rounded-full bg-neutral-200/80 sm:block sm:w-8 dark:bg-neutral-800/80"
199
+ ></div>
197
200
  {/if}
198
201
  {/each}
199
202
  </div>
200
203
 
201
204
  {#if showProgressBar}
202
- <div class="mt-3 h-1.5 w-full rounded-full bg-gray-200">
205
+ <div class="mt-3 h-1.5 w-full rounded-full bg-neutral-200/80 dark:bg-neutral-800/80">
203
206
  <div
204
- class="h-1.5 rounded-full bg-gradient-to-r from-purple-600 to-blue-600 transition-all duration-300"
207
+ class="h-1.5 rounded-full bg-linear-to-r from-indigo-500 via-violet-500 to-blue-500 transition-all duration-300"
205
208
  style:width={`${pct}%`}
206
209
  ></div>
207
210
  </div>
@@ -224,14 +227,16 @@
224
227
 
225
228
  <div
226
229
  class={stickyFooter
227
- ? 'sticky right-0 bottom-0 left-0 bg-gradient-to-t from-white via-white to-transparent pt-4'
230
+ ? 'sticky right-0 bottom-0 left-0 bg-linear-to-t from-neutral-50 via-neutral-50/95 to-transparent pt-4 dark:from-neutral-950 dark:via-neutral-950/95 dark:to-transparent'
228
231
  : 'mt-6'}
229
232
  >
230
- <div class="flex w-full gap-3 border-t border-gray-200 pt-4">
233
+ <div class="flex w-full gap-3 border-t border-neutral-200 pt-4 dark:border-neutral-800">
231
234
  {#if footer}
232
235
  {@render footer({ current, total, next, prev, goTo })}
233
236
  {:else}
234
- <Button type="button" onclick={prev} disabled={current === 1}>Anterior</Button>
237
+ <Button type="button" variant="outline" onclick={prev} disabled={current === 1}>
238
+ Anterior
239
+ </Button>
235
240
  <Button type="button" onclick={next} disabled={current === total}>Siguiente</Button>
236
241
  {/if}
237
242
  </div>
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './stores/I18n.svelte.js';
2
+ export * from './stores/FormStatus.svelte.js';
2
3
  export * from './settings/index.js';
3
4
  export * from './types/index.js';
4
5
  export * from './components/index.js';
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './stores/I18n.svelte.js';
2
+ export * from './stores/FormStatus.svelte.js';
2
3
  export * from './settings/index.js';
3
4
  export * from './types/index.js';
4
5
  export * from './components/index.js';
@@ -0,0 +1,61 @@
1
+ type BuiltinFormTypes = 'delete' | 'duplicate' | 'change_status' | 'preview' | 'download' | 'create' | 'update' | 'error';
2
+ type ActionKeys<P extends Record<string, any>> = keyof P | BuiltinFormTypes;
3
+ type Payloads<P extends Record<string, any>> = {
4
+ [K in ActionKeys<P>]: K extends keyof P ? P[K] : unknown;
5
+ };
6
+ export interface IFormStatusStore<P extends Record<string, any> = {}> {
7
+ open<K extends ActionKeys<P>>(...args: Payloads<P>[K] extends void ? [type: K] : [type: K, data: Payloads<P>[K]]): void;
8
+ close(): void;
9
+ readonly type: ActionKeys<P> | null;
10
+ readonly data: Payloads<P>[ActionKeys<P>] | null;
11
+ readonly isOpen: boolean;
12
+ is<K extends ActionKeys<P>>(type: K): this is {
13
+ type: K;
14
+ data: Payloads<P>[K];
15
+ };
16
+ isSome<K extends ActionKeys<P>>(type: K[]): this is {
17
+ type: K;
18
+ data: Payloads<P>[K];
19
+ };
20
+ isNone<K extends ActionKeys<P>>(type: K[]): this is {
21
+ type: K;
22
+ data: Payloads<P>[K];
23
+ };
24
+ match<R>(handlers: Partial<{
25
+ [K in ActionKeys<P>]: (data: Payloads<P>[K]) => R;
26
+ }> & {
27
+ _: () => R;
28
+ }): R;
29
+ get<K extends keyof P>(type: K): P[K] | undefined;
30
+ expect<K extends keyof P>(type: K, msg?: string): P[K];
31
+ select<K extends keyof P, R>(type: K, map: (data: P[K]) => R, fallback?: R): R | undefined;
32
+ }
33
+ export declare class StoreFormStatus<P extends Record<string, any> = {}> implements IFormStatusStore<P> {
34
+ #private;
35
+ open<K extends ActionKeys<P>>(...args: Payloads<P>[K] extends void ? [type: K] : [type: K, data: Payloads<P>[K]]): void;
36
+ close: () => void;
37
+ get type(): ActionKeys<P> | null;
38
+ get data(): Payloads<P>[ActionKeys<P>] | null;
39
+ get isOpen(): boolean;
40
+ is<K extends ActionKeys<P>>(type: K): this is {
41
+ type: K;
42
+ data: Payloads<P>[K];
43
+ };
44
+ isSome<K extends ActionKeys<P>>(type: K[]): this is {
45
+ type: K;
46
+ data: Payloads<P>[K];
47
+ };
48
+ isNone<K extends ActionKeys<P>>(type: K[]): this is {
49
+ type: K;
50
+ data: Payloads<P>[K];
51
+ };
52
+ match<R>(handlers: Partial<{
53
+ [K in ActionKeys<P>]: (data: Payloads<P>[K]) => R;
54
+ }> & {
55
+ _: () => R;
56
+ }): R;
57
+ get<K extends keyof P>(type: K): P[K] | undefined;
58
+ expect<K extends keyof P>(type: K, msg?: string): P[K];
59
+ select<K extends keyof P, R>(type: K, map: (data: P[K]) => R, fallback?: R): R | undefined;
60
+ }
61
+ export {};
@@ -0,0 +1,45 @@
1
+ export class StoreFormStatus {
2
+ #type = $state(null);
3
+ #data = $state(null);
4
+ open(...args) {
5
+ const [type, maybeData] = args;
6
+ this.#type = type;
7
+ this.#data = (maybeData ?? null);
8
+ }
9
+ close = () => {
10
+ this.#type = null;
11
+ this.#data = null;
12
+ };
13
+ get type() { return this.#type; }
14
+ get data() { return this.#data; }
15
+ get isOpen() { return this.#type !== null; }
16
+ is(type) {
17
+ return this.#type === type;
18
+ }
19
+ isSome(type) {
20
+ return this.#type !== null && type.includes(this.#type);
21
+ }
22
+ isNone(type) {
23
+ return this.#type === null || !type.includes(this.#type);
24
+ }
25
+ match(handlers) {
26
+ if (!this.isOpen || this.#type === null)
27
+ return handlers._();
28
+ const fn = handlers[this.#type];
29
+ return fn ? fn(this.#data) : handlers._();
30
+ }
31
+ get(type) {
32
+ return this.#type === type
33
+ ? (this.#data ?? undefined)
34
+ : undefined;
35
+ }
36
+ expect(type, msg = `Expected state '${String(type)}'`) {
37
+ const value = this.get(type);
38
+ if (value === undefined)
39
+ throw new Error(msg);
40
+ return value;
41
+ }
42
+ select(type, map, fallback) {
43
+ return this.#type === type ? map(this.#data) : fallback;
44
+ }
45
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@r2digisolutions/ui",
3
- "version": "0.26.11",
3
+ "version": "0.27.0",
4
4
  "private": false,
5
5
  "packageManager": "bun@1.3.2",
6
6
  "publishConfig": {
@@ -49,41 +49,41 @@
49
49
  "devDependencies": {
50
50
  "@changesets/cli": "2.29.7",
51
51
  "@chromatic-com/storybook": "4.1.2",
52
- "@eslint/compat": "1.4.1",
52
+ "@eslint/compat": "2.0.0",
53
53
  "@playwright/test": "1.56.1",
54
54
  "@storybook/addon-essentials": "8.6.14",
55
55
  "@storybook/addon-interactions": "8.6.14",
56
56
  "@storybook/addon-svelte-csf": "5.0.10",
57
57
  "@storybook/blocks": "8.6.14",
58
- "@storybook/svelte": "10.0.6",
59
- "@storybook/sveltekit": "10.0.6",
58
+ "@storybook/svelte": "10.0.7",
59
+ "@storybook/sveltekit": "10.0.7",
60
60
  "@storybook/test": "8.6.14",
61
61
  "@sveltejs/adapter-static": "3.0.10",
62
- "@sveltejs/kit": "2.48.4",
62
+ "@sveltejs/kit": "2.48.5",
63
63
  "@sveltejs/package": "2.5.4",
64
64
  "@sveltejs/vite-plugin-svelte": "6.2.1",
65
65
  "@tailwindcss/postcss": "4.1.17",
66
- "@testing-library/svelte": "5.2.8",
67
- "@vitest/browser": "4.0.8",
66
+ "@testing-library/svelte": "5.2.9",
67
+ "@vitest/browser": "4.0.9",
68
68
  "changeset": "0.2.6",
69
69
  "eslint": "9.39.1",
70
70
  "eslint-config-prettier": "10.1.8",
71
71
  "eslint-plugin-svelte": "3.13.0",
72
72
  "globals": "16.5.0",
73
- "jsdom": "27.1.0",
73
+ "jsdom": "27.2.0",
74
74
  "lucide-svelte": "0.553.0",
75
75
  "prettier": "3.6.2",
76
76
  "prettier-plugin-svelte": "3.4.0",
77
77
  "prettier-plugin-tailwindcss": "0.7.1",
78
78
  "publint": "0.3.15",
79
- "storybook": "10.0.6",
79
+ "storybook": "10.0.7",
80
80
  "svelte": "5.43.6",
81
81
  "svelte-check": "4.3.4",
82
82
  "tailwindcss": "4.1.17",
83
83
  "typescript": "5.9.3",
84
84
  "typescript-eslint": "8.46.4",
85
85
  "vite": "7.2.2",
86
- "vitest": "4.0.8"
86
+ "vitest": "4.0.9"
87
87
  },
88
88
  "dependencies": {
89
89
  "@tailwindcss/container-queries": "0.1.1",