@marianmeres/stuic 3.167.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 (103) hide show
  1. package/AGENTS.md +6 -6
  2. package/dist/attachments/index.d.ts +1 -0
  3. package/dist/attachments/index.js +1 -0
  4. package/dist/attachments/long-press.d.ts +43 -0
  5. package/dist/attachments/long-press.js +107 -0
  6. package/dist/components/Breadcrumbs/Breadcrumbs.svelte +198 -0
  7. package/dist/components/Breadcrumbs/Breadcrumbs.svelte.d.ts +60 -0
  8. package/dist/components/Breadcrumbs/README.md +174 -0
  9. package/dist/components/Breadcrumbs/i18n-sk.d.ts +17 -0
  10. package/dist/components/Breadcrumbs/i18n-sk.js +19 -0
  11. package/dist/components/Breadcrumbs/i18n.d.ts +31 -0
  12. package/dist/components/Breadcrumbs/i18n.js +40 -0
  13. package/dist/components/Breadcrumbs/index.css +84 -0
  14. package/dist/components/Breadcrumbs/index.d.ts +4 -0
  15. package/dist/components/Breadcrumbs/index.js +4 -0
  16. package/dist/components/Breadcrumbs/json-ld.d.ts +58 -0
  17. package/dist/components/Breadcrumbs/json-ld.js +56 -0
  18. package/dist/components/Collapsible/Collapsible.svelte +84 -15
  19. package/dist/components/Collapsible/Collapsible.svelte.d.ts +29 -2
  20. package/dist/components/Collapsible/README.md +82 -23
  21. package/dist/components/Collapsible/index.css +38 -0
  22. package/dist/components/Collapsible/index.d.ts +1 -1
  23. package/dist/components/ContextMenu/ContextMenu.svelte +302 -0
  24. package/dist/components/ContextMenu/ContextMenu.svelte.d.ts +94 -0
  25. package/dist/components/ContextMenu/README.md +159 -0
  26. package/dist/components/ContextMenu/i18n-sk.d.ts +17 -0
  27. package/dist/components/ContextMenu/i18n-sk.js +18 -0
  28. package/dist/components/ContextMenu/i18n.d.ts +30 -0
  29. package/dist/components/ContextMenu/i18n.js +38 -0
  30. package/dist/components/ContextMenu/index.css +26 -0
  31. package/dist/components/ContextMenu/index.d.ts +3 -0
  32. package/dist/components/ContextMenu/index.js +3 -0
  33. package/dist/components/CopyButton/CopyButton.svelte +278 -0
  34. package/dist/components/CopyButton/CopyButton.svelte.d.ts +76 -0
  35. package/dist/components/CopyButton/README.md +241 -0
  36. package/dist/components/CopyButton/i18n-sk.d.ts +17 -0
  37. package/dist/components/CopyButton/i18n-sk.js +20 -0
  38. package/dist/components/CopyButton/i18n.d.ts +35 -0
  39. package/dist/components/CopyButton/i18n.js +43 -0
  40. package/dist/components/CopyButton/index.css +67 -0
  41. package/dist/components/CopyButton/index.d.ts +3 -0
  42. package/dist/components/CopyButton/index.js +3 -0
  43. package/dist/components/DataTable/DataTable.svelte +41 -34
  44. package/dist/components/DataTable/README.md +45 -0
  45. package/dist/components/DataTable/i18n-sk.js +1 -0
  46. package/dist/components/DataTable/i18n.d.ts +1 -0
  47. package/dist/components/DataTable/i18n.js +3 -0
  48. package/dist/components/DataTable/index.css +41 -3
  49. package/dist/components/EmptyState/EmptyState.svelte +121 -0
  50. package/dist/components/EmptyState/EmptyState.svelte.d.ts +40 -0
  51. package/dist/components/EmptyState/README.md +188 -0
  52. package/dist/components/EmptyState/index.css +154 -0
  53. package/dist/components/EmptyState/index.d.ts +1 -0
  54. package/dist/components/EmptyState/index.js +1 -0
  55. package/dist/components/Pagination/Pagination.svelte +250 -0
  56. package/dist/components/Pagination/Pagination.svelte.d.ts +69 -0
  57. package/dist/components/Pagination/README.md +152 -0
  58. package/dist/components/Pagination/i18n-sk.d.ts +17 -0
  59. package/dist/components/Pagination/i18n-sk.js +24 -0
  60. package/dist/components/Pagination/i18n.d.ts +41 -0
  61. package/dist/components/Pagination/i18n.js +49 -0
  62. package/dist/components/Pagination/index.css +50 -0
  63. package/dist/components/Pagination/index.d.ts +4 -0
  64. package/dist/components/Pagination/index.js +4 -0
  65. package/dist/components/Pagination/pagination-range.d.ts +21 -0
  66. package/dist/components/Pagination/pagination-range.js +41 -0
  67. package/dist/components/Stat/README.md +157 -0
  68. package/dist/components/Stat/Stat.svelte +226 -0
  69. package/dist/components/Stat/Stat.svelte.d.ts +54 -0
  70. package/dist/components/Stat/index.css +217 -0
  71. package/dist/components/Stat/index.d.ts +1 -0
  72. package/dist/components/Stat/index.js +1 -0
  73. package/dist/components/Stepper/README.md +181 -0
  74. package/dist/components/Stepper/Stepper.svelte +240 -0
  75. package/dist/components/Stepper/Stepper.svelte.d.ts +86 -0
  76. package/dist/components/Stepper/i18n-sk.d.ts +17 -0
  77. package/dist/components/Stepper/i18n-sk.js +21 -0
  78. package/dist/components/Stepper/i18n.d.ts +35 -0
  79. package/dist/components/Stepper/i18n.js +43 -0
  80. package/dist/components/Stepper/index.css +302 -0
  81. package/dist/components/Stepper/index.d.ts +3 -0
  82. package/dist/components/Stepper/index.js +3 -0
  83. package/dist/components/TrendChart/README.md +123 -0
  84. package/dist/components/TrendChart/TrendChart.svelte +114 -0
  85. package/dist/components/TrendChart/TrendChart.svelte.d.ts +36 -0
  86. package/dist/components/TrendChart/index.css +51 -0
  87. package/dist/components/TrendChart/index.d.ts +1 -0
  88. package/dist/components/TrendChart/index.js +1 -0
  89. package/dist/icons/index.d.ts +4 -0
  90. package/dist/icons/index.js +4 -0
  91. package/dist/index.css +7 -0
  92. package/dist/index.d.ts +7 -0
  93. package/dist/index.js +12 -0
  94. package/dist/utils/copy-to-clipboard.d.ts +43 -0
  95. package/dist/utils/copy-to-clipboard.js +102 -0
  96. package/dist/utils/index.d.ts +1 -0
  97. package/dist/utils/index.js +1 -0
  98. package/docs/architecture.md +1 -1
  99. package/docs/domains/attachments.md +41 -4
  100. package/docs/domains/components.md +280 -13
  101. package/docs/domains/utils.md +8 -6
  102. package/docs/maybe-todo.md +111 -0
  103. package/package.json +11 -1
