sveltacular 1.0.16 → 1.0.17

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.
@@ -25,17 +25,23 @@
25
25
  } from './cell-renderers.js';
26
26
  import type { Snippet } from 'svelte';
27
27
  import { useVirtualList } from '../helpers/use-virtual-list.svelte.js';
28
+ import type { ButtonVariant, FormFieldSizeOptions } from '../types/form.js';
28
29
 
29
30
  type PaginationEvent = (pagination: PaginationProperties) => void;
30
31
 
31
32
  interface Action {
32
33
  text: string;
33
- onClick: (row: JsonObject) => unknown;
34
+ variant?: ButtonVariant;
35
+ href?: (row: JsonObject) => string;
36
+ onClick?: (row: JsonObject) => unknown;
34
37
  }
35
38
 
36
39
  interface Actions {
37
40
  text?: string;
38
- type?: string;
41
+ type?: 'buttons' | 'dropdown';
42
+ variant?: ButtonVariant | 'default';
43
+ size?: FormFieldSizeOptions;
44
+ align?: 'left' | 'center' | 'right';
39
45
  items: Action[];
40
46
  }
41
47
 
@@ -113,10 +119,15 @@
113
119
  let colCount = $derived(
114
120
  Math.max(1, visibleCols.length) + (hasActionCol ? 1 : 0) + (hasSelectionCol ? 1 : 0)
115
121
  );
116
-
122
+ let actionButtonVariant = $derived.by(() => {
123
+ return !actions?.variant || actions.variant === 'default' ? 'outline' : actions.variant;
124
+ });
125
+ let actionButtonSize = $derived(actions?.size ?? 'sm');
126
+ let actionAlign = $derived(actions?.align ?? 'center');
127
+
117
128
  // Track selected count from selection change callbacks
118
129
  let internalSelectedCount = $state(0);
119
-
130
+
120
131
  // Sync selectedCount with internal tracking
