@marianmeres/stuic 3.166.0 → 3.168.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 (105) hide show
  1. package/AGENTS.md +6 -6
  2. package/API.md +14 -13
  3. package/dist/attachments/index.d.ts +1 -0
  4. package/dist/attachments/index.js +1 -0
  5. package/dist/attachments/long-press.d.ts +43 -0
  6. package/dist/attachments/long-press.js +107 -0
  7. package/dist/components/Breadcrumbs/Breadcrumbs.svelte +198 -0
  8. package/dist/components/Breadcrumbs/Breadcrumbs.svelte.d.ts +60 -0
  9. package/dist/components/Breadcrumbs/README.md +174 -0
  10. package/dist/components/Breadcrumbs/i18n-sk.d.ts +17 -0
  11. package/dist/components/Breadcrumbs/i18n-sk.js +19 -0
  12. package/dist/components/Breadcrumbs/i18n.d.ts +31 -0
  13. package/dist/components/Breadcrumbs/i18n.js +40 -0
  14. package/dist/components/Breadcrumbs/index.css +84 -0
  15. package/dist/components/Breadcrumbs/index.d.ts +4 -0
  16. package/dist/components/Breadcrumbs/index.js +4 -0
  17. package/dist/components/Breadcrumbs/json-ld.d.ts +58 -0
  18. package/dist/components/Breadcrumbs/json-ld.js +56 -0
  19. package/dist/components/Collapsible/Collapsible.svelte +84 -15
  20. package/dist/components/Collapsible/Collapsible.svelte.d.ts +29 -2
  21. package/dist/components/Collapsible/README.md +82 -23
  22. package/dist/components/Collapsible/index.css +38 -0
  23. package/dist/components/Collapsible/index.d.ts +1 -1
  24. package/dist/components/ContextMenu/ContextMenu.svelte +302 -0
  25. package/dist/components/ContextMenu/ContextMenu.svelte.d.ts +94 -0
  26. package/dist/components/ContextMenu/README.md +159 -0
  27. package/dist/components/ContextMenu/i18n-sk.d.ts +17 -0
  28. package/dist/components/ContextMenu/i18n-sk.js +18 -0
  29. package/dist/components/ContextMenu/i18n.d.ts +30 -0
  30. package/dist/components/ContextMenu/i18n.js +38 -0
  31. package/dist/components/ContextMenu/index.css +26 -0
  32. package/dist/components/ContextMenu/index.d.ts +3 -0
  33. package/dist/components/ContextMenu/index.js +3 -0
  34. package/dist/components/CopyButton/CopyButton.svelte +278 -0
  35. package/dist/components/CopyButton/CopyButton.svelte.d.ts +76 -0
  36. package/dist/components/CopyButton/README.md +241 -0
  37. package/dist/components/CopyButton/i18n-sk.d.ts +17 -0
  38. package/dist/components/CopyButton/i18n-sk.js +20 -0
  39. package/dist/components/CopyButton/i18n.d.ts +35 -0
  40. package/dist/components/CopyButton/i18n.js +43 -0
  41. package/dist/components/CopyButton/index.css +67 -0
  42. package/dist/components/CopyButton/index.d.ts +3 -0
  43. package/dist/components/CopyButton/index.js +3 -0
  44. package/dist/components/DataTable/DataTable.svelte +58 -36
  45. package/dist/components/DataTable/DataTable.svelte.d.ts +12 -0
  46. package/dist/components/DataTable/README.md +108 -33
  47. package/dist/components/DataTable/i18n-sk.js +1 -0
  48. package/dist/components/DataTable/i18n.d.ts +1 -0
  49. package/dist/components/DataTable/i18n.js +3 -0
  50. package/dist/components/DataTable/index.css +41 -3
  51. package/dist/components/EmptyState/EmptyState.svelte +121 -0
  52. package/dist/components/EmptyState/EmptyState.svelte.d.ts +40 -0
  53. package/dist/components/EmptyState/README.md +188 -0
  54. package/dist/components/EmptyState/index.css +154 -0
  55. package/dist/components/EmptyState/index.d.ts +1 -0
  56. package/dist/components/EmptyState/index.js +1 -0
  57. package/dist/components/Pagination/Pagination.svelte +250 -0
  58. package/dist/components/Pagination/Pagination.svelte.d.ts +69 -0
  59. package/dist/components/Pagination/README.md +152 -0
  60. package/dist/components/Pagination/i18n-sk.d.ts +17 -0
  61. package/dist/components/Pagination/i18n-sk.js +24 -0
  62. package/dist/components/Pagination/i18n.d.ts +41 -0
  63. package/dist/components/Pagination/i18n.js +49 -0
  64. package/dist/components/Pagination/index.css +50 -0
  65. package/dist/components/Pagination/index.d.ts +4 -0
  66. package/dist/components/Pagination/index.js +4 -0
  67. package/dist/components/Pagination/pagination-range.d.ts +21 -0
  68. package/dist/components/Pagination/pagination-range.js +41 -0
  69. package/dist/components/Stat/README.md +157 -0
  70. package/dist/components/Stat/Stat.svelte +226 -0
  71. package/dist/components/Stat/Stat.svelte.d.ts +54 -0
  72. package/dist/components/Stat/index.css +217 -0
  73. package/dist/components/Stat/index.d.ts +1 -0
  74. package/dist/components/Stat/index.js +1 -0
  75. package/dist/components/Stepper/README.md +181 -0
  76. package/dist/components/Stepper/Stepper.svelte +240 -0
  77. package/dist/components/Stepper/Stepper.svelte.d.ts +86 -0
  78. package/dist/components/Stepper/i18n-sk.d.ts +17 -0
  79. package/dist/components/Stepper/i18n-sk.js +21 -0
  80. package/dist/components/Stepper/i18n.d.ts +35 -0
  81. package/dist/components/Stepper/i18n.js +43 -0
  82. package/dist/components/Stepper/index.css +302 -0
  83. package/dist/components/Stepper/index.d.ts +3 -0
  84. package/dist/components/Stepper/index.js +3 -0
  85. package/dist/components/TrendChart/README.md +123 -0
  86. package/dist/components/TrendChart/TrendChart.svelte +114 -0
  87. package/dist/components/TrendChart/TrendChart.svelte.d.ts +36 -0
  88. package/dist/components/TrendChart/index.css +51 -0
  89. package/dist/components/TrendChart/index.d.ts +1 -0
  90. package/dist/components/TrendChart/index.js +1 -0
  91. package/dist/icons/index.d.ts +4 -0
  92. package/dist/icons/index.js +4 -0
  93. package/dist/index.css +7 -0
  94. package/dist/index.d.ts +7 -0
  95. package/dist/index.js +12 -0
  96. package/dist/utils/copy-to-clipboard.d.ts +43 -0
  97. package/dist/utils/copy-to-clipboard.js +102 -0
  98. package/dist/utils/index.d.ts +1 -0
  99. package/dist/utils/index.js +1 -0
  100. package/docs/architecture.md +1 -1
  101. package/docs/domains/attachments.md +41 -4
  102. package/docs/domains/components.md +280 -13
  103. package/docs/domains/utils.md +8 -6
  104. package/docs/maybe-todo.md +111 -0
  105. package/package.json +15 -5
