@marianmeres/stuic 3.115.0 → 3.116.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.
Files changed (86) hide show
  1. package/API.md +297 -304
  2. package/dist/actions/dim-behind/index.css +4 -1
  3. package/dist/actions/focus-trap.js +3 -1
  4. package/dist/components/Accordion/README.md +17 -17
  5. package/dist/components/Accordion/index.css +4 -2
  6. package/dist/components/AssetsPreview/README.md +7 -7
  7. package/dist/components/AssetsPreview/_internal/assets-preview-types.d.ts +1 -2
  8. package/dist/components/AssetsPreview/_internal/assets-preview-utils.d.ts +1 -1
  9. package/dist/components/AssetsPreview/_internal/assets-preview-utils.js +9 -3
  10. package/dist/components/Avatar/Avatar.svelte +1 -3
  11. package/dist/components/Avatar/README.md +33 -27
  12. package/dist/components/Book/Book.svelte +6 -1
  13. package/dist/components/Book/README.md +22 -20
  14. package/dist/components/Book/index.css +4 -2
  15. package/dist/components/Button/README.md +17 -17
  16. package/dist/components/Card/Card.svelte +25 -8
  17. package/dist/components/Card/README.md +52 -56
  18. package/dist/components/Card/index.css +2 -1
  19. package/dist/components/Carousel/Carousel.svelte +1 -3
  20. package/dist/components/Carousel/README.md +28 -28
  21. package/dist/components/Cart/Cart.svelte +2 -1
  22. package/dist/components/Cart/README.md +25 -25
  23. package/dist/components/Checkout/CheckoutGuestOrLoginForm.svelte +8 -3
  24. package/dist/components/Checkout/CheckoutShippingStep.svelte +1 -2
  25. package/dist/components/Checkout/README.md +143 -130
  26. package/dist/components/CronInput/CronInput.svelte +64 -60
  27. package/dist/components/CronInput/README.md +46 -46
  28. package/dist/components/DataTable/DataTable.svelte +5 -1
  29. package/dist/components/DataTable/README.md +78 -63
  30. package/dist/components/DropdownMenu/DropdownMenu.svelte +6 -2
  31. package/dist/components/DropdownMenu/README.md +33 -27
  32. package/dist/components/EmailVerifyForm/EmailVerifyForm.svelte +2 -9
  33. package/dist/components/EmailVerifyForm/README.md +30 -30
  34. package/dist/components/Header/Header.svelte +161 -165
  35. package/dist/components/Header/README.md +7 -7
  36. package/dist/components/IconSwap/README.md +20 -15
  37. package/dist/components/IconSwap/index.css +2 -1
  38. package/dist/components/ImageCycler/ImageCycler.svelte +19 -5
  39. package/dist/components/ImageCycler/ImageCycler.svelte.d.ts +14 -10
  40. package/dist/components/ImageCycler/README.md +15 -15
  41. package/dist/components/ImageCycler/index.css +26 -20
  42. package/dist/components/Input/FieldFile.svelte +1 -3
  43. package/dist/components/Input/FieldInput.svelte +1 -3
  44. package/dist/components/Input/FieldKeyValues.svelte +2 -6
  45. package/dist/components/Input/FieldObject.svelte +2 -1
  46. package/dist/components/Input/README.md +11 -11
  47. package/dist/components/KbdShortcut/index.css +2 -1
  48. package/dist/components/LoginForm/LoginForm.svelte +1 -7
  49. package/dist/components/LoginForm/README.md +46 -46
  50. package/dist/components/ModalDialog/index.css +2 -1
  51. package/dist/components/Notifications/index.css +24 -6
  52. package/dist/components/OtpInput/OtpInput.svelte +0 -0
  53. package/dist/components/OtpInput/README.md +15 -19
  54. package/dist/components/OtpInput/index.css +1 -4
  55. package/dist/components/OtpInput/index.d.ts +1 -1
  56. package/dist/components/OtpInput/index.js +1 -1
  57. package/dist/components/Pill/README.md +41 -40
  58. package/dist/components/Pill/index.css +3 -6
  59. package/dist/components/PricingTable/README.md +86 -86
  60. package/dist/components/PricingTable/index.css +20 -35
  61. package/dist/components/RegisterForm/README.md +60 -60
  62. package/dist/components/RegisterForm/RegisterForm.svelte +1 -7
  63. package/dist/components/Separator/README.md +7 -7
  64. package/dist/components/TabbedMenu/index.css +6 -3
  65. package/dist/components/Tree/README.md +67 -67
  66. package/dist/components/UserAvatarMenu/UserAvatarMenu.svelte +1 -5
  67. package/dist/components/WithSidePanel/index.css +4 -4
  68. package/dist/index.css +12 -8
  69. package/dist/utils/design-tokens.d.ts +1 -1
  70. package/dist/utils/design-tokens.js +1 -1
  71. package/docs/architecture.md +7 -7
  72. package/docs/component-testing/00-overview-and-roadmap.md +19 -19
  73. package/docs/component-testing/01-framework-setup.md +6 -6
  74. package/docs/component-testing/02-test-conventions.md +6 -5
  75. package/docs/component-testing/03-component-coverage-roadmap.md +27 -27
  76. package/docs/component-testing/04-hard-cases-and-e2e.md +8 -8
  77. package/docs/component-testing/05-ci.md +3 -3
  78. package/docs/component-testing/PROGRESS.md +30 -26
  79. package/docs/component-testing/README.md +8 -8
  80. package/docs/conventions.md +25 -25
  81. package/docs/domains/components.md +386 -385
  82. package/docs/domains/theming.md +24 -24
  83. package/docs/domains/utils.md +22 -25
  84. package/docs/testing.md +2 -2
  85. package/docs/upgrading.md +32 -28
  86. package/package.json +2 -1