@@ -0,0 +1,152 @@
1
+ # Pagination
2
+
3
+ Standalone pagination control for lists, search results, and custom views. Two
4
+ variants: `compact` — prev / "Page X of Y" / next, which _is_ the pager `DataTable`
5
+ renders internally — and `numbers` — windowed page-number buttons with ellipsis gaps
6
+ (the classic `1 … 4 5 6 … 20`).
7
+
8
+ Consumes the same `PagingCalcResult` from
9
+ [`@marianmeres/paging-store`](https://github.com/marianmeres/paging-store) that
10
+ `DataTable` does, so one paging store (or one `calculatePaging` call) can feed both.
11
+ For quick standalone use, `total`/`limit`/`offset` props compute the paging
12
+ internally.
13
+
14
+ ## Props
15
+
16
+ | Prop | Type | Default | Description |
17
+ | -------------------- | ------------------------ | ----------- | ------------------------------------------------------------------------------------- |
18
+ | `paging` | `PagingCalcResult` | - | Paging metadata (same shape `DataTable` consumes); wins over `total`/`limit`/`offset` |
19
+ | `total` | `number` | - | Convenience alternative to `paging`: total item count |
20
+ | `limit` | `number` | `10` | Convenience alternative to `paging`: page size |
21
+ | `offset` | `number` | `0` | Convenience alternative to `paging`: current offset |
22
+ | `onPageChange` | `(offset, page) => void` | - | Navigation callback: new offset (`DataTable`-compatible first arg) + 1-based page |
23
+ | `variant` | `"compact" \| "numbers"` | `"compact"` | Prev/info/next, or windowed page-number buttons |
24
+ | `siblingCount` | `number` | `1` | Numbers variant: pages always shown around the current page |
25
+ | `boundaryCount` | `number` | `1` | Numbers variant: pages always shown at the start and end |
26
+ | `showFirstLast` | `boolean` | `false` | Render first/last jump buttons (« ») |
27
+ | `showInfo` | `boolean` | per variant | "Page X of Y" info; defaults `true` for compact, `false` for numbers |
28
+ | `hideSinglePage` | `boolean` | `true` | Render nothing when there is at most one page (the `DataTable` rule) |
29
+ | `disabled` | `boolean` | `false` | Disable all controls (e.g. while loading) |
30
+ | `size` | `ButtonSize` | `"sm"` | Size preset of the inner Buttons |
31
+ | `t` | `TranslateFn` | English | i18n translate function (see below) |
32
+ | `unstyled` | `boolean` | `false` | Skip pagination styling (inner Buttons keep their own Button styling) |
33
+ | `class` | `string` | - | Additional CSS classes (merged via twMerge) |
34
+ | `classButton` | `string` | - | Class for every inner button |
35
+ | `classButtonCurrent` | `string` | - | Extra class for the current-page button (numbers variant) |
36
+ | `classInfo` | `string` | - | Class for the "Page X of Y" info |
37
+ | `el` | `HTMLElement` | - | Element reference (bindable) |
38
+
39
+ ## Snippet Props
40
+
41
+ | Snippet | Description |
42
+ | ------------ | ------------------------------------------------------------------------ |
43
+ | `renderInfo` | Override the "Page X of Y" info content; receives the `PagingCalcResult` |
44
+
45
+ ## Usage
46
+
47
+ ### Basic (compact)
48
+
49
+ ```svelte
50
+ <script lang="ts">
51
+ import { Pagination } from "@marianmeres/stuic";
52
+
53
+ let offset = $state(0);
54
+ </script>
55
+
56
+ <Pagination total={137} limit={10} {offset} onPageChange={(o) => (offset = o)} />
57
+ ```
58
+
59
+ ### With a paging store (shared with DataTable)
60
+
61
+ The `paging` prop takes the exact object `DataTable` consumes — hide the table's
62
+ built-in pager and drive both from one store:
63
+
64
+ ```svelte
65
+ <script lang="ts">
66
+ import { DataTable, Pagination } from "@marianmeres/stuic";
67
+ import { createPagingStore } from "@marianmeres/paging-store";
68
+
69
+ const paging = createPagingStore({ total: 137, limit: 10, offset: 0 });
70
+ const onPageChange = (offset: number) => paging.update({ offset });
71
+ </script>
72
+
73
+ <DataTable {columns} {data} paging={$paging} showPager={false} />
74
+ <Pagination paging={$paging} {onPageChange} variant="numbers" />
75
+ ```
76
+
77
+ ### Numbers variant
78
+
79
+ ```svelte
80
+ <Pagination variant="numbers" total={200} {offset} onPageChange={(o) => (offset = o)} />
81
+
82
+ <!-- wider window, first/last jumps -->
83
+ <Pagination
84
+ variant="numbers"
85
+ siblingCount={2}
86
+ boundaryCount={2}
87
+ showFirstLast
88
+ total={200}
89
+ {offset}
90
+ onPageChange={(o) => (offset = o)}
91
+ />
92
+ ```
93
+
94
+ ### i18n
95
+
96
+ Built-in English; bundled Slovak (`PAGINATION_MESSAGES_SK`) is opt-in. The
97
+ `previous_page`, `next_page` and `page_x_of_y` keys are identical to `DataTable`'s,
98
+ so one merged catalog (and one `t`) can serve both components.
99
+
100
+ ```svelte
101
+ <script lang="ts">
102
+ import {
103
+ Pagination,
104
+ createPaginationT,
105
+ PAGINATION_MESSAGES_SK,
106
+ } from "@marianmeres/stuic";
107
+ const t = createPaginationT(PAGINATION_MESSAGES_SK);
108
+ </script>
109
+
110
+ <Pagination {paging} {onPageChange} {t} />
111
+ ```
112
+
113
+ ### Custom info
114
+
115
+ ```svelte
116
+ <Pagination {paging} {onPageChange}>
117
+ {#snippet renderInfo(p)}
118
+ {p.offset + 1}–{Math.min(p.offset + p.limit, p.total)} of {p.total}
119
+ {/snippet}
120
+ </Pagination>
121
+ ```
122
+
123
+ ### Single page
124
+
125
+ By default nothing renders when there is at most one page (same rule as
126
+ `DataTable`'s internal pager). Keep it visible with `hideSinglePage={false}`.
127
+
128
+ ## Accessibility
129
+
130
+ - Renders a `<nav aria-label="Pagination">` landmark (label localized via `t`).
131
+ - Every button carries a localized `aria-label`; the current page button (numbers
132
+ variant) is marked `aria-current="page"`.
133
+
134
+ ## Exports
135
+
136
+ Besides the component: `paginationRange(currentPage, pageCount, { siblingCount, boundaryCount })`
137
+ — the pure windowing helper behind the numbers variant, usable for fully custom pagers.
138
+
139
+ ## CSS Variables
140
+
141
+ | Variable | Default | Description |
142
+ | ------------------------------------- | -------------------------------- | -------------------------------------------------- |
143
+ | `--stuic-pagination-gap` | `0.5rem` | Gap between controls |
144
+ | `--stuic-pagination-button-min-width` | `--stuic-button-min-height-sm` | Min button width (keeps number buttons square-ish) |
145
+ | `--stuic-pagination-info-font-size` | `--text-sm` | Info font size |
146
+ | `--stuic-pagination-info-text` | `--stuic-color-muted-foreground` | Info color |
147
+ | `--stuic-pagination-ellipsis-text` | `--stuic-color-muted-foreground` | Ellipsis color |
148
+
149
+ ## Data Attributes
150
+
151
+ - `data-variant` - `"compact" | "numbers"` (on the root `<nav>`)
152
+ - `data-current` - `"true"` on the current-page button (numbers variant)
@@ -0,0 +1,17 @@
1
+ import type { PaginationMessages } from "./i18n.js";
2
+ /**
3
+ * Slovak message catalog for `Pagination`. Opt-in — English stays the built-in default,
4
+ * and this module is only pulled into a bundle when it is actually imported (the
5
+ * component itself never references it).
6
+ *
7
+ * @example
8
+ * ```svelte
9
+ * <script>
10
+ * import { Pagination, createPaginationT, PAGINATION_MESSAGES_SK } from "@marianmeres/stuic";
11
+ * const t = createPaginationT(PAGINATION_MESSAGES_SK);
12
+ * </script>
13
+ *
14
+ * <Pagination {paging} {onPageChange} {t} />
15
+ * ```
16
+ */
17
+ export declare const PAGINATION_MESSAGES_SK: PaginationMessages;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Slovak message catalog for `Pagination`. Opt-in — English stays the built-in default,
3
+ * and this module is only pulled into a bundle when it is actually imported (the
4
+ * component itself never references it).
5
+ *
6
+ * @example
7
+ * ```svelte
8
+ * <script>
9
+ * import { Pagination, createPaginationT, PAGINATION_MESSAGES_SK } from "@marianmeres/stuic";
10
+ * const t = createPaginationT(PAGINATION_MESSAGES_SK);
11
+ * </script>
12
+ *
13
+ * <Pagination {paging} {onPageChange} {t} />
14
+ * ```
15
+ */
16
+ export const PAGINATION_MESSAGES_SK = {
17
+ previous_page: "Späť",
18
+ next_page: "Ďalej",
19
+ first_page: "Prvá strana",
20
+ last_page: "Posledná strana",
21
+ page_x_of_y: "Strana {page} z {pageCount}",
22
+ go_to_page_x: "Prejsť na stranu {page}",
23
+ pagination: "Stránkovanie",
24
+ };
@@ -0,0 +1,41 @@
1
+ import type { TranslateFn } from "../../types.js";
2
+ /**
3
+ * The built-in (English) message catalog of `Pagination`. Also the fallback of every
4
+ * other bundled locale, so a locale missing a key still renders text.
5
+ *
6
+ * The `previous_page`, `next_page` and `page_x_of_y` keys are intentionally identical
7
+ * to `DataTable`'s, so one merged catalog (and one `t`) can serve both components.
8
+ *
9
+ * Placeholders are single-brace (`{page}`, `{pageCount}`, ...).
10
+ */
11
+ export declare const PAGINATION_MESSAGES_EN: {
12
+ previous_page: string;
13
+ next_page: string;
14
+ first_page: string;
15
+ last_page: string;
16
+ page_x_of_y: string;
17
+ go_to_page_x: string;
18
+ pagination: string;
19
+ };
20
+ /** Every message key `Pagination` may look up. */
21
+ export type PaginationMessageKey = keyof typeof PAGINATION_MESSAGES_EN;
22
+ /** A (possibly partial) catalog for one locale. */
23
+ export type PaginationMessages = Record<PaginationMessageKey, string>;
24
+ /**
25
+ * Builds the `t` prop of `Pagination` from a message catalog. Unknown or untranslated
26
+ * keys fall back to `fallbackMessages` (English by default), so a catalog may safely be
27
+ * partial and never renders a raw key.
28
+ *
29
+ * @example
30
+ * ```svelte
31
+ * <script>
32
+ * import { Pagination, createPaginationT, PAGINATION_MESSAGES_SK } from "@marianmeres/stuic";
33
+ * const t = createPaginationT(PAGINATION_MESSAGES_SK);
34
+ * </script>
35
+ *
36
+ * <Pagination {paging} {onPageChange} {t} />
37
+ * ```
38
+ */
39
+ export declare function createPaginationT(messages: Partial<PaginationMessages> | Record<string, string>, fallbackMessages?: Partial<PaginationMessages> | Record<string, string>): TranslateFn;
40
+ /** The component's built-in English `t`. */
41
+ export declare const t_default: TranslateFn;
@@ -0,0 +1,49 @@
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 `Pagination`. Also the fallback of every
5
+ * other bundled locale, so a locale missing a key still renders text.
6
+ *
7
+ * The `previous_page`, `next_page` and `page_x_of_y` keys are intentionally identical
8
+ * to `DataTable`'s, so one merged catalog (and one `t`) can serve both components.
9
+ *
10
+ * Placeholders are single-brace (`{page}`, `{pageCount}`, ...).
11
+ */
12
+ export const PAGINATION_MESSAGES_EN = {
13
+ previous_page: "Prev",
14
+ next_page: "Next",
15
+ first_page: "First page",
16
+ last_page: "Last page",
17
+ page_x_of_y: "Page {page} of {pageCount}",
18
+ go_to_page_x: "Go to page {page}",
19
+ pagination: "Pagination",
20
+ };
21
+ /**
22
+ * Builds the `t` prop of `Pagination` from a message catalog. Unknown or untranslated
23
+ * keys fall back to `fallbackMessages` (English by default), so a catalog may safely be
24
+ * partial and never renders a raw key.
25
+ *
26
+ * @example
27
+ * ```svelte
28
+ * <script>
29
+ * import { Pagination, createPaginationT, PAGINATION_MESSAGES_SK } from "@marianmeres/stuic";
30
+ * const t = createPaginationT(PAGINATION_MESSAGES_SK);
31
+ * </script>
32
+ *
33
+ * <Pagination {paging} {onPageChange} {t} />
34
+ * ```
35
+ */
36
+ export function createPaginationT(messages, fallbackMessages = PAGINATION_MESSAGES_EN) {
37
+ return (k, values = null, fallback = "") => {
38
+ const out = messages[k] ??
39
+ fallbackMessages[k] ??
40
+ (typeof fallback === "string" ? fallback : k);
41
+ return isPlainObject(values)
42
+ ? replaceMap(out, values, {
43
+ preSearchKeyTransform: (k) => `{${k}}`,
44
+ })
45
+ : out;
46
+ };
47
+ }
48
+ /** The component's built-in English `t`. */
49
+ export const t_default = createPaginationT(PAGINATION_MESSAGES_EN);
@@ -0,0 +1,50 @@
1
+ /* ============================================================================
2
+ PAGINATION COMPONENT TOKENS
3
+ Override globally: :root { --stuic-pagination-gap: 1rem; }
4
+ Override locally: <Pagination style="--stuic-pagination-button-min-width: 2rem;">
5
+ ============================================================================ */
6
+
7
+ /* prettier-ignore */
8
+ :root {
9
+ --stuic-pagination-gap: 0.5rem;
10
+
11
+ /* Keeps single-character buttons («, ‹, page numbers) square-ish and aligned.
12
+ Sized to the `size="sm"` Button height. Text buttons (Prev/Next) simply exceed it. */
13
+ --stuic-pagination-button-min-width: var(--stuic-button-min-height-sm, 2.75rem);
14
+
15
+ /* "Page X of Y" info */
16
+ --stuic-pagination-info-font-size: var(--text-sm);
17
+ --stuic-pagination-info-text: var(--stuic-color-muted-foreground);
18
+
19
+ /* Ellipsis gap (numbers variant) */
20
+ --stuic-pagination-ellipsis-text: var(--stuic-color-muted-foreground);
21
+ }
22
+
23
+ @layer components {
24
+ .stuic-pagination {
25
+ display: flex;
26
+ align-items: center;
27
+ justify-content: center;
28
+ flex-wrap: wrap;
29
+ gap: var(--stuic-pagination-gap);
30
+ }
31
+
32
+ .stuic-pagination-button {
33
+ min-width: var(--stuic-pagination-button-min-width);
34
+ }
35
+
36
+ .stuic-pagination-info {
37
+ font-size: var(--stuic-pagination-info-font-size);
38
+ color: var(--stuic-pagination-info-text);
39
+ white-space: nowrap;
40
+ }
41
+
42
+ .stuic-pagination-ellipsis {
43
+ display: inline-flex;
44
+ align-items: center;
45
+ justify-content: center;
46
+ min-width: var(--stuic-pagination-button-min-width);
47
+ color: var(--stuic-pagination-ellipsis-text);
48
+ user-select: none;
49
+ }
50
+ }
@@ -0,0 +1,4 @@
1
+ export { default as Pagination, type Props as PaginationProps, type PaginationVariant, } from "./Pagination.svelte";
2
+ export { paginationRange, type PaginationRangeItem, type PaginationRangeOptions, } from "./pagination-range.js";
3
+ export { createPaginationT, PAGINATION_MESSAGES_EN, type PaginationMessageKey, type PaginationMessages, } from "./i18n.js";
4
+ export { PAGINATION_MESSAGES_SK } from "./i18n-sk.js";
@@ -0,0 +1,4 @@
1
+ export { default as Pagination, } from "./Pagination.svelte";
2
+ export { paginationRange, } from "./pagination-range.js";
3
+ export { createPaginationT, PAGINATION_MESSAGES_EN, } from "./i18n.js";
4
+ export { PAGINATION_MESSAGES_SK } from "./i18n-sk.js";
@@ -0,0 +1,21 @@
1
+ /** One rendered slot of the "numbers" variant: a page number or an ellipsis gap. */
2
+ export type PaginationRangeItem = number | "ellipsis";
3
+ export interface PaginationRangeOptions {
4
+ /** How many pages to always show around the current page (default 1). */
5
+ siblingCount?: number;
6
+ /** How many pages to always show at the start and at the end (default 1). */
7
+ boundaryCount?: number;
8
+ }
9
+ /**
10
+ * Computes the windowed page list of the "numbers" variant: boundary pages at both
11
+ * ends, siblings around the current page, and `"ellipsis"` tokens for the collapsed
12
+ * gaps. The window is stable — for a given `pageCount` and options, every
13
+ * `currentPage` yields the same number of slots (no layout jumping while paging).
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * paginationRange(5, 10); // [1, "ellipsis", 4, 5, 6, "ellipsis", 10]
18
+ * paginationRange(1, 5); // [1, 2, 3, 4, 5]
19
+ * ```
20
+ */
21
+ export declare function paginationRange(currentPage: number, pageCount: number, options?: PaginationRangeOptions): PaginationRangeItem[];
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Computes the windowed page list of the "numbers" variant: boundary pages at both
3
+ * ends, siblings around the current page, and `"ellipsis"` tokens for the collapsed
4
+ * gaps. The window is stable — for a given `pageCount` and options, every
5
+ * `currentPage` yields the same number of slots (no layout jumping while paging).
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * paginationRange(5, 10); // [1, "ellipsis", 4, 5, 6, "ellipsis", 10]
10
+ * paginationRange(1, 5); // [1, 2, 3, 4, 5]
11
+ * ```
12
+ */
13
+ export function paginationRange(currentPage, pageCount, options = {}) {
14
+ const siblingCount = Math.max(0, Math.floor(options.siblingCount ?? 1));
15
+ const boundaryCount = Math.max(0, Math.floor(options.boundaryCount ?? 1));
16
+ if (!Number.isFinite(pageCount) || pageCount <= 0)
17
+ return [];
18
+ const page = Math.min(Math.max(1, Math.floor(currentPage) || 1), pageCount);
19
+ const range = (start, end) => Array.from({ length: end - start + 1 }, (_, i) => start + i);
20
+ const startPages = range(1, Math.min(boundaryCount, pageCount));
21
+ const endPages = range(Math.max(pageCount - boundaryCount + 1, boundaryCount + 1), pageCount);
22
+ const siblingsStart = Math.max(Math.min(page - siblingCount, pageCount - boundaryCount - siblingCount * 2 - 1), boundaryCount + 2);
23
+ const siblingsEnd = Math.min(Math.max(page + siblingCount, boundaryCount + siblingCount * 2 + 2), endPages.length > 0 ? endPages[0] - 2 : pageCount - 1);
24
+ return [
25
+ ...startPages,
26
+ // start gap: ellipsis, the single page it would hide, or nothing
27
+ ...(siblingsStart > boundaryCount + 2
28
+ ? ["ellipsis"]
29
+ : boundaryCount + 1 < pageCount - boundaryCount
30
+ ? [boundaryCount + 1]
31
+ : []),
32
+ ...range(siblingsStart, siblingsEnd),
33
+ // end gap: ellipsis, the single page it would hide, or nothing
34
+ ...(siblingsEnd < pageCount - boundaryCount - 1
35
+ ? ["ellipsis"]
36
+ : pageCount - boundaryCount > boundaryCount
37
+ ? [pageCount - boundaryCount]
38
+ : []),
39
+ ...endPages,
40
+ ];
41
+ }
@@ -0,0 +1,157 @@
1
+ # Stat
2
+
3
+ A KPI/stat card: a metric label, a prominent value, and an optional delta with trend
4
+ arrow and color-coded semantics (up/down/flat), plus an optional hint, corner icon, and
5
+ footer area (e.g. for a sparkline). Renders as a `<div>`, or as an `<a>`/`<button>` when
6
+ `href`/`onclick` is provided.
7
+
8
+ ## Usage
9
+
10
+ ```svelte
11
+ <script>
12
+ import { Stat } from "@marianmeres/stuic";
13
+ // optional, for the sparkline footer example below (see TrendChart's README):
14
+ import { TrendChart } from "@marianmeres/stuic/trend-chart";
15
+ </script>
16
+
17
+ <!-- Basic -->
18
+ <Stat
19
+ label="Total Revenue"
20
+ value="$45,231.89"
21
+ delta="+20.1%"
22
+ trend="up"
23
+ hint="vs. last month"
24
+ />
25
+
26
+ <!-- Trend down (auto-colored destructive) -->
27
+ <Stat
28
+ label="Bounce Rate"
29
+ value="47.3%"
30
+ delta="+2.4%"
31
+ trend="up"
32
+ trendIntent="destructive"
33
+ />
34
+
35
+ <!-- Down is good here: override the auto color mapping -->
36
+ <Stat label="Churn" value="2.1%" delta="-0.4%" trend="down" trendIntent="success" />
37
+
38
+ <!-- With a corner icon -->
39
+ <Stat label="Active Users" value="2,338" delta="+180" trend="up">
40
+ {#snippet renderIcon()}
41
+ {@html iconUser({ size: 20 })}
42
+ {/snippet}
43
+ </Stat>
44
+
45
+ <!-- Clickable (renders as <a>) -->
46
+ <Stat
47
+ href="/analytics/revenue"
48
+ label="Revenue"
49
+ value="$45,231"
50
+ delta="+20.1%"
51
+ trend="up"
52
+ />
53
+
54
+ <!-- Footer (sparkline, actions, ...) — first-class pairing with the TrendChart
55
+ wrapper (subpath import; optional @marianmeres/trend-chart peer dep) -->
56
+ <Stat label="Requests" value="1.2M" delta="+8.4%" trend="up">
57
+ {#snippet renderFooter()}
58
+ <TrendChart data={[42, 51, 48, 63, 58, 71, 84]} sparkline />
59
+ {/snippet}
60
+ </Stat>
61
+
62
+ <!-- Full override -->
63
+ <Stat>
64
+ <div class="p-4">Completely custom content</div>
65
+ </Stat>
66
+ ```
67
+
68
+ ## Props
69
+
70
+ | Prop | Type | Default | Description |
71
+ | ---------------- | ---------------------------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
72
+ | `label` | `THC` | - | Small label above the value (metric name) |
73
+ | `value` | `THC \| number` | - | The primary metric value, preformatted (e.g. `"$45,231.89"`) |
74
+ | `delta` | `THC \| number` | - | Delta/change indicator, preformatted (e.g. `"+20.1%"`) |
75
+ | `trend` | `"up" \| "down" \| "flat"` | - | Trend direction — drives the arrow and (via `trendIntent="auto"`) the delta color |
76
+ | `trendIntent` | `"auto" \| "success" \| "destructive" \| "warning" \| "neutral"` | `"auto"` | Delta color semantics. `"auto"` maps up→success, down→destructive, flat→neutral. Set explicitly for metrics where down is good (churn, costs). |
77
+ | `showTrendArrow` | `boolean` | `true` | Show the trend direction arrow next to the delta |
78
+ | `hint` | `THC` | - | Secondary text next to the delta (e.g. "vs. last month") |
79
+ | `href` | `string` | - | Renders stat as `<a>` |
80
+ | `onclick` | `(e: MouseEvent) => void` | - | Renders stat as `<button>` |
81
+ | `disabled` | `boolean` | `false` | Disabled state (reduced opacity, no interaction) |
82
+ | `unstyled` | `boolean` | `false` | Skip all default styling |
83
+ | `class` | `string` | - | Additional CSS classes for the stat shell |
84
+ | `classLabel` | `string` | - | Additional CSS classes for the label |
85
+ | `classValue` | `string` | - | Additional CSS classes for the value |
86
+ | `classDelta` | `string` | - | Additional CSS classes for the delta |
87
+ | `classHint` | `string` | - | Additional CSS classes for the hint |
88
+ | `classFooter` | `string` | - | Additional CSS classes for the footer |
89
+ | `el` | `HTMLElement` | - | Bindable element reference |
90
+
91
+ ## Snippets
92
+
93
+ | Snippet | Parameters | Description |
94
+ | -------------- | ---------- | ------------------------------------------------- |
95
+ | `children` | - | Overrides the entire stat body |
96
+ | `renderIcon` | - | Icon area in the top-right corner (next to label) |
97
+ | `renderFooter` | - | Footer area (sparkline, actions, metadata) |
98
+
99
+ ## CSS Variables
100
+
101
+ | Variable | Default | Description |
102
+ | ------------------------------------- | --------------------------------------------------------- | ----------------------------------- |
103
+ | `--stuic-stat-bg` | `var(--stuic-color-card, var(--stuic-color-background))` | Background color |
104
+ | `--stuic-stat-bg-hover` | `var(--stuic-color-card-hover, var(--stuic-color-muted))` | Hover background (interactive only) |
105
+ | `--stuic-stat-border-width` | `1px` | Border width |
106
+ | `--stuic-stat-border` | `var(--stuic-color-border)` | Border color |
107
+ | `--stuic-stat-border-hover` | `var(--stuic-color-border-hover)` | Hover border color |
108
+ | `--stuic-stat-radius` | `var(--radius-lg)` | Border radius |
109
+ | `--stuic-stat-shadow` | `var(--shadow-sm)` | Box shadow |
110
+ | `--stuic-stat-shadow-hover` | `var(--shadow-md)` | Hover box shadow |
111
+ | `--stuic-stat-transition` | `150ms` | Transition duration |
112
+ | `--stuic-stat-padding` | `1rem` | Padding |
113
+ | `--stuic-stat-gap` | `0.375rem` | Vertical gap between rows |
114
+ | `--stuic-stat-meta-gap` | `0.5rem` | Gap between delta and hint |
115
+ | `--stuic-stat-delta-gap` | `0.25rem` | Gap between arrow and delta text |
116
+ | `--stuic-stat-label-font-size` | `var(--text-sm)` | Label font size |
117
+ | `--stuic-stat-label-font-weight` | `var(--font-weight-medium)` | Label font weight |
118
+ | `--stuic-stat-label-text` | `var(--stuic-color-muted-foreground)` | Label text color |
119
+ | `--stuic-stat-value-font-size` | `var(--text-2xl)` | Value font size |
120
+ | `--stuic-stat-value-font-weight` | `var(--font-weight-semibold)` | Value font weight |
121
+ | `--stuic-stat-value-line-height` | `1.2` | Value line height |
122
+ | `--stuic-stat-value-text` | `var(--stuic-color-foreground)` | Value text color |
123
+ | `--stuic-stat-delta-font-size` | `var(--text-sm)` | Delta font size |
124
+ | `--stuic-stat-delta-font-weight` | `var(--font-weight-medium)` | Delta font weight |
125
+ | `--stuic-stat-delta-text-success` | `var(--stuic-color-success)` | Delta color (success intent) |
126
+ | `--stuic-stat-delta-text-destructive` | `var(--stuic-color-destructive)` | Delta color (destructive intent) |
127
+ | `--stuic-stat-delta-text-warning` | `var(--stuic-color-warning)` | Delta color (warning intent) |
128
+ | `--stuic-stat-delta-text-neutral` | `var(--stuic-color-muted-foreground)` | Delta color (neutral intent) |
129
+ | `--stuic-stat-hint-font-size` | `var(--text-sm)` | Hint font size |
130
+ | `--stuic-stat-hint-text` | `var(--stuic-color-muted-foreground)` | Hint text color |
131
+ | `--stuic-stat-icon-text` | `var(--stuic-color-muted-foreground)` | Corner icon color |
132
+ | `--stuic-stat-ring-width` | `3px` | Focus ring width |
133
+ | `--stuic-stat-ring-color` | `var(--stuic-color-ring)` | Focus ring color |
134
+ | `--stuic-stat-opacity-disabled` | `0.5` | Disabled opacity |
135
+
136
+ ## Notes
137
+
138
+ - The stat fills its parent container (`width: 100%; height: 100%`). Control sizing via
139
+ the parent element (typically a CSS grid of KPI cards).
140
+ - The arrow shows the **direction** (`trend`), the color shows the **sentiment**
141
+ (`trendIntent`). They are deliberately independent: a downward trend can be colored
142
+ as success (churn, costs, error rate) via `trendIntent="success"`.
143
+ - Values and deltas are rendered as-is — format numbers/percentages/currencies yourself
144
+ (e.g. via `Intl.NumberFormat`). Bare `number` values are stringified verbatim.
145
+ - The trend arrow is `aria-hidden`; make sure the delta text itself carries the sign
146
+ (`"+20.1%"`, `"-3%"`) so the direction survives for screen readers.
147
+ - All content props (`label`, `value`, `delta`, `hint`) accept the THC type (plain
148
+ string, HTML, component, or snippet).
149
+ - When using `href` or `onclick`, the entire stat becomes interactive with hover
150
+ effects. Avoid placing interactive elements inside `renderFooter` in this case, as it
151
+ creates invalid nested interactive HTML.
152
+ - Footer sticks to the bottom of the stat via `margin-top: auto` (relevant in
153
+ equal-height grids).
154
+ - For a sparkline footer, the first-class pairing is the `TrendChart` wrapper
155
+ (`import { TrendChart } from "@marianmeres/stuic/trend-chart"`) — a subpath
156
+ export whose `@marianmeres/trend-chart` peer dependency stays opt-in (nothing
157
+ is bundled unless you use it). See `src/lib/components/TrendChart/README.md`.