@@ -0,0 +1,17 @@
1
+ import type { CopyButtonMessages } from "./i18n.js";
2
+ /**
3
+ * Slovak message catalog for `CopyButton`. Opt-in — English stays the built-in
4
+ * default, and this module is only pulled into a bundle when it is actually imported
5
+ * (the component itself never references it).
6
+ *
7
+ * @example
8
+ * ```svelte
9
+ * <script>
10
+ * import { CopyButton, createCopyButtonT, COPY_BUTTON_MESSAGES_SK } from "@marianmeres/stuic";
11
+ * const t = createCopyButtonT(COPY_BUTTON_MESSAGES_SK);
12
+ * </script>
13
+ *
14
+ * <CopyButton text={url} {t} />
15
+ * ```
16
+ */
17
+ export declare const COPY_BUTTON_MESSAGES_SK: CopyButtonMessages;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Slovak message catalog for `CopyButton`. Opt-in — English stays the built-in
3
+ * default, and this module is only pulled into a bundle when it is actually imported
4
+ * (the component itself never references it).
5
+ *
6
+ * @example
7
+ * ```svelte
8
+ * <script>
9
+ * import { CopyButton, createCopyButtonT, COPY_BUTTON_MESSAGES_SK } from "@marianmeres/stuic";
10
+ * const t = createCopyButtonT(COPY_BUTTON_MESSAGES_SK);
11
+ * </script>
12
+ *
13
+ * <CopyButton text={url} {t} />
14
+ * ```
15
+ */
16
+ export const COPY_BUTTON_MESSAGES_SK = {
17
+ copy: "Kopírovať",
18
+ copied: "Skopírované",
19
+ copy_failed: "Kopírovanie zlyhalo",
20
+ };
@@ -0,0 +1,35 @@
1
+ import type { TranslateFn } from "../../types.js";
2
+ /**
3
+ * The built-in (English) message catalog of `CopyButton`. Also the fallback of every
4
+ * other bundled locale, so a locale missing a key still renders text.
5
+ */
6
+ export declare const COPY_BUTTON_MESSAGES_EN: {
7
+ /** Idle: accessible name of the icon-only button, and the default `label` */
8
+ copy: string;
9
+ /** After a successful copy: label, accessible name, and the live announcement */
10
+ copied: string;
11
+ /** After a failed copy: label, accessible name, and the live announcement */
12
+ copy_failed: string;
13
+ };
14
+ /** Every message key `CopyButton` may look up. */
15
+ export type CopyButtonMessageKey = keyof typeof COPY_BUTTON_MESSAGES_EN;
16
+ /** A (possibly partial) catalog for one locale. */
17
+ export type CopyButtonMessages = Record<CopyButtonMessageKey, string>;
18
+ /**
19
+ * Builds the `t` prop of `CopyButton` from a message catalog. Unknown or untranslated
20
+ * keys fall back to `fallbackMessages` (English by default), so a catalog may safely be
21
+ * partial and never renders a raw key.
22
+ *
23
+ * @example
24
+ * ```svelte
25
+ * <script>
26
+ * import { CopyButton, createCopyButtonT, COPY_BUTTON_MESSAGES_SK } from "@marianmeres/stuic";
27
+ * const t = createCopyButtonT(COPY_BUTTON_MESSAGES_SK);
28
+ * </script>
29
+ *
30
+ * <CopyButton text={url} {t} />
31
+ * ```
32
+ */
33
+ export declare function createCopyButtonT(messages: Partial<CopyButtonMessages> | Record<string, string>, fallbackMessages?: Partial<CopyButtonMessages> | Record<string, string>): TranslateFn;
34
+ /** The component's built-in English `t`. */
35
+ export declare const t_default: TranslateFn;
@@ -0,0 +1,43 @@
1
+ import { isPlainObject } from "../../utils/is-plain-object.js";
2
+ import { replaceMap } from "../../utils/replace-map.js";
3
+ /**
4
+ * The built-in (English) message catalog of `CopyButton`. Also the fallback of every
5
+ * other bundled locale, so a locale missing a key still renders text.
6
+ */
7
+ export const COPY_BUTTON_MESSAGES_EN = {
8
+ /** Idle: accessible name of the icon-only button, and the default `label` */
9
+ copy: "Copy",
10
+ /** After a successful copy: label, accessible name, and the live announcement */
11
+ copied: "Copied",
12
+ /** After a failed copy: label, accessible name, and the live announcement */
13
+ copy_failed: "Copy failed",
14
+ };
15
+ /**
16
+ * Builds the `t` prop of `CopyButton` from a message catalog. Unknown or untranslated
17
+ * keys fall back to `fallbackMessages` (English by default), so a catalog may safely be
18
+ * partial and never renders a raw key.
19
+ *
20
+ * @example
21
+ * ```svelte
22
+ * <script>
23
+ * import { CopyButton, createCopyButtonT, COPY_BUTTON_MESSAGES_SK } from "@marianmeres/stuic";
24
+ * const t = createCopyButtonT(COPY_BUTTON_MESSAGES_SK);
25
+ * </script>
26
+ *
27
+ * <CopyButton text={url} {t} />
28
+ * ```
29
+ */
30
+ export function createCopyButtonT(messages, fallbackMessages = COPY_BUTTON_MESSAGES_EN) {
31
+ return (k, values = null, fallback = "") => {
32
+ const out = messages[k] ??
33
+ fallbackMessages[k] ??
34
+ (typeof fallback === "string" ? fallback : k);
35
+ return isPlainObject(values)
36
+ ? replaceMap(out, values, {
37
+ preSearchKeyTransform: (k) => `{${k}}`,
38
+ })
39
+ : out;
40
+ };
41
+ }
42
+ /** The component's built-in English `t`. */
43
+ export const t_default = createCopyButtonT(COPY_BUTTON_MESSAGES_EN);
@@ -0,0 +1,67 @@
1
+ /* ============================================================================
2
+ COPY BUTTON COMPONENT TOKENS
3
+ The button itself IS a Button — colors, sizes, radius and focus ring theme via
4
+ the --stuic-button-* tokens. These tokens only cover the icon and the feedback
5
+ "pop" when the state flips to copied/error.
6
+ Override globally: :root { --stuic-copy-button-icon-size: 1em; }
7
+ Override locally: <CopyButton style="--stuic-copy-button-icon-size: 1em;">
8
+ ============================================================================ */
9
+
10
+ /* prettier-ignore */
11
+ :root {
12
+ /* Icon (any svg inside the icon wrapper is normalized to this) */
13
+ --stuic-copy-button-icon-size: 1.25em;
14
+
15
+ /* Feedback pop: the icon scales up from this factor when the state changes */
16
+ --stuic-copy-button-icon-pop-duration: 200ms;
17
+ --stuic-copy-button-icon-pop-scale: 0.6;
18
+ }
19
+
20
+ @layer components {
21
+ /* ============================================================================
22
+ ICON
23
+ ============================================================================ */
24
+
25
+ .stuic-copy-button-icon {
26
+ display: inline-flex;
27
+ align-items: center;
28
+ justify-content: center;
29
+ flex: none;
30
+ }
31
+
32
+ .stuic-copy-button-icon svg {
33
+ width: var(--stuic-copy-button-icon-size);
34
+ height: var(--stuic-copy-button-icon-size);
35
+ }
36
+
37
+ /* ============================================================================
38
+ FEEDBACK POP
39
+ Runs once when [data-state] flips to copied/error (the selector starts
40
+ matching). Re-clicking within the feedback window keeps the state and so
41
+ does not replay it.
42
+ ============================================================================ */
43
+
44
+ .stuic-copy-button[data-state="copied"] .stuic-copy-button-icon,
45
+ .stuic-copy-button[data-state="error"] .stuic-copy-button-icon {
46
+ animation: stuic-copy-button-icon-pop var(--stuic-copy-button-icon-pop-duration)
47
+ ease-out;
48
+ }
49
+
50
+ @media (prefers-reduced-motion: reduce) {
51
+ .stuic-copy-button[data-state="copied"] .stuic-copy-button-icon,
52
+ .stuic-copy-button[data-state="error"] .stuic-copy-button-icon {
53
+ animation: none;
54
+ }
55
+ }
56
+ }
57
+
58
+ @keyframes stuic-copy-button-icon-pop {
59
+ from {
60
+ transform: scale(var(--stuic-copy-button-icon-pop-scale));
61
+ opacity: 0.4;
62
+ }
63
+ to {
64
+ transform: scale(1);
65
+ opacity: 1;
66
+ }
67
+ }
@@ -0,0 +1,3 @@
1
+ export { default as CopyButton, type Props as CopyButtonProps, type CopyButtonState, type CopyButtonText, } from "./CopyButton.svelte";
2
+ export { createCopyButtonT, COPY_BUTTON_MESSAGES_EN, type CopyButtonMessageKey, type CopyButtonMessages, } from "./i18n.js";
3
+ export { COPY_BUTTON_MESSAGES_SK } from "./i18n-sk.js";
@@ -0,0 +1,3 @@
1
+ export { default as CopyButton, } from "./CopyButton.svelte";
2
+ export { createCopyButtonT, COPY_BUTTON_MESSAGES_EN, } from "./i18n.js";
3
+ export { COPY_BUTTON_MESSAGES_SK } from "./i18n-sk.js";
@@ -41,6 +41,18 @@
41
41
  paging?: PagingCalcResult;