121
132
  $effect(() => {
122
133
  selectedCount = internalSelectedCount;
@@ -198,10 +209,16 @@
198
209
  {selectionMode}
199
210
  {rowIdKey}
200
211
  onSort={handleSortChange}
201
- onSelectionChange={(selectedRows) => {
202
- onSelectionChange?.(selectedRows);
212
+ onSelectionChange={(selectedRowIds) => {
213
+ // Convert selected IDs to actual row objects
214
+ // Use sortedRows (before pagination) to include all selected rows, not just current page
215
+ const selectedRowObjects = (sortedRows ?? []).filter((row) => {
216
+ const id = row[rowIdKey] as string | number;
217
+ return id !== undefined && selectedRowIds.includes(id);
218
+ });
219
+ onSelectionChange?.(selectedRowObjects);
203
220
  // Track selected count from the callback
204
- internalSelectedCount = selectedRows.length;
221
+ internalSelectedCount = selectedRowIds.length;
205
222
  }}
206
223
  >
207
224
  {#if children}
@@ -228,7 +245,7 @@
228
245
  </TableHeaderCell>
229
246
  {/each}
230
247
  {#if hasActionCol}
231
- <TableHeaderCell type="actions">Actions</TableHeaderCell>
248
+ <TableHeaderCell type="actions" align={actionAlign}>Actions</TableHeaderCell>
232
249
  {/if}
233
250
  </tr>
234
251
  </TableHeader>
@@ -284,26 +301,32 @@
284
301
  </TableCell>
285
302
  {/each}
286
303
  {#if hasActionCol && actions}
287
- <TableCell type="actions">
304
+ <TableCell type="actions" align={actionAlign}>
288
305
  {#if actions.type === 'dropdown'}
289
306
  <DropdownButton text={actions.text ?? ''} variant="ghost">
290
307
  {#each actions.items as action}
291
- <DropdownItem onClick={() => action.onClick(row)}
308
+ <DropdownItem
309
+ href={action.href ? action.href(row) : undefined}
310
+ onClick={action.onClick ? () => action.onClick?.(row) : undefined}
292
311
  >{action.text}</DropdownItem
293
312
  >
294
313
  {/each}
295
314
  </DropdownButton>
296
315
  {:else}
297
- {#each actions.items as action}
298
- <Button
299
- collapse={true}
300
- size="sm"
301
- type="button"
302
- variant={actions.type === 'outline' ? 'outline' : 'secondary'}
303
- onClick={() => action.onClick(row)}
304
- label={action.text}
305
- />
306
- {/each}
316
+ <div class="actions">
317
+ {#each actions.items as action}
318
+ {@const buttonVariant = action.variant ?? actionButtonVariant}
319
+ <Button
320
+ collapse={true}
321
+ type="button"
322
+ variant={buttonVariant}
323
+ size={actionButtonSize}
324
+ href={action.href ? action.href(row) : undefined}
325
+ onClick={action.onClick ? () => action.onClick?.(row) : undefined}
326
+ label={action.text}
327
+ />
328
+ {/each}
329
+ </div>
307
330
  {/if}
308
331
  </TableCell>
309
332
  {/if}
@@ -336,24 +359,31 @@
336
359
  </TableCell>
337
360
  {/each}
338
361
  {#if hasActionCol && actions}
339
- <TableCell type="actions">
362
+ <TableCell type="actions" align={actionAlign}>
340
363
  {#if actions.type === 'dropdown'}
341
364
  <DropdownButton text={actions.text ?? ''} variant="ghost">
342
365
  {#each actions.items as action}
343
- <DropdownItem onClick={() => action.onClick(row)}>{action.text}</DropdownItem>
366
+ <DropdownItem
367
+ href={action.href ? action.href(row) : undefined}
368
+ onClick={action.onClick ? () => action.onClick?.(row) : undefined}
369
+ >{action.text}</DropdownItem
370
+ >
344
371
  {/each}
345
372
  </DropdownButton>
346
373
  {:else}
347
- {#each actions.items as action}
348
- <Button
349
- collapse={true}
350
- size="sm"
351
- type="button"
352
- variant={actions.type === 'outline' ? 'outline' : 'secondary'}
353
- onClick={() => action.onClick(row)}
354
- label={action.text}
355
- />
356
- {/each}
374
+ <div class="actions">
375
+ {#each actions.items as action}
376
+ {@const buttonVariant = action.variant ?? actionButtonVariant}
377
+ <Button
378
+ type="button"
379
+ variant={buttonVariant}
380
+ size={actionButtonSize}
381
+ href={action.href ? action.href(row) : undefined}
382
+ onClick={action.onClick ? () => action.onClick?.(row) : undefined}
383
+ label={action.text}
384
+ />
385
+ {/each}
386
+ </div>
357
387
  {/if}
358
388
  </TableCell>
359
389
  {/if}
@@ -412,4 +442,9 @@ td.footer-cell :global(.pagination) {
412
442
  display: flex;
413
443
  justify-content: center;
414
444
  align-items: center;
445
+ }
446
+
447
+ .actions {
448
+ display: flex;
449
+ gap: 0.5rem;
415
450
  }</style>
@@ -1,13 +1,19 @@
1
1
  import type { ColumnDef, JsonObject, PaginationProperties } from '../types/data.js';
2
2
  import type { Snippet } from 'svelte';
3
+ import type { ButtonVariant, FormFieldSizeOptions } from '../types/form.js';
3
4
  type PaginationEvent = (pagination: PaginationProperties) => void;
4
5
  interface Action {
5
6
  text: string;
6
- onClick: (row: JsonObject) => unknown;
7
+ variant?: ButtonVariant;
8
+ href?: (row: JsonObject) => string;
9
+ onClick?: (row: JsonObject) => unknown;
7
10
  }
8
11
  interface Actions {
9
12
  text?: string;
10
- type?: string;
13
+ type?: 'buttons' | 'dropdown';
14
+ variant?: ButtonVariant | 'default';
15
+ size?: FormFieldSizeOptions;
16
+ align?: 'left' | 'center' | 'right';
11
17
  items: Action[];
12
18
  }
13
19
  type $$ComponentProps = {
@@ -4,7 +4,7 @@ export interface TableContextConfig<T extends JsonObject = JsonObject> {
4
4
  selectionMode?: 'none' | 'single' | 'multi';
5
5
  rowIdKey?: keyof T & string;
6
6
  onSort?: (column: string, direction: SortDirection) => void;
7
- onSelectionChange?: (selectedRows: T[]) => void;
7
+ onSelectionChange?: (selectedRowIds: (string | number)[]) => void;
8
8
  rows?: T[];
9
9
  }
10
10
  export declare class TableContext<T extends JsonObject = JsonObject> {
@@ -101,9 +101,9 @@ export class TableContext {
101
101
  this.notifySelectionChange(rows);
102
102
  }
103
103
  notifySelectionChange(rows) {
104
- if (this.config.onSelectionChange && rows) {
105
- const selectedRows = this.getSelectedRows(rows);
106
- this.config.onSelectionChange(selectedRows);
104
+ if (this.config.onSelectionChange) {
105
+ const selectedRowIds = Array.from(this.selectedIds);
106
+ this.config.onSelectionChange(selectedRowIds);
107
107
  }
108
108
  }
109
109
  selectRange(startIndex, endIndex, rows) {
@@ -78,18 +78,33 @@
78
78
  // Handle cell click - toggle selection when clicking anywhere in the cell
79
79
  function handleCellClick(event: MouseEvent) {
80
80
  const target = event.target as HTMLElement;
81
+ const currentTarget = event.currentTarget as HTMLElement;
82
+
83
+ // If the click is on the label or any of its children (input, span, etc.),
84
+ // let the component's native handlers take care of it
85
+ const label = currentTarget.querySelector('label');
86
+ if (label && (target === label || label.contains(target))) {
87
+ // For single selection, we need to handle deselection ourselves
88
+ // since native radio buttons don't allow unchecking
89
+ if (selectionMode === 'single' && rowId !== undefined) {
90
+ // Check if already selected and toggle off if so
91
+ if (context?.radioGroup === String(rowId)) {
92
+ event.preventDefault();
93
+ handleRadioChange(String(rowId));
94
+ }
95
+ // If not selected, let the native handler select it
96
+ }
97
+ // For multi-selection, always let the native checkbox handler work
98
+ return;
99
+ }
81
100
 
82
- if (selectionMode === 'multi') {
83
- // For checkboxes: skip if clicking on input to avoid double-toggle
84
- if (target.tagName === 'INPUT') return;
85
- handleCheckboxChange({ isChecked: !localChecked, value: '' });
86
- } else if (selectionMode === 'single' && rowId !== undefined) {
87
- // For radios: always handle the click ourselves to enable deselection
88
- // Native radio buttons don't allow unchecking, so we take control
89
- if (target.tagName === 'INPUT') {
90
- event.preventDefault();
101
+ // Only handle cell click if clicking directly on the cell (not on the label/input)
102
+ if (target === currentTarget) {
103
+ if (selectionMode === 'multi') {
104
+ handleCheckboxChange({ isChecked: !localChecked, value: '' });
105
+ } else if (selectionMode === 'single' && rowId !== undefined) {
106
+ handleRadioChange(String(rowId));
91
107
  }
92
- handleRadioChange(String(rowId));
93
108
  }
94
109
  }
95
110
  </script>
@@ -52,11 +52,21 @@
52
52
 
53
53
  // Handle header cell click - toggle select all when clicking anywhere in the cell
54
54
  function handleCellClick(event: MouseEvent) {
55
- // Don't double-toggle if the click was on the actual input element
56
55
  const target = event.target as HTMLElement;
57
- if (target.tagName === 'INPUT') return;
56
+ const currentTarget = event.currentTarget as HTMLElement;
58
57
 
59
- handleSelectAllChange({ isChecked: !localChecked, value: '' });
58
+ // If the click is on the label or any of its children (input, span, etc.),
59
+ // let the component's native handlers take care of it
60
+ const label = currentTarget.querySelector('label');
61
+ if (label && (target === label || label.contains(target))) {
62
+ // Let the native checkbox handler work
63
+ return;
64
+ }
65
+
66
+ // Only handle cell click if clicking directly on the cell (not on the label/input)
67
+ if (target === currentTarget) {
68
+ handleSelectAllChange({ isChecked: !localChecked, value: '' });
69
+ }
60
70
  }
61
71
  </script>
62
72
 
@@ -21,7 +21,7 @@
21
21
  rowIdKey?: string;
22
22
  stickyHeader?: boolean;
23
23
  onSort?: (column: string, direction: 'asc' | 'desc') => void;
24
- onSelectionChange?: (selectedRows: JsonObject[]) => void;
24
+ onSelectionChange?: (selectedRowIds: (string | number)[]) => void;
25
25
  } = $props();
26
26
 
27
27
  // Create table context for child components
@@ -8,7 +8,7 @@ type $$ComponentProps = {
8
8
  rowIdKey?: string;
9
9
  stickyHeader?: boolean;
10
10
  onSort?: (column: string, direction: 'asc' | 'desc') => void;
11
- onSelectionChange?: (selectedRows: JsonObject[]) => void;
11
+ onSelectionChange?: (selectedRowIds: (string | number)[]) => void;
12
12
  };
13
13
  declare const Table: import("svelte").Component<$$ComponentProps, {}, "">;
14
14
  type Table = ReturnType<typeof Table>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sveltacular",
3
- "version": "1.0.16",
3
+ "version": "1.0.17",
4
4
  "description": "A Svelte component library",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",