@@ -90,8 +90,19 @@
90
90
  ] as const;
91
91
 
92
92
  const MONTH_NAMES = [
93
- "", "Jan", "Feb", "Mar", "Apr", "May", "Jun",
94
- "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
93
+ "",
94
+ "Jan",
95
+ "Feb",
96
+ "Mar",
97
+ "Apr",
98
+ "May",
99
+ "Jun",
100
+ "Jul",
101
+ "Aug",
102
+ "Sep",
103
+ "Oct",
104
+ "Nov",
105
+ "Dec",
95
106
  ];
96
107
 
97
108
  const DAY_NAMES = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
@@ -120,7 +131,9 @@
120
131
  } else if (hour.startsWith("*/")) {
121
132
  segments.push(`every ${hour.slice(2)} hours`);
122
133
  } else {
123
- segments.push(`at ${hour.padStart(2, "0")}:${minute === "*" ? "00" : minute.padStart(2, "0")}`);
134
+ segments.push(
135
+ `at ${hour.padStart(2, "0")}:${minute === "*" ? "00" : minute.padStart(2, "0")}`
136
+ );
124
137
  // remove the minute segment if we have a specific hour
125
138
  if (minute !== "*" && !minute.includes("/")) {
126
139
  segments.length = 0;
@@ -167,7 +180,9 @@
167
180
  }
168
181
  }
169
182
 
170
- return segments.length ? segments[0].charAt(0).toUpperCase() + segments.join(", ").slice(1) : "";
183
+ return segments.length
184
+ ? segments[0].charAt(0).toUpperCase() + segments.join(", ").slice(1)
185
+ : "";
171
186
  }
172
187
  </script>
173
188
 
@@ -240,21 +255,11 @@
240
255
  }
241
256
 
242
257
  // Effective show flags — mode overrides explicit props when defined
243
- let _showPresets = $derived(
244
- hasModeToggle ? mode === "predefined" : showPresets
245
- );
246
- let _showFields = $derived(
247
- hasModeToggle ? mode === "manual" : showFields
248
- );
249
- let _showRawInput = $derived(
250
- hasModeToggle ? false : showRawInput
251
- );
252
- let _showDescription = $derived(
253
- hasModeToggle ? mode === "manual" : showDescription
254
- );
255
- let _showNextRun = $derived(
256
- hasModeToggle ? mode === "manual" : showNextRun
257
- );
258
+ let _showPresets = $derived(hasModeToggle ? mode === "predefined" : showPresets);
259
+ let _showFields = $derived(hasModeToggle ? mode === "manual" : showFields);
260
+ let _showRawInput = $derived(hasModeToggle ? false : showRawInput);
261
+ let _showDescription = $derived(hasModeToggle ? mode === "manual" : showDescription);
262
+ let _showNextRun = $derived(hasModeToggle ? mode === "manual" : showNextRun);
258
263
 
259
264
  const BTN_CLS = [
260
265
  "toggle-btn",
@@ -378,7 +383,9 @@
378
383
  }
379
384
 
380
385
  // When only presets are visible, render as a plain select (no extra padding/border)
381
- let presetsOnly = $derived(_showPresets && !_showFields && !_showRawInput && !_showDescription && !_showNextRun);
386
+ let presetsOnly = $derived(
387
+ _showPresets && !_showFields && !_showRawInput && !_showDescription && !_showNextRun
388
+ );
382
389
 