42
42
  /** Callback when the user navigates to a different page (receives new offset) */
43
43
  onPageChange?: (offset: number) => void;
44
+ /**
45
+ * Render the built-in pager. Set `false` to keep feeding `paging` -- which the
46
+ * select-all-across-pages banner, `effectiveCount` and `totalCount` need -- while
47
+ * paging the data from your own control elsewhere on the page.
48
+ *
49
+ * With the pager hidden `onPageChange` never fires (nothing else calls it), so the
50
+ * caller's own control is the single thing driving the offset -- don't wire both.
51
+ *
52
+ * Orthogonal to the existing "one page of results needs no pager" rule: a single-page
53
+ * `paging` renders no pager either way.
54
+ */
55
+ showPager?: boolean;
44
56
 
45
57
  /** Enable row selection checkboxes */
46
58
  selectable?: boolean;
@@ -223,6 +235,8 @@
223
235
  import { Breakpoint } from "../../utils/breakpoint.svelte.js";
224
236
  import Spinner from "../Spinner/Spinner.svelte";
225
237
  import Button from "../Button/Button.svelte";
238
+ import EmptyState from "../EmptyState/EmptyState.svelte";
239
+ import Pagination from "../Pagination/Pagination.svelte";
226
240
  import Thc, { isTHCNotEmpty, getTHCStringContent } from "../Thc/Thc.svelte";
