@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.
- package/AGENTS.md +6 -6
- package/API.md +14 -13
- package/dist/attachments/index.d.ts +1 -0
- package/dist/attachments/index.js +1 -0
- package/dist/attachments/long-press.d.ts +43 -0
- package/dist/attachments/long-press.js +107 -0
- package/dist/components/Breadcrumbs/Breadcrumbs.svelte +198 -0
- package/dist/components/Breadcrumbs/Breadcrumbs.svelte.d.ts +60 -0
- package/dist/components/Breadcrumbs/README.md +174 -0
- package/dist/components/Breadcrumbs/i18n-sk.d.ts +17 -0
- package/dist/components/Breadcrumbs/i18n-sk.js +19 -0
- package/dist/components/Breadcrumbs/i18n.d.ts +31 -0
- package/dist/components/Breadcrumbs/i18n.js +40 -0
- package/dist/components/Breadcrumbs/index.css +84 -0
- package/dist/components/Breadcrumbs/index.d.ts +4 -0
- package/dist/components/Breadcrumbs/index.js +4 -0
- package/dist/components/Breadcrumbs/json-ld.d.ts +58 -0
- package/dist/components/Breadcrumbs/json-ld.js +56 -0
- package/dist/components/Collapsible/Collapsible.svelte +84 -15
- package/dist/components/Collapsible/Collapsible.svelte.d.ts +29 -2
- package/dist/components/Collapsible/README.md +82 -23
- package/dist/components/Collapsible/index.css +38 -0
- package/dist/components/Collapsible/index.d.ts +1 -1
- package/dist/components/ContextMenu/ContextMenu.svelte +302 -0
- package/dist/components/ContextMenu/ContextMenu.svelte.d.ts +94 -0
- package/dist/components/ContextMenu/README.md +159 -0
- package/dist/components/ContextMenu/i18n-sk.d.ts +17 -0
- package/dist/components/ContextMenu/i18n-sk.js +18 -0
- package/dist/components/ContextMenu/i18n.d.ts +30 -0
- package/dist/components/ContextMenu/i18n.js +38 -0
- package/dist/components/ContextMenu/index.css +26 -0
- package/dist/components/ContextMenu/index.d.ts +3 -0
- package/dist/components/ContextMenu/index.js +3 -0
- package/dist/components/CopyButton/CopyButton.svelte +278 -0
- package/dist/components/CopyButton/CopyButton.svelte.d.ts +76 -0
- package/dist/components/CopyButton/README.md +241 -0
- package/dist/components/CopyButton/i18n-sk.d.ts +17 -0
- package/dist/components/CopyButton/i18n-sk.js +20 -0
- package/dist/components/CopyButton/i18n.d.ts +35 -0
- package/dist/components/CopyButton/i18n.js +43 -0
- package/dist/components/CopyButton/index.css +67 -0
- package/dist/components/CopyButton/index.d.ts +3 -0
- package/dist/components/CopyButton/index.js +3 -0
- package/dist/components/DataTable/DataTable.svelte +58 -36
- package/dist/components/DataTable/DataTable.svelte.d.ts +12 -0
- package/dist/components/DataTable/README.md +108 -33
- package/dist/components/DataTable/i18n-sk.js +1 -0
- package/dist/components/DataTable/i18n.d.ts +1 -0
- package/dist/components/DataTable/i18n.js +3 -0
- package/dist/components/DataTable/index.css +41 -3
- package/dist/components/EmptyState/EmptyState.svelte +121 -0
- package/dist/components/EmptyState/EmptyState.svelte.d.ts +40 -0
- package/dist/components/EmptyState/README.md +188 -0
- package/dist/components/EmptyState/index.css +154 -0
- package/dist/components/EmptyState/index.d.ts +1 -0
- package/dist/components/EmptyState/index.js +1 -0
- package/dist/components/Pagination/Pagination.svelte +250 -0
- package/dist/components/Pagination/Pagination.svelte.d.ts +69 -0
- package/dist/components/Pagination/README.md +152 -0
- package/dist/components/Pagination/i18n-sk.d.ts +17 -0
- package/dist/components/Pagination/i18n-sk.js +24 -0
- package/dist/components/Pagination/i18n.d.ts +41 -0
- package/dist/components/Pagination/i18n.js +49 -0
- package/dist/components/Pagination/index.css +50 -0
- package/dist/components/Pagination/index.d.ts +4 -0
- package/dist/components/Pagination/index.js +4 -0
- package/dist/components/Pagination/pagination-range.d.ts +21 -0
- package/dist/components/Pagination/pagination-range.js +41 -0
- package/dist/components/Stat/README.md +157 -0
- package/dist/components/Stat/Stat.svelte +226 -0
- package/dist/components/Stat/Stat.svelte.d.ts +54 -0
- package/dist/components/Stat/index.css +217 -0
- package/dist/components/Stat/index.d.ts +1 -0
- package/dist/components/Stat/index.js +1 -0
- package/dist/components/Stepper/README.md +181 -0
- package/dist/components/Stepper/Stepper.svelte +240 -0
- package/dist/components/Stepper/Stepper.svelte.d.ts +86 -0
- package/dist/components/Stepper/i18n-sk.d.ts +17 -0
- package/dist/components/Stepper/i18n-sk.js +21 -0
- package/dist/components/Stepper/i18n.d.ts +35 -0
- package/dist/components/Stepper/i18n.js +43 -0
- package/dist/components/Stepper/index.css +302 -0
- package/dist/components/Stepper/index.d.ts +3 -0
- package/dist/components/Stepper/index.js +3 -0
- package/dist/components/TrendChart/README.md +123 -0
- package/dist/components/TrendChart/TrendChart.svelte +114 -0
- package/dist/components/TrendChart/TrendChart.svelte.d.ts +36 -0
- package/dist/components/TrendChart/index.css +51 -0
- package/dist/components/TrendChart/index.d.ts +1 -0
- package/dist/components/TrendChart/index.js +1 -0
- package/dist/icons/index.d.ts +4 -0
- package/dist/icons/index.js +4 -0
- package/dist/index.css +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +12 -0
- package/dist/utils/copy-to-clipboard.d.ts +43 -0
- package/dist/utils/copy-to-clipboard.js +102 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/docs/architecture.md +1 -1
- package/docs/domains/attachments.md +41 -4
- package/docs/domains/components.md +280 -13
- package/docs/domains/utils.md +8 -6
- package/docs/maybe-todo.md +111 -0
- 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";
|
|
@@ -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
|
-
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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
|
-
<!--
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
onclick={() => onPageChange?.(paging!.nextOffset)}
|
|
774
|
-
aria-label={t("next_page")}
|
|
775
|
-
>
|
|
776
|
-
{t("next_page")} ›
|
|
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
|
-
| `
|
|
322
|
-
| `
|
|
323
|
-
| `
|
|
324
|
-
| `
|
|
325
|
-
| `
|
|
326
|
-
| `
|
|
327
|
-
| `
|
|
328
|
-
| `
|
|
329
|
-
| `
|
|
330
|
-
| `
|
|
331
|
-
| `
|
|
332
|
-
| `
|
|
333
|
-
| `
|
|
334
|
-
| `
|
|
335
|
-
| `
|
|
336
|
-
| `
|
|
337
|
-
| `
|
|
338
|
-
| `
|
|
339
|
-
| `
|
|
340
|
-
| `
|
|
341
|
-
| `
|
|
342
|
-
| `
|
|
343
|
-
| `
|
|
344
|
-
| `
|
|
345
|
-
| `
|
|
346
|
-
| `
|
|
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",
|
|
@@ -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
|
============================================================================ */
|