383
390
  // Minute tick — triggers re-evaluation of "Next: ..." every 60s
384
391
  let _tick = $state(0);
@@ -463,48 +470,46 @@
463
470
  {/if}
464
471
 
465
472
  {#if _showFields}
466
- <div
467
- class={unstyled
468
- ? classFields
469
- : twMerge("stuic-cron-input-fields", classFields)}
470
- >
471
- {#each FIELD_DEFS as def}
472
- <div
473
- class={unstyled
474
- ? classField
475
- : twMerge("stuic-cron-input-field", classField)}
476
- >
477
- <span
473
+ <div
474
+ class={unstyled
475
+ ? classFields
476
+ : twMerge("stuic-cron-input-fields", classFields)}
477
+ >
478
+ {#each FIELD_DEFS as def}
479
+ <div
478
480
  class={unstyled
479
- ? classFieldLabel
480
- : twMerge("stuic-cron-input-field-label", classFieldLabel)}
481
+ ? classField
482
+ : twMerge("stuic-cron-input-field", classField)}
481
483
  >
482
- {def.label}
483
- </span>
484
- <input
485
- type="text"
486
- class={unstyled
487
- ? classFieldInput
488
- : twMerge("stuic-cron-input-field-input", classFieldInput)}
489
- bind:value={fields[def.key]}
490
- oninput={onFieldInput}
491
- placeholder={def.placeholder}
492
- aria-label={`${def.label} (${def.placeholder})`}
493
- {disabled}
494
- autocomplete="off"
495
- spellcheck={false}
496
- />
497
- </div>
498
- {/each}
499
- </div>
484
+ <span
485
+ class={unstyled
486
+ ? classFieldLabel
487
+ : twMerge("stuic-cron-input-field-label", classFieldLabel)}
488
+ >
489
+ {def.label}
490
+ </span>
491
+ <input
492
+ type="text"
493
+ class={unstyled
494
+ ? classFieldInput
495
+ : twMerge("stuic-cron-input-field-input", classFieldInput)}
496
+ bind:value={fields[def.key]}
497
+ oninput={onFieldInput}
498
+ placeholder={def.placeholder}
499
+ aria-label={`${def.label} (${def.placeholder})`}
500
+ {disabled}
501
+ autocomplete="off"
502
+ spellcheck={false}
503
+ />
504
+ </div>
505
+ {/each}
506
+ </div>
500
507
  {/if}
501
508
 
502
509
  {#if _showRawInput}
503
510
  <input
504
511
  type="text"
505
- class={unstyled
506
- ? classRaw
507
- : twMerge("stuic-cron-input-raw", classRaw)}
512
+ class={unstyled ? classRaw : twMerge("stuic-cron-input-raw", classRaw)}
508
513
  bind:value={rawValue}
509
514
  oninput={onRawInput}
510
515
  placeholder="* * * * *"
@@ -524,16 +529,15 @@
524
529
  {humanDescription}
525
530
  </div>
526
531
  {/if}
527
-
528
532
  </div>
529
533
  {#if hasModeToggle}
530
534
  <button
531
535
  type="button"
532
- class={unstyled
533
- ? classToggleButton
534
- : twMerge(BTN_CLS, classToggleButton)}
536
+ class={unstyled ? classToggleButton : twMerge(BTN_CLS, classToggleButton)}
535
537
  onclick={toggleMode}
536
- aria-label={mode === "predefined" ? "Switch to manual input" : "Switch to presets"}
538
+ aria-label={mode === "predefined"
539
+ ? "Switch to manual input"
540
+ : "Switch to presets"}
537
541
  {disabled}
538
542
  use:tooltip={() => ({
539
543
  enabled: true,
@@ -10,9 +10,9 @@ The cron expression string is the single source of truth; field / preset / raw s
10
10
 
11
11
  ```svelte
12
12
  <script lang="ts">
13
- import { CronInput } from "@marianmeres/stuic";
13
+ import { CronInput } from "@marianmeres/stuic";
14
14
 
15
- let expression = $state("0 9 * * 1-5");
15
+ let expression = $state("0 9 * * 1-5");
16
16
  </script>
17
17
 
18
18
  <CronInput bind:value={expression} label="Schedule" />
@@ -24,13 +24,13 @@ Default `mode` is `"predefined"` — users see the preset picker with a toggle b
24
24
 
25
25
  ```svelte
26
26
  <CronInput
27
- bind:value={expression}
28
- mode={undefined}
29
- showPresets
30
- showFields
31
- showRawInput
32
- showDescription
33
- showNextRun
27
+ bind:value={expression}
28
+ mode={undefined}
29
+ showPresets
30
+ showFields
31
+ showRawInput
32
+ showDescription
33
+ showNextRun
34
34
  />
35
35
  ```
36
36
 
@@ -38,13 +38,13 @@ Default `mode` is `"predefined"` — users see the preset picker with a toggle b
38
38
 
39
39
  ```svelte
40
40
  <script lang="ts">
41
- import { CronInput, type CronPreset } from "@marianmeres/stuic";
41
+ import { CronInput, type CronPreset } from "@marianmeres/stuic";
42
42
 
43
- const presets: CronPreset[] = [
44
- { label: "Top of every hour", value: "0 * * * *" },
45
- { label: "Daily at 09:00", value: "0 9 * * *" },
46
- { label: "Every 5 minutes", value: "*/5 * * * *" },
47
- ];
43
+ const presets: CronPreset[] = [
44
+ { label: "Top of every hour", value: "0 * * * *" },
45
+ { label: "Daily at 09:00", value: "0 9 * * *" },
46
+ { label: "Every 5 minutes", value: "*/5 * * * *" },
47
+ ];
48
48
  </script>
49
49
 
50
50
  <CronInput bind:value={expression} {presets} />
@@ -56,11 +56,11 @@ For places outside the CronInput that need the next-fire time (e.g. a dashboard
56
56
 
57
57
  ```svelte
58
58
  <script lang="ts">
59
- import { CronNextRun } from "@marianmeres/stuic";
60
- import { onDestroy } from "svelte";
59
+ import { CronNextRun } from "@marianmeres/stuic";
60
+ import { onDestroy } from "svelte";
61
61
 
62
- const nr = new CronNextRun("0 9 * * 1-5");
63
- onDestroy(() => nr.destroy());
62
+ const nr = new CronNextRun("0 9 * * 1-5");
63
+ onDestroy(() => nr.destroy());
64
64
  </script>
65
65
 
66
66
  <p>Next run: {nr.nextRunFormatted}</p>
@@ -72,11 +72,11 @@ For places outside the CronInput that need the next-fire time (e.g. a dashboard
72
72
 
73
73
  The `mode` prop controls which surfaces are visible and whether the toggle button is shown:
74
74
 
75
- | `mode` | Toggle shown | Visible surfaces |
76
- | ------------------- | ------------ | ---------------------------------------------------- |
77
- | `"predefined"` | yes | preset picker |
78
- | `"manual"` | yes | fields + raw + description + next-run |
79
- | `undefined` | no | whatever the `show*` flags enable (full control) |
75
+ | `mode` | Toggle shown | Visible surfaces |
76
+ | -------------- | ------------ | ------------------------------------------------ |
77
+ | `"predefined"` | yes | preset picker |
78
+ | `"manual"` | yes | fields + raw + description + next-run |
79
+ | `undefined` | no | whatever the `show*` flags enable (full control) |
80
80
 
81
81
  When `mode` is defined, it **overrides** the individual `show*` flags. Set `mode={undefined}` if you want to pick surfaces yourself.
82
82
 
@@ -91,13 +91,13 @@ The `value` string is canonical. Internally:
91
91
 
92
92
  Off-by-one conventions match standard cron:
93
93
 
94
- | Field | Range | Notes |
95
- | ------------ | ------ | ------------------------------------ |
96
- | minute | 0-59 | |
97
- | hour | 0-23 | |
98
- | day of month | 1-31 | |
99
- | month | 1-12 | 1 = January |
100
- | day of week | 0-6 | 0 = Sunday |
94
+ | Field | Range | Notes |
95
+ | ------------ | ----- | ----------- |
96
+ | minute | 0-59 | |
97
+ | hour | 0-23 | |
98
+ | day of month | 1-31 | |
99
+ | month | 1-12 | 1 = January |
100
+ | day of week | 0-6 | 0 = Sunday |
101
101
 
102
102
  ## Validation
103
103
 
@@ -113,21 +113,21 @@ All next-run calculations use the **host's local timezone**. There is no `timezo
113
113
 
114
114
  ## Props
115
115
 
116
- | Prop | Type | Default | Description |
117
- | --------------------- | ------------------------------------- | --------------- | ---------------------------------------------------- |
118
- | `value` | `string` | `"* * * * *"` | Cron expression (bindable) |
119
- | `mode` | `"predefined" \| "manual" \| undefined` | `"predefined"` | Editor mode; `undefined` hides the toggle |
120
- | `presets` | `CronPreset[]` | `DEFAULT_PRESETS` | Preset options |
121
- | `showPresets` | `boolean` | `true` | (Ignored when `mode` is defined) |
122
- | `showFields` | `boolean` | `true` | (Ignored when `mode` is defined) |
123
- | `showRawInput` | `boolean` | `true` | (Ignored when `mode` is defined) |
124
- | `showDescription` | `boolean` | `true` | (Ignored when `mode` is defined) |
125
- | `showNextRun` | `boolean` | `true` | (Ignored when `mode` is defined) |
126
- | `onchange` | `(expr, valid) => void` | - | Fires on every valid or invalid change |
127
- | `unstyled` | `boolean` | `false` | Skip stuic base classes (user provides all styling) |
128
- | `class` | `string` | - | Wrapper class |
129
- | `el` | `HTMLElement` | - | Bindable wrapper element |
130
- | `classFields`/`classField`/`classFieldLabel`/`classFieldInput`/`classPreset`/`classRaw`/`classSummary`/`classToggleButton` | `string` | - | Per-element class overrides |
116
+ | Prop | Type | Default | Description |
117
+ | -------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | ----------------- | --------------------------------------------------- |
118
+ | `value` | `string` | `"* * * * *"` | Cron expression (bindable) |
119
+ | `mode` | `"predefined" \| "manual" \| undefined` | `"predefined"` | Editor mode; `undefined` hides the toggle |
120
+ | `presets` | `CronPreset[]` | `DEFAULT_PRESETS` | Preset options |
121
+ | `showPresets` | `boolean` | `true` | (Ignored when `mode` is defined) |
122
+ | `showFields` | `boolean` | `true` | (Ignored when `mode` is defined) |
123
+ | `showRawInput` | `boolean` | `true` | (Ignored when `mode` is defined) |
124
+ | `showDescription` | `boolean` | `true` | (Ignored when `mode` is defined) |
125
+ | `showNextRun` | `boolean` | `true` | (Ignored when `mode` is defined) |
126
+ | `onchange` | `(expr, valid) => void` | - | Fires on every valid or invalid change |
127
+ | `unstyled` | `boolean` | `false` | Skip stuic base classes (user provides all styling) |
128
+ | `class` | `string` | - | Wrapper class |
129
+ | `el` | `HTMLElement` | - | Bindable wrapper element |
130
+ | `classFields`/`classField`/`classFieldLabel`/`classFieldInput`/`classPreset`/`classRaw`/`classSummary`/`classToggleButton` | `string` | - | Per-element class overrides |
131
131
 
132
132
  Plus all standard `InputWrap` wrapper class props (`classLabel`, `classLabelBox`, `classInputBox`, `classInputBoxWrap`, `classInputBoxWrapInvalid`, `classDescBox`, `classBelowBox`) — same pattern as every other `Field*` component.
133
133
 
@@ -563,7 +563,11 @@
563
563
  }}
564
564
  >
565
565
  {#if selectable}
566
- <div class={!unstyled ? "stuic-checkbox stuic-data-table-card-checkbox" : undefined}>
566
+ <div
567
+ class={!unstyled
568
+ ? "stuic-checkbox stuic-data-table-card-checkbox"
569
+ : undefined}
570
+ >
567
571
  <input
568
572
  type="checkbox"
569
573
  checked={isSelected}
@@ -87,7 +87,13 @@ When your data is paged, DataTable can offer a "select all results" affordance t
87
87
  bind:excluded
88
88
  getRowId={(row) => row.id}
89
89
  >
90
- {#snippet batchActions({ selectedAll, excluded, effectiveCount, totalCount, clearSelection })}
90
+ {#snippet batchActions({
91
+ selectedAll,
92
+ excluded,
93
+ effectiveCount,
94
+ totalCount,
95
+ clearSelection,
96
+ })}
91
97
  <span>{effectiveCount} selected{selectedAll ? ` of ${totalCount}` : ""}</span>
92
98
  <Button onclick={() => deleteSelection({ selectedAll, excluded })}>Delete</Button>
93
99
  <Button variant="ghost" onclick={clearSelection}>Clear</Button>
@@ -112,7 +118,16 @@ New records inserted while all-pages mode is active are implicitly selected (the
112
118
  **Customising the banner:** the default banner uses the built-in `t()` keys `select_all_on_page_x`, `select_all_results`, `all_results_selected`, and `clear_selection`. Override markup entirely with the `selectAllBanner` snippet:
113
119
 
114
120
  ```svelte
115
- <DataTable {columns} {data} {paging} selectable allowSelectAllPages bind:selected bind:selectedAll bind:excluded>
121
+ <DataTable
122
+ {columns}
123
+ {data}
124
+ {paging}
125
+ selectable
126
+ allowSelectAllPages
127
+ bind:selected
128
+ bind:selectedAll
129
+ bind:excluded
130
+ >
116
131
  {#snippet selectAllBanner({ selectedAll, totalCount, selectAll, clearSelection })}
117
132
  <div class="my-banner">
118
133
  {#if selectedAll}
@@ -189,44 +204,44 @@ Replace the entire `<tr>` on desktop. When this snippet is provided, DataTable d
189
204
 
190
205
  ## Props
191
206
 
192
- | Prop | Type | Default | Description |
193
- | ------------------- | ------------------------------------------ | ------------- | ----------------------------------------------------------------- |
194
- | `columns` | `DataTableColumn<T>[]` | required | Column definitions |
195
- | `data` | `T[]` | required | Array of row data |
196
- | `getRowId` | `(row, index) => string \| number` | `(_, i) => i` | Row ID extractor |
197
- | `paging` | `PagingCalcResult` | - | Paging state (from `@marianmeres/paging-store`) |
198
- | `onPageChange` | `(offset: number) => void` | - | Called with the new offset when the user navigates pages |
199
- | `selectable` | `boolean` | `false` | Enable selection checkboxes |
200
- | `selected` | `Set<string \| number>` | `new Set()` | Selected row IDs (bindable) |
201
- | `selectOnRowClick` | `boolean` | `false` | Clicking anywhere on a row toggles its selection |
202
- | `selectDisabledBy` | `(row, index) => boolean` | - | Return `true` to disable selection for a specific row |
203
- | `allowSelectAllPages` | `boolean` | `false` | Show a banner offering "select all results" across paged data |
204
- | `selectedAll` | `boolean` | `false` | All-pages mode flag (bindable). In this mode `excluded` drives selection |
205
- | `excluded` | `Set<string \| number>` | `new Set()` | Deselected row IDs while in all-pages mode (bindable) |
206
- | `onRowClick` | `(row, index) => void` | - | Row click callback |
207
- | `loading` | `boolean` | `false` | Show loading overlay |
208
- | `small` | `boolean` | `false` | Force mobile/card layout regardless of viewport |
209
- | `t` | `TranslateFn` | built-in | Optional translation function |
210
- | `cell` | `Snippet` | - | Custom cell renderer (desktop + mobile) |
211
- | `row` | `Snippet` | - | Custom desktop `<tr>` renderer (overrides default row) |
212
- | `mobileRow` | `Snippet` | - | Custom mobile card renderer |
213
- | `batchActions` | `Snippet` | - | Batch action bar content |
214
- | `selectAllBanner` | `Snippet` | - | Override default "select all across pages" banner |
215
- | `empty` | `Snippet` | - | Custom empty state |
216
- | `unstyled` | `boolean` | `false` | Skip default styling |
217
- | `class` | `string` | - | Additional CSS classes |
218
- | `el` | `HTMLDivElement` | - | Bindable element ref |
207
+ | Prop | Type | Default | Description |
208
+ | --------------------- | ---------------------------------- | ------------- | ------------------------------------------------------------------------ |
209
+ | `columns` | `DataTableColumn<T>[]` | required | Column definitions |
210
+ | `data` | `T[]` | required | Array of row data |
211
+ | `getRowId` | `(row, index) => string \| number` | `(_, i) => i` | Row ID extractor |
212
+ | `paging` | `PagingCalcResult` | - | Paging state (from `@marianmeres/paging-store`) |
213
+ | `onPageChange` | `(offset: number) => void` | - | Called with the new offset when the user navigates pages |
214
+ | `selectable` | `boolean` | `false` | Enable selection checkboxes |
215
+ | `selected` | `Set<string \| number>` | `new Set()` | Selected row IDs (bindable) |
216
+ | `selectOnRowClick` | `boolean` | `false` | Clicking anywhere on a row toggles its selection |
217
+ | `selectDisabledBy` | `(row, index) => boolean` | - | Return `true` to disable selection for a specific row |
218
+ | `allowSelectAllPages` | `boolean` | `false` | Show a banner offering "select all results" across paged data |
219
+ | `selectedAll` | `boolean` | `false` | All-pages mode flag (bindable). In this mode `excluded` drives selection |
220
+ | `excluded` | `Set<string \| number>` | `new Set()` | Deselected row IDs while in all-pages mode (bindable) |
221
+ | `onRowClick` | `(row, index) => void` | - | Row click callback |
222
+ | `loading` | `boolean` | `false` | Show loading overlay |
223
+ | `small` | `boolean` | `false` | Force mobile/card layout regardless of viewport |
224
+ | `t` | `TranslateFn` | built-in | Optional translation function |
225
+ | `cell` | `Snippet` | - | Custom cell renderer (desktop + mobile) |
226
+ | `row` | `Snippet` | - | Custom desktop `<tr>` renderer (overrides default row) |
227
+ | `mobileRow` | `Snippet` | - | Custom mobile card renderer |
228
+ | `batchActions` | `Snippet` | - | Batch action bar content |
229
+ | `selectAllBanner` | `Snippet` | - | Override default "select all across pages" banner |
230
+ | `empty` | `Snippet` | - | Custom empty state |
231
+ | `unstyled` | `boolean` | `false` | Skip default styling |
232
+ | `class` | `string` | - | Additional CSS classes |
233
+ | `el` | `HTMLDivElement` | - | Bindable element ref |
219
234
 
220
235
  ### Snippet signatures
221
236
 
222
- | Snippet | Props |
223
- | ------------------ | -------------------------------------------------------------------------------------------------------------------- |
224
- | `cell` | `{ column, row, value, rowIndex, variant: "desktop" \| "mobile" }` |
225
- | `row` | `{ row, columns, rowIndex, isSelected }` — desktop only |
226
- | `mobileRow` | `{ row, columns, rowIndex }` — mobile only |
227
- | `batchActions` | `{ selected, selectedRows, selectedAll, excluded, effectiveCount, totalCount, clearSelection }` |
228
- | `selectAllBanner` | `{ selectedAll, effectiveCount, totalCount, pageCount, selectAll, clearSelection }` |
229
- | `empty` | — |
237
+ | Snippet | Props |
238
+ | ----------------- | ----------------------------------------------------------------------------------------------- |
239
+ | `cell` | `{ column, row, value, rowIndex, variant: "desktop" \| "mobile" }` |
240
+ | `row` | `{ row, columns, rowIndex, isSelected }` — desktop only |
241
+ | `mobileRow` | `{ row, columns, rowIndex }` — mobile only |
242
+ | `batchActions` | `{ selected, selectedRows, selectedAll, excluded, effectiveCount, totalCount, clearSelection }` |
243
+ | `selectAllBanner` | `{ selectedAll, effectiveCount, totalCount, pageCount, selectAll, clearSelection }` |
244
+ | `empty` | — |
230
245
 
231
246
  > **Note:** "Select all rows" affects only the rows currently in `data` (i.e. the current page when using external paging). Rows for which `selectDisabledBy` returns `true` are excluded from "select all". To select across pages, enable `allowSelectAllPages` and use the banner that appears.
232
247
  >
@@ -247,29 +262,29 @@ Replace the entire `<tr>` on desktop. When this snippet is provided, DataTable d
247
262
 
248
263
  ## CSS Variables
249
264
 
250
- | Variable | Default | Description |
251
- | --------------------------------------- | ------------------------------------- | ------------------------- |
252
- | `--stuic-data-table-radius` | `var(--radius-md)` | Border radius |
253
- | `--stuic-data-table-border-color` | `var(--stuic-color-border)` | Border color |
254
- | `--stuic-data-table-header-bg` | `var(--stuic-color-muted)` | Header background |
255
- | `--stuic-data-table-header-color` | `var(--stuic-color-muted-foreground)` | Header text |
256
- | `--stuic-data-table-header-font-size` | `0.875rem` | Header font size |
257
- | `--stuic-data-table-header-font-weight` | `var(--font-weight-semibold)` | Header font weight |
258
- | `--stuic-data-table-header-padding-x` | `0.75rem` | Header horizontal padding |
259
- | `--stuic-data-table-header-padding-y` | `0.5rem` | Header vertical padding |
260
- | `--stuic-data-table-row-bg` | `transparent` | Row background |
261
- | `--stuic-data-table-row-bg-hover` | `var(--stuic-color-muted)` | Row hover background |
262
- | `--stuic-data-table-row-bg-selected` | `color-mix(primary 10%)` | Selected row background |
263
- | `--stuic-data-table-row-border-color` | `var(--stuic-color-border)` | Row border color |
264
- | `--stuic-data-table-cell-padding-x` | `0.75rem` | Cell horizontal padding |
265
- | `--stuic-data-table-cell-padding-y` | `0.75rem` | Cell vertical padding |
266
- | `--stuic-data-table-cell-font-size` | `0.875rem` | Cell font size |
267
- | `--stuic-data-table-loading-opacity` | `0.5` | Loading state opacity |
268
- | `--stuic-data-table-card-bg` | `var(--stuic-color-background)` | Mobile card background |
269
- | `--stuic-data-table-card-border-color` | `var(--stuic-color-border)` | Mobile card border |
270
- | `--stuic-data-table-card-radius` | `var(--radius-md)` | Mobile card radius |
271
- | `--stuic-data-table-card-padding` | `0.75rem` | Mobile card padding |
272
- | `--stuic-data-table-card-gap` | `0.5rem` | Gap between mobile cards |
273
- | `--stuic-data-table-select-all-bg` | `color-mix(primary 10%)` | Select-all banner background |
274
- | `--stuic-data-table-select-all-padding-x` | `0.75rem` | Banner horizontal padding |
275
- | `--stuic-data-table-select-all-padding-y` | `0.5rem` | Banner vertical padding |
265
+ | Variable | Default | Description |
266
+ | ----------------------------------------- | ------------------------------------- | ---------------------------- |
267
+ | `--stuic-data-table-radius` | `var(--radius-md)` | Border radius |
268
+ | `--stuic-data-table-border-color` | `var(--stuic-color-border)` | Border color |
269
+ | `--stuic-data-table-header-bg` | `var(--stuic-color-muted)` | Header background |
270
+ | `--stuic-data-table-header-color` | `var(--stuic-color-muted-foreground)` | Header text |
271
+ | `--stuic-data-table-header-font-size` | `0.875rem` | Header font size |
272
+ | `--stuic-data-table-header-font-weight` | `var(--font-weight-semibold)` | Header font weight |
273
+ | `--stuic-data-table-header-padding-x` | `0.75rem` | Header horizontal padding |
274
+ | `--stuic-data-table-header-padding-y` | `0.5rem` | Header vertical padding |
275
+ | `--stuic-data-table-row-bg` | `transparent` | Row background |
276
+ | `--stuic-data-table-row-bg-hover` | `var(--stuic-color-muted)` | Row hover background |
277
+ | `--stuic-data-table-row-bg-selected` | `color-mix(primary 10%)` | Selected row background |
278
+ | `--stuic-data-table-row-border-color` | `var(--stuic-color-border)` | Row border color |
279
+ | `--stuic-data-table-cell-padding-x` | `0.75rem` | Cell horizontal padding |
280
+ | `--stuic-data-table-cell-padding-y` | `0.75rem` | Cell vertical padding |
281
+ | `--stuic-data-table-cell-font-size` | `0.875rem` | Cell font size |
282
+ | `--stuic-data-table-loading-opacity` | `0.5` | Loading state opacity |
283
+ | `--stuic-data-table-card-bg` | `var(--stuic-color-background)` | Mobile card background |
284
+ | `--stuic-data-table-card-border-color` | `var(--stuic-color-border)` | Mobile card border |
285
+ | `--stuic-data-table-card-radius` | `var(--radius-md)` | Mobile card radius |
286
+ | `--stuic-data-table-card-padding` | `0.75rem` | Mobile card padding |
287
+ | `--stuic-data-table-card-gap` | `0.5rem` | Gap between mobile cards |
288
+ | `--stuic-data-table-select-all-bg` | `color-mix(primary 10%)` | Select-all banner background |
289
+ | `--stuic-data-table-select-all-padding-x` | `0.75rem` | Banner horizontal padding |
290
+ | `--stuic-data-table-select-all-padding-y` | `0.5rem` | Banner vertical padding |
@@ -835,7 +835,9 @@
835
835
  <button
836
836
  bind:this={triggerEl}
837
837
  id={triggerId}
838
- class={unstyled ? classTrigger : twMerge(DROPDOWN_MENU_TRIGGER_CLASSES, classTrigger)}
838
+ class={unstyled
839
+ ? classTrigger
840
+ : twMerge(DROPDOWN_MENU_TRIGGER_CLASSES, classTrigger)}
839
841
  onclick={() => (isOpen = !isOpen)}
840
842
  aria-haspopup="menu"
841
843
  aria-expanded={isOpen}
@@ -858,7 +860,9 @@
858
860
  {#if isOpen && !isSupported && showBackdrop}
859
861
  <!-- svelte-ignore a11y_click_events_have_key_events -->
860
862
  <div
861
- class={unstyled ? classBackdrop : twMerge(DROPDOWN_MENU_BACKDROP_CLASSES, classBackdrop)}
863
+ class={unstyled
864
+ ? classBackdrop
865
+ : twMerge(DROPDOWN_MENU_BACKDROP_CLASSES, classBackdrop)}
862
866
  onclick={() => {
863
867
  if (closeOnClickOutside) {
864
868
  isOpen = false;