227
241
 
228
242
  let {
@@ -231,6 +245,7 @@
231
245
  getRowId = (_row: T, index: number) => index,
232
246
  paging,
233
247
  onPageChange,
248
+ showPager = true,
234
249
  selectable = false,
235
250
  selected = $bindable(new Set()),
236
251
  selectOnRowClick = false,
@@ -265,7 +280,9 @@
265
280
  let isDesktop = $derived(small ? false : bp.md);
266
281
 
267
282
  // --- Paging ---
268
- let showPaging = $derived(paging != null && paging.pageCount > 1);
283
+ // The prop only ADDs to the existing rule -- a single-page result still renders no
284
+ // pager, regardless of `showPager`.
285
+ let renderPager = $derived(showPager && paging != null && paging.pageCount > 1);
269
286
 
270
287
  // --- Selection ---
271
288
  let allRowIds = $derived(data.map((row, i) => getRowId(row, i)));
@@ -502,6 +519,25 @@
502
519
  {/if}
503
520
  {/snippet}
504
521
 
522
+ <!--
523
+ The empty state, shared by both layouts. The built-in one is an `EmptyState`; its
524
+ typography is pulled back to table chrome (muted, cell-sized, no headline weight) by
525
+ the `--stuic-empty-state-*` overrides in index.css, so it reads exactly as the bare
526
+ `t("no_data")` string it replaces. An `empty` snippet still replaces it whole.
527
+ -->
528
+ {#snippet emptyBody()}
529
+ {#if empty}
530
+ {@render empty()}
531
+ {:else}
532
+ <EmptyState
533
+ title={t("no_data")}
534
+ size="sm"
535
+ {unstyled}
536
+ class={!unstyled ? "stuic-data-table-empty-state" : undefined}
537
+ />
538
+ {/if}
539
+ {/snippet}
540
+
505
541
  <!--
506
542
  Selection bar -- one strip carrying the selection status, the consumer's
507
543
  `batchActions`, and the select-all-across-pages offer. Deliberately a single element
@@ -653,11 +689,7 @@
653
689
  colspan={columns.length + (selectable ? 1 : 0)}
654
690
  class={!unstyled ? "stuic-data-table-empty" : undefined}
655
691
  >
656
- {#if empty}
657
- {@render empty()}
658
- {:else}
659
- {t("no_data")}
660
- {/if}
692
+ {@render emptyBody()}
661
693
  </td>
662
694
  </tr>
663
695
  {/each}
@@ -734,11 +766,7 @@
734
766
  {/if}
735
767
  {:else}
736
768
  <div class={!unstyled ? "stuic-data-table-empty" : undefined}>
737
- {#if empty}
738
- {@render empty()}
739
- {:else}
740
- {t("no_data")}
741
- {/if}
769
+ {@render emptyBody()}
742
770
  </div>
743
771
  {/each}
744
772
  </div>
@@ -751,30 +779,24 @@
751
779
  </div>
752
780
  {/if}
753
781
 
754
- <!-- Paging -->
755
- {#if showPaging && paging}
756
- <div class={!unstyled ? "stuic-data-table-paging" : undefined}>
757
- <Button
758
- variant="ghost"
759
- size="sm"
760
- disabled={!paging.hasPrevious}
761
- onclick={() => onPageChange?.(paging!.previousOffset)}
762
- aria-label={t("previous_page")}
763
- >
764
- &lsaquo; {t("previous_page")}
765
- </Button>
766
- <span class={!unstyled ? "stuic-data-table-paging-info" : undefined}>
767
- {t("page_x_of_y", { page: paging.currentPage, pageCount: paging.pageCount })}
768
- </span>
769
- <Button
770
- variant="ghost"
771
- size="sm"
772
- disabled={!paging.hasNext}
773
- onclick={() => onPageChange?.(paging!.nextOffset)}
774
- aria-label={t("next_page")}
775
- >
776
- {t("next_page")} &rsaquo;
777
- </Button>
778
- </div>
782
+ <!--
783
+ Paging -- the built-in pager IS a `Pagination` in its default "compact" variant,
784
+ which is exactly the prev / "Page X of Y" / next control this used to inline.
785
+ `.stuic-data-table-paging` moves onto its `<nav>` rather than wrapping it, so the
786
+ region keeps its class (and its `--stuic-data-table-paging-*` tokens, re-pointed
787
+ at Pagination's in index.css) without an extra node.
788
+
789
+ Note `Pagination`'s own single-page rule is left at its default: `renderPager`
790
+ already encodes it, plus `showPager`.
791
+ -->
792
+ {#if renderPager && paging}
793
+ <Pagination
794
+ {paging}
795
+ {onPageChange}
796
+ {t}
797
+ {unstyled}
798
+ class={!unstyled ? "stuic-data-table-paging" : undefined}
799
+ classInfo={!unstyled ? "stuic-data-table-paging-info" : undefined}
800
+ />
779
801
  {/if}
780
802
  </div>
@@ -32,6 +32,18 @@ export interface Props<T = Record<string, any>> extends Omit<HTMLAttributes<HTML
32
32
  paging?: PagingCalcResult;
33
33
  /** Callback when the user navigates to a different page (receives new offset) */
34
34
  onPageChange?: (offset: number) => void;
35
+ /**
36
+ * Render the built-in pager. Set `false` to keep feeding `paging` -- which the
37
+ * select-all-across-pages banner, `effectiveCount` and `totalCount` need -- while
38
+ * paging the data from your own control elsewhere on the page.
39
+ *
40
+ * With the pager hidden `onPageChange` never fires (nothing else calls it), so the
41
+ * caller's own control is the single thing driving the offset -- don't wire both.
42
+ *
43
+ * Orthogonal to the existing "one page of results needs no pager" rule: a single-page
44
+ * `paging` renders no pager either way.
45
+ */
46
+ showPager?: boolean;
35
47
  /** Enable row selection checkboxes */
36
48
  selectable?: boolean;
37
49
  /** Set of selected row IDs (bindable) */
@@ -46,6 +46,79 @@ DataTable integrates with [`@marianmeres/paging-store`](https://github.com/maria
46
46
  />
47
47
  ```
48
48
 
49
+ A single-page result renders no pager -- there is nothing to page through.
50
+
51
+ #### Bringing your own pager
52
+
53
+ `paging` does two unrelated jobs: it renders the built-in pager, and it feeds the
54
+ select-all-across-pages banner, `effectiveCount` and `totalCount`. `showPager={false}`
55
+ asks for the second without the first, for a page that already pages the data from its own
56
+ control (one with a rows-per-page selector, say):
57
+
58
+ ```svelte
59
+ <DataTable
60
+ {columns}
61
+ {data}
62
+ paging={pagingResult}
63
+ showPager={false}
64
+ selectable
65
+ allowSelectAllPages
66
+ />
67
+
68
+ <MyOwnPager … />
69
+ ```
70
+
71
+ The pager node is then absent from the DOM, not hidden -- it takes no space and holds no
72
+ focusable buttons. `onPageChange` never fires while it is hidden (nothing else calls it),
73
+ so your own control is the single thing driving the offset; don't wire both.
74
+
75
+ (The node's class stays `.stuic-data-table-paging` and its `--stuic-data-table-paging-*`
76
+ tokens are unchanged -- `showPager` names the control, the CSS names the region.)
77
+
78
+ ### What the built-in pager and empty state are made of
79
+
80
+ Neither is bespoke markup any more:
81
+
82
+ - the pager is a [`Pagination`](../Pagination/README.md) in its default `"compact"`
83
+ variant, rendered with `.stuic-data-table-paging` on its own `<nav>`;
84
+ - the default empty state (no `empty` snippet) is an
85
+ [`EmptyState`](../EmptyState/README.md) at `size="sm"`, carrying the marker class
86
+ `.stuic-data-table-empty-state` inside the usual `.stuic-data-table-empty` cell.
87
+
88
+ The table's `--stuic-data-table-*` tokens are re-pointed at the inner components' own
89
+ tokens, so an existing override still drives them, and the `t` you pass `DataTable` is
90
+ the one the pager uses -- the shared `previous_page`, `next_page` and `page_x_of_y`
91
+ keys are identical in both catalogs, so nothing to wire.
92
+
93
+ Two things did change:
94
+
95
+ - the pager is a `<nav>` landmark named by the new `pagination` key, where it used to
96
+ be a plain `<div>`. Same buttons, same labels, same `onPageChange` offsets;
97
+ - the built-in empty state now gets the `2rem` of padding `.stuic-data-table-empty` has
98
+ always declared. On desktop it never actually got it (the more specific
99
+ `.stuic-data-table td` padding rule won), so that cell is taller than it was, and now
100
+ matches the mobile card layout, which already had it. Typography is unchanged.
101
+
102
+ An `empty` snippet is untouched by any of this -- it replaces the built-in `EmptyState`
103
+ outright and keeps the container padding it has always had. Rendering your own
104
+ `EmptyState` in it is a perfectly good way to get an icon, a description and a CTA:
105
+
106
+ ```svelte
107
+ <DataTable {columns} {data}>
108
+ {#snippet empty()}
109
+ <EmptyState
110
+ icon={{ html: iconSearch({ size: 48 }) }}
111
+ title="No matches"
112
+ description="Try widening the filter."
113
+ >
114
+ {#snippet actions()}
115
+ <Button onclick={resetFilters}>Reset filters</Button>
116
+ {/snippet}
117
+ </EmptyState>
118
+ {/snippet}
119
+ </DataTable>
120
+ ```
121
+
49
122
  ### With Selection
50
123
 
51
124
  ```svelte
@@ -311,39 +384,40 @@ Replace the entire `<tr>` on desktop. When this snippet is provided, DataTable d
311
384
 
312
385
  ## Props
313
386
 
314
- | Prop | Type | Default | Description |
315
- | --------------------- | ------------------------------------- | ------------- | -------------------------------------------------------------------------- |
316
- | `columns` | `DataTableColumn<T>[]` | required | Column definitions |
317
- | `data` | `T[]` | required | Array of row data |
318
- | `getRowId` | `(row, index) => string \| number` | `(_, i) => i` | Row ID extractor |
319
- | `paging` | `PagingCalcResult` | - | Paging state (from `@marianmeres/paging-store`) |
320
- | `onPageChange` | `(offset: number) => void` | - | Called with the new offset when the user navigates pages |
321
- | `selectable` | `boolean` | `false` | Enable selection checkboxes |
322
- | `selected` | `Set<string \| number>` | `new Set()` | Selected row IDs (bindable) |
323
- | `selectOnRowClick` | `boolean` | `false` | Clicking anywhere on a row toggles its selection |
324
- | `selectDisabledBy` | `(row, index) => boolean` | - | Return `true` to disable selection for a specific row |
325
- | `allowSelectAllPages` | `boolean` | `false` | Show a banner offering "select all results" across paged data |
326
- | `selectedAll` | `boolean` | `false` | All-pages mode flag (bindable). In this mode `excluded` drives selection |
327
- | `excluded` | `Set<string \| number>` | `new Set()` | Deselected row IDs while in all-pages mode (bindable) |
328
- | `reserveBatchBar` | `boolean` | `true` | Keep the selection bar in the layout while nothing is selected |
329
- | `onRowClick` | `(row, index) => void` | - | Row click callback |
330
- | `rowHref` | `(row, index) => string \| undefined` | - | Wrap the lead cell's content in an `<a href>` (keyboard/⌘-click reachable) |
331
- | `rowHrefColumn` | `string` | first column | Which column is the lead cell for `rowHref` |
332
- | `classRowLink` | `string` | - | Extra classes for the `rowHref` anchor |
333
- | `rowActivatable` | `boolean` | `false` | Make the desktop `<tr>` focusable + `Enter`-activatable (no `role`) |
334
- | `rowLabel` | `(row, index) => string \| undefined` | - | Accessible name for an activatable row |
335
- | `loading` | `boolean` | `false` | Show loading overlay |
336
- | `small` | `boolean` | `false` | Force mobile/card layout regardless of viewport |
337
- | `t` | `TranslateFn` | built-in | Optional translation function |
338
- | `cell` | `Snippet` | - | Custom cell renderer (desktop + mobile) |
339
- | `row` | `Snippet` | - | Custom desktop `<tr>` renderer (overrides default row) |
340
- | `mobileRow` | `Snippet` | - | Custom mobile card renderer |
341
- | `batchActions` | `Snippet` | - | Selection bar content while something is selected |
342
- | `selectAllBanner` | `Snippet` | - | Override default "select all across pages" offer |
343
- | `empty` | `Snippet` | - | Custom empty state |
344
- | `unstyled` | `boolean` | `false` | Skip default styling |
345
- | `class` | `string` | - | Additional CSS classes |
346
- | `el` | `HTMLDivElement` | - | Bindable element ref |
387
+ | Prop | Type | Default | Description |
388
+ | --------------------- | ------------------------------------- | ------------- | ------------------------------------------------------------------------------------ |
389
+ | `columns` | `DataTableColumn<T>[]` | required | Column definitions |
390
+ | `data` | `T[]` | required | Array of row data |
391
+ | `getRowId` | `(row, index) => string \| number` | `(_, i) => i` | Row ID extractor |
392
+ | `paging` | `PagingCalcResult` | - | Paging state (from `@marianmeres/paging-store`) |
393
+ | `onPageChange` | `(offset: number) => void` | - | Called with the new offset when the user navigates pages |
394
+ | `showPager` | `boolean` | `true` | Render the built-in pager. `false` keeps `paging` feeding the select-all banner only |
395
+ | `selectable` | `boolean` | `false` | Enable selection checkboxes |
396
+ | `selected` | `Set<string \| number>` | `new Set()` | Selected row IDs (bindable) |
397
+ | `selectOnRowClick` | `boolean` | `false` | Clicking anywhere on a row toggles its selection |
398
+ | `selectDisabledBy` | `(row, index) => boolean` | - | Return `true` to disable selection for a specific row |
399
+ | `allowSelectAllPages` | `boolean` | `false` | Show a banner offering "select all results" across paged data |
400
+ | `selectedAll` | `boolean` | `false` | All-pages mode flag (bindable). In this mode `excluded` drives selection |
401
+ | `excluded` | `Set<string \| number>` | `new Set()` | Deselected row IDs while in all-pages mode (bindable) |
402
+ | `reserveBatchBar` | `boolean` | `true` | Keep the selection bar in the layout while nothing is selected |
403
+ | `onRowClick` | `(row, index) => void` | - | Row click callback |
404
+ | `rowHref` | `(row, index) => string \| undefined` | - | Wrap the lead cell's content in an `<a href>` (keyboard/⌘-click reachable) |
405
+ | `rowHrefColumn` | `string` | first column | Which column is the lead cell for `rowHref` |
406
+ | `classRowLink` | `string` | - | Extra classes for the `rowHref` anchor |
407
+ | `rowActivatable` | `boolean` | `false` | Make the desktop `<tr>` focusable + `Enter`-activatable (no `role`) |
408
+ | `rowLabel` | `(row, index) => string \| undefined` | - | Accessible name for an activatable row |
409
+ | `loading` | `boolean` | `false` | Show loading overlay |
410
+ | `small` | `boolean` | `false` | Force mobile/card layout regardless of viewport |
411
+ | `t` | `TranslateFn` | built-in | Optional translation function |
412
+ | `cell` | `Snippet` | - | Custom cell renderer (desktop + mobile) |
413
+ | `row` | `Snippet` | - | Custom desktop `<tr>` renderer (overrides default row) |
414
+ | `mobileRow` | `Snippet` | - | Custom mobile card renderer |
415
+ | `batchActions` | `Snippet` | - | Selection bar content while something is selected |
416
+ | `selectAllBanner` | `Snippet` | - | Override default "select all across pages" offer |
417
+ | `empty` | `Snippet` | - | Custom empty state |
418
+ | `unstyled` | `boolean` | `false` | Skip default styling |
419
+ | `class` | `string` | - | Additional CSS classes |
420
+ | `el` | `HTMLDivElement` | - | Bindable element ref |
347
421
 
348
422
  ### Snippet signatures
349
423
 
@@ -407,6 +481,7 @@ const t = createDataTableT({ ...DATA_TABLE_MESSAGES_SK, no_data: "Nič tu nie je
407
481
  | `previous_page` | Prev | Paging |
408
482
  | `next_page` | Next | Paging |
409
483
  | `page_x_of_y` | Page {page} of {pageCount} | Paging |
484
+ | `pagination` | Pagination | Pager `<nav>` `aria-label` |
410
485
  | `no_data` | No data | Empty state (no `empty` snippet) |
411
486
  | `select_all_rows` | Select all rows on this page | Header checkbox `aria-label` |
412
487
  | `select_row` | Select row | Row checkbox `aria-label` |
@@ -17,6 +17,7 @@ export const DATA_TABLE_MESSAGES_SK = {
17
17
  previous_page: "Späť",
18
18
  next_page: "Ďalej",
19
19
  page_x_of_y: "Strana {page} z {pageCount}",
20
+ pagination: "Stránkovanie",
20
21
  no_data: "Žiadne údaje",
21
22
  select_all_rows: "Vybrať všetky riadky na tejto strane",
22
23
  select_row: "Vybrať riadok",
@@ -9,6 +9,7 @@ export declare const DATA_TABLE_MESSAGES_EN: {
9
9
  previous_page: string;
10
10
  next_page: string;
11
11
  page_x_of_y: string;
12
+ pagination: string;
12
13
  no_data: string;
13
14
  select_all_rows: string;
14
15
  select_row: string;
@@ -10,6 +10,9 @@ export const DATA_TABLE_MESSAGES_EN = {
10
10
  previous_page: "Prev",
11
11
  next_page: "Next",
12
12
  page_x_of_y: "Page {page} of {pageCount}",
13
+ // The pager is a `Pagination`; this is its `<nav>` accessible name. Identical to
14
+ // `PAGINATION_MESSAGES_EN` so one merged catalog can still serve both components.
15
+ pagination: "Pagination",
13
16
  no_data: "No data",
14
17
  select_all_rows: "Select all rows on this page",
15
18
  select_row: "Select row",
@@ -224,18 +224,31 @@
224
224
  PAGING
225
225
  ============================================================================ */
226
226
 
227
+ /*
228
+ The built-in pager IS a `Pagination` (compact variant), so these two classes now
229
+ sit on Pagination's own nodes alongside `.stuic-pagination`. They add the region's
230
+ top padding (which Pagination has no notion of) and re-point Pagination's tokens
231
+ at the table's -- as custom properties rather than plain declarations on purpose:
232
+ an existing `--stuic-data-table-paging-*` / `--stuic-data-table-cell-font-size`
233
+ override keeps driving the pager, with no dependence on which of the two
234
+ same-specificity rules happens to win the cascade. The layout declarations below
235
+ are a deliberate duplicate of Pagination's own, so the region class stays
236
+ self-sufficient.
237
+ */
227
238
  .stuic-data-table-paging {
228
239
  display: flex;
229
240
  align-items: center;
230
241
  justify-content: center;
231
- gap: var(--stuic-data-table-paging-gap);
232
242
  padding-top: var(--stuic-data-table-paging-padding-y);
243
+
244
+ --stuic-pagination-gap: var(--stuic-data-table-paging-gap);
233
245
  }
234
246
 
235
247
  .stuic-data-table-paging-info {
236
- font-size: var(--stuic-data-table-cell-font-size);
237
- color: var(--stuic-data-table-header-color);
238
248
  white-space: nowrap;
249
+
250
+ --stuic-pagination-info-font-size: var(--stuic-data-table-cell-font-size);
251
+ --stuic-pagination-info-text: var(--stuic-data-table-header-color);
239
252
  }
240
253
 
241
254
  /* ============================================================================
@@ -322,6 +335,31 @@
322
335
  justify-content: center;
323
336
  }
324
337
 
338
+ /*
339
+ The built-in empty state is an `EmptyState`, which brings its own padding -- which
340
+ would otherwise stack on the container's. Keyed on the marker class rather than on
341
+ `.stuic-empty-state` so that an `empty` snippet rendering an EmptyState of its own
342
+ is left alone: it keeps the container padding it has always had, and the
343
+ component's own look.
344
+ */
345
+ .stuic-data-table-empty:has(> .stuic-data-table-empty-state) {
346
+ padding: 0;
347
+ }
348
+
349
+ /*
350
+ Pull the built-in EmptyState back to table chrome: the "No data" line is a cell
351
+ note, not a headline. Fed from the tokens the rest of the table uses, so it stays
352
+ in sync with a consumer's overrides.
353
+ */
354
+ .stuic-data-table-empty-state {
355
+ --stuic-empty-state-padding-sm: 2rem;
356
+ --stuic-empty-state-title-font-size-sm: var(--stuic-data-table-cell-font-size);
357
+ /* NOT `inherit`: a CSS-wide keyword in a custom property applies to the property
358
+ itself, so it would inherit the :root value (semibold) rather than the cell's. */
359
+ --stuic-empty-state-title-font-weight: normal;
360
+ --stuic-empty-state-title-text: var(--stuic-data-table-header-color);
361
+ }
362
+
325
363
  /* ============================================================================
326
364
  MOBILE CARDS
327
365
  ============================================================================ */