@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.
- package/AGENTS.md +6 -6
- 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 +41 -34
- package/dist/components/DataTable/README.md +45 -0
- 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 +11 -1
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { TranslateFn } from "../../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* The built-in (English) message catalog of `Breadcrumbs`. Also the fallback of
|
|
4
|
+
* every other bundled locale, so a locale missing a key still renders text.
|
|
5
|
+
*/
|
|
6
|
+
export declare const BREADCRUMBS_MESSAGES_EN: {
|
|
7
|
+
breadcrumbs: string;
|
|
8
|
+
show_all: string;
|
|
9
|
+
};
|
|
10
|
+
/** Every message key `Breadcrumbs` may look up. */
|
|
11
|
+
export type BreadcrumbsMessageKey = keyof typeof BREADCRUMBS_MESSAGES_EN;
|
|
12
|
+
/** A (possibly partial) catalog for one locale. */
|
|
13
|
+
export type BreadcrumbsMessages = Record<BreadcrumbsMessageKey, string>;
|
|
14
|
+
/**
|
|
15
|
+
* Builds the `t` prop of `Breadcrumbs` from a message catalog. Unknown or
|
|
16
|
+
* untranslated keys fall back to `fallbackMessages` (English by default), so a
|
|
17
|
+
* catalog may safely be partial and never renders a raw key.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```svelte
|
|
21
|
+
* <script>
|
|
22
|
+
* import { Breadcrumbs, createBreadcrumbsT, BREADCRUMBS_MESSAGES_SK } from "@marianmeres/stuic";
|
|
23
|
+
* const t = createBreadcrumbsT(BREADCRUMBS_MESSAGES_SK);
|
|
24
|
+
* </script>
|
|
25
|
+
*
|
|
26
|
+
* <Breadcrumbs {items} {t} />
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare function createBreadcrumbsT(messages: Partial<BreadcrumbsMessages> | Record<string, string>, fallbackMessages?: Partial<BreadcrumbsMessages> | Record<string, string>): TranslateFn;
|
|
30
|
+
/** The component's built-in English `t`. */
|
|
31
|
+
export declare const t_default: TranslateFn;
|
|
@@ -0,0 +1,40 @@
|
|
|
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 `Breadcrumbs`. Also the fallback of
|
|
5
|
+
* every other bundled locale, so a locale missing a key still renders text.
|
|
6
|
+
*/
|
|
7
|
+
export const BREADCRUMBS_MESSAGES_EN = {
|
|
8
|
+
// singular "Breadcrumb" is the WAI-ARIA APG recommended landmark label
|
|
9
|
+
breadcrumbs: "Breadcrumb",
|
|
10
|
+
show_all: "Show all breadcrumbs",
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Builds the `t` prop of `Breadcrumbs` from a message catalog. Unknown or
|
|
14
|
+
* untranslated keys fall back to `fallbackMessages` (English by default), so a
|
|
15
|
+
* catalog may safely be partial and never renders a raw key.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```svelte
|
|
19
|
+
* <script>
|
|
20
|
+
* import { Breadcrumbs, createBreadcrumbsT, BREADCRUMBS_MESSAGES_SK } from "@marianmeres/stuic";
|
|
21
|
+
* const t = createBreadcrumbsT(BREADCRUMBS_MESSAGES_SK);
|
|
22
|
+
* </script>
|
|
23
|
+
*
|
|
24
|
+
* <Breadcrumbs {items} {t} />
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export function createBreadcrumbsT(messages, fallbackMessages = BREADCRUMBS_MESSAGES_EN) {
|
|
28
|
+
return (k, values = null, fallback = "") => {
|
|
29
|
+
const out = messages[k] ??
|
|
30
|
+
fallbackMessages[k] ??
|
|
31
|
+
(typeof fallback === "string" ? fallback : k);
|
|
32
|
+
return isPlainObject(values)
|
|
33
|
+
? replaceMap(out, values, {
|
|
34
|
+
preSearchKeyTransform: (k) => `{${k}}`,
|
|
35
|
+
})
|
|
36
|
+
: out;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** The component's built-in English `t`. */
|
|
40
|
+
export const t_default = createBreadcrumbsT(BREADCRUMBS_MESSAGES_EN);
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
BREADCRUMBS COMPONENT TOKENS
|
|
3
|
+
Override globally: :root { --stuic-breadcrumbs-gap: 0.5rem; }
|
|
4
|
+
Override locally: <Breadcrumbs style="--stuic-breadcrumbs-font-size: 1rem;">
|
|
5
|
+
============================================================================ */
|
|
6
|
+
|
|
7
|
+
/* prettier-ignore */
|
|
8
|
+
:root {
|
|
9
|
+
--stuic-breadcrumbs-gap: 0.375rem;
|
|
10
|
+
--stuic-breadcrumbs-font-size: var(--text-sm);
|
|
11
|
+
|
|
12
|
+
/* links + plain (non-current) crumbs + inherited by the ellipsis button */
|
|
13
|
+
--stuic-breadcrumbs-text: var(--stuic-color-muted-foreground);
|
|
14
|
+
--stuic-breadcrumbs-text-hover: var(--stuic-color-foreground);
|
|
15
|
+
|
|
16
|
+
/* the current (last) crumb */
|
|
17
|
+
--stuic-breadcrumbs-text-current: var(--stuic-color-foreground);
|
|
18
|
+
--stuic-breadcrumbs-font-weight-current: 500;
|
|
19
|
+
|
|
20
|
+
--stuic-breadcrumbs-separator-text: var(--stuic-color-muted-foreground);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@layer components {
|
|
24
|
+
.stuic-breadcrumbs-list {
|
|
25
|
+
display: flex;
|
|
26
|
+
align-items: center;
|
|
27
|
+
flex-wrap: wrap;
|
|
28
|
+
gap: var(--stuic-breadcrumbs-gap);
|
|
29
|
+
margin: 0;
|
|
30
|
+
padding: 0;
|
|
31
|
+
list-style: none;
|
|
32
|
+
font-size: var(--stuic-breadcrumbs-font-size);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.stuic-breadcrumbs-item {
|
|
36
|
+
display: inline-flex;
|
|
37
|
+
align-items: center;
|
|
38
|
+
gap: var(--stuic-breadcrumbs-gap);
|
|
39
|
+
color: var(--stuic-breadcrumbs-text);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.stuic-breadcrumbs-link {
|
|
43
|
+
color: inherit;
|
|
44
|
+
text-decoration: none;
|
|
45
|
+
transition: color var(--stuic-breadcrumbs-transition, var(--stuic-transition));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.stuic-breadcrumbs-link:hover {
|
|
49
|
+
color: var(--stuic-breadcrumbs-text-hover);
|
|
50
|
+
text-decoration: underline;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.stuic-breadcrumbs-current {
|
|
54
|
+
color: var(--stuic-breadcrumbs-text-current);
|
|
55
|
+
font-weight: var(--stuic-breadcrumbs-font-weight-current);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* current crumb that is also a link keeps its current color on hover */
|
|
59
|
+
.stuic-breadcrumbs-link.stuic-breadcrumbs-current:hover {
|
|
60
|
+
color: var(--stuic-breadcrumbs-text-current);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.stuic-breadcrumbs-separator {
|
|
64
|
+
color: var(--stuic-breadcrumbs-separator-text);
|
|
65
|
+
user-select: none;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.stuic-breadcrumbs-ellipsis {
|
|
69
|
+
appearance: none;
|
|
70
|
+
background: none;
|
|
71
|
+
border: none;
|
|
72
|
+
padding: 0;
|
|
73
|
+
margin: 0;
|
|
74
|
+
font: inherit;
|
|
75
|
+
color: inherit;
|
|
76
|
+
cursor: pointer;
|
|
77
|
+
line-height: 1;
|
|
78
|
+
transition: color var(--stuic-breadcrumbs-transition, var(--stuic-transition));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.stuic-breadcrumbs-ellipsis:hover {
|
|
82
|
+
color: var(--stuic-breadcrumbs-text-hover);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { default as Breadcrumbs, type Props as BreadcrumbsProps, type BreadcrumbItem, } from "./Breadcrumbs.svelte";
|
|
2
|
+
export { breadcrumbsJsonLd, breadcrumbsJsonLdScript, type BreadcrumbsJsonLdOptions, type BreadcrumbListItemJsonLd, type BreadcrumbListJsonLd, } from "./json-ld.js";
|
|
3
|
+
export { createBreadcrumbsT, BREADCRUMBS_MESSAGES_EN, type BreadcrumbsMessageKey, type BreadcrumbsMessages, } from "./i18n.js";
|
|
4
|
+
export { BREADCRUMBS_MESSAGES_SK } from "./i18n-sk.js";
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { default as Breadcrumbs, } from "./Breadcrumbs.svelte";
|
|
2
|
+
export { breadcrumbsJsonLd, breadcrumbsJsonLdScript, } from "./json-ld.js";
|
|
3
|
+
export { createBreadcrumbsT, BREADCRUMBS_MESSAGES_EN, } from "./i18n.js";
|
|
4
|
+
export { BREADCRUMBS_MESSAGES_SK } from "./i18n-sk.js";
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { BreadcrumbItem } from "./Breadcrumbs.svelte";
|
|
2
|
+
/** Options for the JSON-LD helpers. */
|
|
3
|
+
export interface BreadcrumbsJsonLdOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Base URL to resolve relative `href`s against (e.g. `page.url.origin` in
|
|
6
|
+
* SvelteKit). Google recommends absolute URLs in `BreadcrumbList` structured
|
|
7
|
+
* data; without a base, hrefs are emitted as-is.
|
|
8
|
+
*/
|
|
9
|
+
baseUrl?: string | URL;
|
|
10
|
+
}
|
|
11
|
+
/** One `ListItem` of a schema.org `BreadcrumbList`. */
|
|
12
|
+
export interface BreadcrumbListItemJsonLd {
|
|
13
|
+
"@type": "ListItem";
|
|
14
|
+
/** 1-based position in the trail. */
|
|
15
|
+
position: number;
|
|
16
|
+
name: string;
|
|
17
|
+
/** The crumb's URL. Omitted for crumbs without `href` (typically the current page). */
|
|
18
|
+
item?: string;
|
|
19
|
+
}
|
|
20
|
+
/** A schema.org `BreadcrumbList` — the structured data search engines read. */
|
|
21
|
+
export interface BreadcrumbListJsonLd {
|
|
22
|
+
"@context": "https://schema.org";
|
|
23
|
+
"@type": "BreadcrumbList";
|
|
24
|
+
itemListElement: BreadcrumbListItemJsonLd[];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Builds a schema.org `BreadcrumbList` object from `Breadcrumbs` items — the
|
|
28
|
+
* structured data Google et al. use to render breadcrumb trails in search results.
|
|
29
|
+
*
|
|
30
|
+
* Crumbs without `href` (typically the last one, the current page) are emitted
|
|
31
|
+
* without `item`, which is exactly what Google's guidelines prescribe for the
|
|
32
|
+
* final breadcrumb.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* breadcrumbsJsonLd(items, { baseUrl: "https://example.com" });
|
|
37
|
+
* // { "@context": "https://schema.org", "@type": "BreadcrumbList", itemListElement: [...] }
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export declare function breadcrumbsJsonLd(items: BreadcrumbItem[], options?: BreadcrumbsJsonLdOptions): BreadcrumbListJsonLd;
|
|
41
|
+
/**
|
|
42
|
+
* Renders the full `<script type="application/ld+json">…</script>` tag as a string,
|
|
43
|
+
* safe to inject via `{@html}` (angle brackets and ampersands inside the JSON are
|
|
44
|
+
* `\uXXXX`-escaped, so a label can never break out of the script element).
|
|
45
|
+
*
|
|
46
|
+
* In SvelteKit, put it in the head:
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```svelte
|
|
50
|
+
* <svelte:head>
|
|
51
|
+
* {@html breadcrumbsJsonLdScript(items, { baseUrl: page.url.origin })}
|
|
52
|
+
* </svelte:head>
|
|
53
|
+
* ```
|
|
54
|
+
*
|
|
55
|
+
* Or let the component render it inline via its `jsonLd` prop (search engines read
|
|
56
|
+
* JSON-LD anywhere in the document).
|
|
57
|
+
*/
|
|
58
|
+
export declare function breadcrumbsJsonLdScript(items: BreadcrumbItem[], options?: BreadcrumbsJsonLdOptions): string;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds a schema.org `BreadcrumbList` object from `Breadcrumbs` items — the
|
|
3
|
+
* structured data Google et al. use to render breadcrumb trails in search results.
|
|
4
|
+
*
|
|
5
|
+
* Crumbs without `href` (typically the last one, the current page) are emitted
|
|
6
|
+
* without `item`, which is exactly what Google's guidelines prescribe for the
|
|
7
|
+
* final breadcrumb.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* breadcrumbsJsonLd(items, { baseUrl: "https://example.com" });
|
|
12
|
+
* // { "@context": "https://schema.org", "@type": "BreadcrumbList", itemListElement: [...] }
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export function breadcrumbsJsonLd(items, options = {}) {
|
|
16
|
+
const { baseUrl } = options;
|
|
17
|
+
return {
|
|
18
|
+
"@context": "https://schema.org",
|
|
19
|
+
"@type": "BreadcrumbList",
|
|
20
|
+
itemListElement: items.map((item, i) => {
|
|
21
|
+
const out = {
|
|
22
|
+
"@type": "ListItem",
|
|
23
|
+
position: i + 1,
|
|
24
|
+
name: item.label,
|
|
25
|
+
};
|
|
26
|
+
if (item.href) {
|
|
27
|
+
out.item = baseUrl ? new URL(item.href, baseUrl).toString() : item.href;
|
|
28
|
+
}
|
|
29
|
+
return out;
|
|
30
|
+
}),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Renders the full `<script type="application/ld+json">…</script>` tag as a string,
|
|
35
|
+
* safe to inject via `{@html}` (angle brackets and ampersands inside the JSON are
|
|
36
|
+
* `\uXXXX`-escaped, so a label can never break out of the script element).
|
|
37
|
+
*
|
|
38
|
+
* In SvelteKit, put it in the head:
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```svelte
|
|
42
|
+
* <svelte:head>
|
|
43
|
+
* {@html breadcrumbsJsonLdScript(items, { baseUrl: page.url.origin })}
|
|
44
|
+
* </svelte:head>
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* Or let the component render it inline via its `jsonLd` prop (search engines read
|
|
48
|
+
* JSON-LD anywhere in the document).
|
|
49
|
+
*/
|
|
50
|
+
export function breadcrumbsJsonLdScript(items, options = {}) {
|
|
51
|
+
const json = JSON.stringify(breadcrumbsJsonLd(items, options))
|
|
52
|
+
.replaceAll("&", "\\u0026")
|
|
53
|
+
.replaceAll("<", "\\u003c")
|
|
54
|
+
.replaceAll(">", "\\u003e");
|
|
55
|
+
return `<script type="application/ld+json">${json}</` + `script>`;
|
|
56
|
+
}
|
|
@@ -2,10 +2,21 @@
|
|
|
2
2
|
import type { Snippet } from "svelte";
|
|
3
3
|
import { twMerge } from "../../utils/tw-merge.js";
|
|
4
4
|
import { tooltip } from "../../actions/index.js";
|
|
5
|
+
import { autoHeight } from "../../attachments/index.js";
|
|
6
|
+
import { iconChevronDown } from "../../icons/index.js";
|
|
5
7
|
import { isPlainObject } from "../../utils/is-plain-object.js";
|
|
6
8
|
import { replaceMap } from "../../utils/replace-map.js";
|
|
7
9
|
import type { TranslateFn } from "../../types.js";
|
|
8
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Vertical alignment of the toggle button relative to the content:
|
|
13
|
+
* - `"bottom"` — always next to the last (clamped) line. Default, historical behavior.
|
|
14
|
+
* - `"top"` — always at the top of the content, in both states.
|
|
15
|
+
* - `"top-when-expanded"` — bottom while collapsed, top once expanded, so the toggle
|
|
16
|
+
* does not run away down a long expanded block.
|
|
17
|
+
*/
|
|
18
|
+
export type CollapsibleToggleAlign = "bottom" | "top" | "top-when-expanded";
|
|
19
|
+
|
|
9
20
|
export interface Props {
|
|
10
21
|
/** Content to display */
|
|
11
22
|
children: Snippet;
|
|
@@ -13,10 +24,29 @@
|
|
|
13
24
|
lines?: number;
|
|
14
25
|
/** Expanded state (bindable) */
|
|
15
26
|
expanded?: boolean;
|
|
16
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Collapsed indicator character. Omit both indicators (the default) to get the
|
|
29
|
+
* rotating chevron; providing either one switches to plain text indicators
|
|
30
|
+
* (the omitted one falls back to the legacy "↓"/"↑" arrow).
|
|
31
|
+
*/
|
|
17
32
|
collapsedIndicator?: string;
|
|
18
|
-
/**
|
|
33
|
+
/**
|
|
34
|
+
* Expanded indicator character. Omit both indicators (the default) to get the
|
|
35
|
+
* rotating chevron; providing either one switches to plain text indicators
|
|
36
|
+
* (the omitted one falls back to the legacy "↓"/"↑" arrow).
|
|
37
|
+
*/
|
|
19
38
|
expandedIndicator?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Opt-in: smoothly animate the height between the collapsed and expanded state
|
|
41
|
+
* instead of snapping. Respects `prefers-reduced-motion` (snaps when reduce is
|
|
42
|
+
* set). Default: false.
|
|
43
|
+
*/
|
|
44
|
+
animate?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Opt-in: where to vertically align the toggle button. Default: `"bottom"`
|
|
47
|
+
* (always next to the last clamped line — the historical behavior).
|
|
48
|
+
*/
|
|
49
|
+
toggleAlign?: CollapsibleToggleAlign;
|
|
20
50
|
/** Container class */
|
|
21
51
|
class?: string;
|
|
22
52
|
/** Content wrapper class */
|
|
@@ -53,8 +83,10 @@
|
|
|
53
83
|
children,
|
|
54
84
|
lines = 1,
|
|
55
85
|
expanded = $bindable(false),
|
|
56
|
-
collapsedIndicator
|
|
57
|
-
expandedIndicator
|
|
86
|
+
collapsedIndicator,
|
|
87
|
+
expandedIndicator,
|
|
88
|
+
animate = false,
|
|
89
|
+
toggleAlign = "bottom",
|
|
58
90
|
class: classProp,
|
|
59
91
|
classContent,
|
|
60
92
|
classToggle,
|
|
@@ -80,21 +112,51 @@
|
|
|
80
112
|
const l = Math.abs(lines);
|
|
81
113
|
return l > 10 ? 10 : l;
|
|
82
114
|
});
|
|
115
|
+
|
|
116
|
+
// The rotating chevron is the default indicator. Passing either custom indicator
|
|
117
|
+
// opts back into plain text; the unspecified one keeps its historical arrow.
|
|
118
|
+
let useChevron = $derived(
|
|
119
|
+
collapsedIndicator === undefined && expandedIndicator === undefined
|
|
120
|
+
);
|
|
121
|
+
let textIndicator = $derived(
|
|
122
|
+
expanded ? (expandedIndicator ?? "↑") : (collapsedIndicator ?? "↓")
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
let label = $derived(expanded ? t("less") : t("more"));
|
|
126
|
+
|
|
127
|
+
let _align = $derived(
|
|
128
|
+
toggleAlign === "top" || (toggleAlign === "top-when-expanded" && expanded)
|
|
129
|
+
? "items-start"
|
|
130
|
+
: "items-end"
|
|
131
|
+
);
|
|
83
132
|
</script>
|
|
84
133
|
|
|
134
|
+
{#snippet content()}
|
|
135
|
+
<div
|
|
136
|
+
bind:this={contentEl}
|
|
137
|
+
class={twMerge("flex-1", !expanded && `line-clamp-${_lines}`, classContent)}
|
|
138
|
+
>
|
|
139
|
+
{@render children()}
|
|
140
|
+
</div>
|
|
141
|
+
{/snippet}
|
|
142
|
+
|
|
85
143
|
<div
|
|
86
144
|
bind:this={el}
|
|
87
145
|
bind:clientWidth={containerWidth}
|
|
88
146
|
class={twMerge("stuic-collapsible", classProp)}
|
|
89
147
|
{style}
|
|
90
148
|
>
|
|
91
|
-
<div class="flex
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
{@
|
|
97
|
-
|
|
149
|
+
<div class={twMerge("flex", _align)}>
|
|
150
|
+
{#if animate}
|
|
151
|
+
<!-- Height-animated viewport: `autoHeight` drives this box to the natural
|
|
152
|
+
height of the (un)clamped content and clips while it transits. Only
|
|
153
|
+
rendered when the opt-in is active, so the default output is unchanged. -->
|
|
154
|
+
<div class="stuic-collapsible-viewport flex-1" {@attach autoHeight}>
|
|
155
|
+
{@render content()}
|
|
156
|
+
</div>
|
|
157
|
+
{:else}
|
|
158
|
+
{@render content()}
|
|
159
|
+
{/if}
|
|
98
160
|
{#if needsCollapse}
|
|
99
161
|
<button
|
|
100
162
|
type="button"
|
|
@@ -102,12 +164,19 @@
|
|
|
102
164
|
"stuic-collapsible-toggle cursor-pointer -my-1 -mr-2",
|
|
103
165
|
classToggle
|
|
104
166
|
)}
|
|
167
|
+
aria-expanded={expanded}
|
|
168
|
+
aria-label={label}
|
|
105
169
|
onclick={() => (expanded = !expanded)}
|
|
106
|
-
use:tooltip={() => ({
|
|
107
|
-
content: expanded ? t("less") : t("more"),
|
|
108
|
-
})}
|
|
170
|
+
use:tooltip={() => ({ content: label })}
|
|
109
171
|
>
|
|
110
|
-
{
|
|
172
|
+
{#if useChevron}
|
|
173
|
+
{@html iconChevronDown({
|
|
174
|
+
class: "stuic-collapsible-chevron",
|
|
175
|
+
"aria-hidden": "true",
|
|
176
|
+
})}
|
|
177
|
+
{:else}
|
|
178
|
+
{textIndicator}
|
|
179
|
+
{/if}
|
|
111
180
|
</button>
|
|
112
181
|
{/if}
|
|
113
182
|
</div>
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import type { Snippet } from "svelte";
|
|
2
2
|
import type { TranslateFn } from "../../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Vertical alignment of the toggle button relative to the content:
|
|
5
|
+
* - `"bottom"` — always next to the last (clamped) line. Default, historical behavior.
|
|
6
|
+
* - `"top"` — always at the top of the content, in both states.
|
|
7
|
+
* - `"top-when-expanded"` — bottom while collapsed, top once expanded, so the toggle
|
|
8
|
+
* does not run away down a long expanded block.
|
|
9
|
+
*/
|
|
10
|
+
export type CollapsibleToggleAlign = "bottom" | "top" | "top-when-expanded";
|
|
3
11
|
export interface Props {
|
|
4
12
|
/** Content to display */
|
|
5
13
|
children: Snippet;
|
|
@@ -7,10 +15,29 @@ export interface Props {
|
|
|
7
15
|
lines?: number;
|
|
8
16
|
/** Expanded state (bindable) */
|
|
9
17
|
expanded?: boolean;
|
|
10
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* Collapsed indicator character. Omit both indicators (the default) to get the
|
|
20
|
+
* rotating chevron; providing either one switches to plain text indicators
|
|
21
|
+
* (the omitted one falls back to the legacy "↓"/"↑" arrow).
|
|
22
|
+
*/
|
|
11
23
|
collapsedIndicator?: string;
|
|
12
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Expanded indicator character. Omit both indicators (the default) to get the
|
|
26
|
+
* rotating chevron; providing either one switches to plain text indicators
|
|
27
|
+
* (the omitted one falls back to the legacy "↓"/"↑" arrow).
|
|
28
|
+
*/
|
|
13
29
|
expandedIndicator?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Opt-in: smoothly animate the height between the collapsed and expanded state
|
|
32
|
+
* instead of snapping. Respects `prefers-reduced-motion` (snaps when reduce is
|
|
33
|
+
* set). Default: false.
|
|
34
|
+
*/
|
|
35
|
+
animate?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Opt-in: where to vertically align the toggle button. Default: `"bottom"`
|
|
38
|
+
* (always next to the last clamped line — the historical behavior).
|
|
39
|
+
*/
|
|
40
|
+
toggleAlign?: CollapsibleToggleAlign;
|
|
14
41
|
/** Container class */
|
|
15
42
|
class?: string;
|
|
16
43
|
/** Content wrapper class */
|
|
@@ -2,21 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
A component that truncates content to a specified number of lines with an expand/collapse toggle. Automatically detects if truncation is needed and only shows the toggle when content overflows.
|
|
4
4
|
|
|
5
|
+
The toggle renders a **rotating chevron** by default (points down when collapsed, rotates 180° to point up when expanded). Height changes snap by default; pass `animate` to transition them instead.
|
|
6
|
+
|
|
5
7
|
## Props
|
|
6
8
|
|
|
7
|
-
| Prop | Type | Default
|
|
8
|
-
| -------------------- | ---------------- |
|
|
9
|
-
| `children` | `Snippet` | -
|
|
10
|
-
| `lines` | `number` | `1`
|
|
11
|
-
| `expanded` | `boolean` | `false`
|
|
12
|
-
| `collapsedIndicator` | `string` |
|
|
13
|
-
| `expandedIndicator` | `string` |
|
|
14
|
-
| `
|
|
15
|
-
| `
|
|
16
|
-
| `
|
|
17
|
-
| `
|
|
18
|
-
| `
|
|
19
|
-
| `
|
|
9
|
+
| Prop | Type | Default | Description |
|
|
10
|
+
| -------------------- | ---------------- | ---------- | ---------------------------------------------------------------------------------------------------- |
|
|
11
|
+
| `children` | `Snippet` | - | Content to display |
|
|
12
|
+
| `lines` | `number` | `1` | Number of lines to show when collapsed |
|
|
13
|
+
| `expanded` | `boolean` | `false` | Expanded state (bindable) |
|
|
14
|
+
| `collapsedIndicator` | `string` | - | Text shown when collapsed. Opts **out** of the chevron (see below) |
|
|
15
|
+
| `expandedIndicator` | `string` | - | Text shown when expanded. Opts **out** of the chevron (see below) |
|
|
16
|
+
| `animate` | `boolean` | `false` | Opt-in: smoothly animate the height between collapsed/expanded. Respects `prefers-reduced-motion` |
|
|
17
|
+
| `toggleAlign` | `ToggleAlign` | `"bottom"` | Vertical alignment of the toggle button — `"bottom"` \| `"top"` \| `"top-when-expanded"` (see below) |
|
|
18
|
+
| `class` | `string` | - | Container element class |
|
|
19
|
+
| `classContent` | `string` | - | Content wrapper class |
|
|
20
|
+
| `classToggle` | `string` | - | Toggle button class |
|
|
21
|
+
| `style` | `string` | - | Inline styles (for CSS variable overrides) |
|
|
22
|
+
| `el` | `HTMLDivElement` | - | Bind reference to container element |
|
|
23
|
+
| `t` | `TranslateFn` | - | Optional translate function |
|
|
20
24
|
|
|
21
25
|
## Usage
|
|
22
26
|
|
|
@@ -42,9 +46,53 @@ A component that truncates content to a specified number of lines with an expand
|
|
|
42
46
|
</Collapsible>
|
|
43
47
|
```
|
|
44
48
|
|
|
45
|
-
###
|
|
49
|
+
### Animated Height (opt-in)
|
|
50
|
+
|
|
51
|
+
```svelte
|
|
52
|
+
<Collapsible lines={2} animate>
|
|
53
|
+
Expanding and collapsing transitions the height instead of snapping.
|
|
54
|
+
</Collapsible>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Nothing animates unless you ask for it. With `animate`, the content is wrapped in a
|
|
58
|
+
`.stuic-collapsible-viewport` element whose height is driven to the natural height of the
|
|
59
|
+
(un)clamped content and transitioned; tune the duration with
|
|
60
|
+
`--stuic-collapsible-transition`. Under `prefers-reduced-motion: reduce` the height snaps,
|
|
61
|
+
exactly as it does without the prop.
|
|
62
|
+
|
|
63
|
+
### Toggle Alignment
|
|
64
|
+
|
|
65
|
+
`toggleAlign` (`CollapsibleToggleAlign`) controls where the toggle button sits vertically:
|
|
66
|
+
|
|
67
|
+
| Value | Collapsed | Expanded |
|
|
68
|
+
| --------------------- | --------- | -------- |
|
|
69
|
+
| `"bottom"` (default) | bottom | bottom |
|
|
70
|
+
| `"top"` | top | top |
|
|
71
|
+
| `"top-when-expanded"` | bottom | top |
|
|
46
72
|
|
|
47
73
|
```svelte
|
|
74
|
+
<!-- default: always next to the last clamped line -->
|
|
75
|
+
<Collapsible lines={2}>Content.</Collapsible>
|
|
76
|
+
|
|
77
|
+
<!-- always at the top, e.g. next to a heading-like first line -->
|
|
78
|
+
<Collapsible lines={2} toggleAlign="top">Content.</Collapsible>
|
|
79
|
+
|
|
80
|
+
<!-- bottom while collapsed, top once expanded — so the toggle does not run away
|
|
81
|
+
down a long expanded paragraph -->
|
|
82
|
+
<Collapsible lines={2} toggleAlign="top-when-expanded">Content.</Collapsible>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Indicators
|
|
86
|
+
|
|
87
|
+
The default indicator is a chevron that rotates 180° between the two states. Passing
|
|
88
|
+
**either** `collapsedIndicator` or `expandedIndicator` switches to plain text indicators;
|
|
89
|
+
the one you omit falls back to its legacy arrow (`↓` / `↑`).
|
|
90
|
+
|
|
91
|
+
```svelte
|
|
92
|
+
<!-- rotating chevron (default) -->
|
|
93
|
+
<Collapsible>Content.</Collapsible>
|
|
94
|
+
|
|
95
|
+
<!-- plain text indicators -->
|
|
48
96
|
<Collapsible collapsedIndicator="▼" expandedIndicator="▲">
|
|
49
97
|
Content with custom expand/collapse indicators.
|
|
50
98
|
</Collapsible>
|
|
@@ -89,15 +137,17 @@ A component that truncates content to a specified number of lines with an expand
|
|
|
89
137
|
|
|
90
138
|
Override to customize appearance:
|
|
91
139
|
|
|
92
|
-
| Variable | Default | Description
|
|
93
|
-
| ------------------------------------------ | -------------------------- |
|
|
94
|
-
| `--stuic-collapsible-toggle-opacity` | `0.7` | Toggle button opacity
|
|
95
|
-
| `--stuic-collapsible-toggle-opacity-hover` | `1` | Hover opacity
|
|
96
|
-
| `--stuic-collapsible-toggle-padding-x` | `calc(var(--spacing) * 2)` | Horizontal padding
|
|
97
|
-
| `--stuic-collapsible-toggle-padding-y` | `0.25rem` | Vertical padding
|
|
98
|
-
| `--stuic-collapsible-
|
|
99
|
-
| `--stuic-collapsible-
|
|
100
|
-
| `--stuic-collapsible-
|
|
140
|
+
| Variable | Default | Description |
|
|
141
|
+
| ------------------------------------------ | -------------------------- | ----------------------------------------------------------------- |
|
|
142
|
+
| `--stuic-collapsible-toggle-opacity` | `0.7` | Toggle button opacity |
|
|
143
|
+
| `--stuic-collapsible-toggle-opacity-hover` | `1` | Hover opacity |
|
|
144
|
+
| `--stuic-collapsible-toggle-padding-x` | `calc(var(--spacing) * 2)` | Horizontal padding |
|
|
145
|
+
| `--stuic-collapsible-toggle-padding-y` | `0.25rem` | Vertical padding |
|
|
146
|
+
| `--stuic-collapsible-chevron-size` | `16px` | Chevron width/height |
|
|
147
|
+
| `--stuic-collapsible-chevron-color` | `currentColor` | Chevron color |
|
|
148
|
+
| `--stuic-collapsible-transition` | `150ms` | Transition duration (opacity, chevron rotation, `animate` height) |
|
|
149
|
+
| `--stuic-collapsible-ring-width` | `2px` | Focus ring width |
|
|
150
|
+
| `--stuic-collapsible-ring-color` | `--stuic-color-ring` | Focus ring color |
|
|
101
151
|
|
|
102
152
|
### Global Override
|
|
103
153
|
|
|
@@ -107,3 +157,12 @@ Override to customize appearance:
|
|
|
107
157
|
--stuic-collapsible-toggle-opacity-hover: 0.8;
|
|
108
158
|
}
|
|
109
159
|
```
|
|
160
|
+
|
|
161
|
+
## Accessibility
|
|
162
|
+
|
|
163
|
+
- The toggle is a real `<button>` carrying `aria-expanded`, which is also the hook the CSS
|
|
164
|
+
uses to rotate the chevron.
|
|
165
|
+
- The chevron SVG is `aria-hidden`; the button's accessible name comes from `aria-label`
|
|
166
|
+
(`t("more")` / `t("less")`), so it is announced even in chevron mode.
|
|
167
|
+
- Motion (chevron rotation and the opt-in height transition) is gated behind
|
|
168
|
+
`prefers-reduced-motion: no-preference`.
|
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
--stuic-collapsible-toggle-padding-x: calc(var(--spacing) * 2);
|
|
12
12
|
--stuic-collapsible-toggle-padding-y: calc(var(--spacing) * 1);
|
|
13
13
|
|
|
14
|
+
/* Chevron indicator (default) */
|
|
15
|
+
--stuic-collapsible-chevron-size: 16px;
|
|
16
|
+
--stuic-collapsible-chevron-color: currentColor;
|
|
17
|
+
|
|
14
18
|
/* Focus ring (uses theme token) */
|
|
15
19
|
--stuic-collapsible-ring-width: 2px;
|
|
16
20
|
--stuic-collapsible-ring-color: var(--stuic-color-ring);
|
|
@@ -36,4 +40,38 @@
|
|
|
36
40
|
outline: var(--stuic-collapsible-ring-width) solid var(--stuic-collapsible-ring-color);
|
|
37
41
|
outline-offset: 2px;
|
|
38
42
|
}
|
|
43
|
+
|
|
44
|
+
/* ============================================================================
|
|
45
|
+
CHEVRON INDICATOR (default)
|
|
46
|
+
============================================================================ */
|
|
47
|
+
|
|
48
|
+
.stuic-collapsible-chevron {
|
|
49
|
+
display: block;
|
|
50
|
+
flex-shrink: 0;
|
|
51
|
+
width: var(--stuic-collapsible-chevron-size);
|
|
52
|
+
height: var(--stuic-collapsible-chevron-size);
|
|
53
|
+
color: var(--stuic-collapsible-chevron-color);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.stuic-collapsible-toggle[aria-expanded="true"] .stuic-collapsible-chevron {
|
|
57
|
+
transform: rotate(180deg);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* ============================================================================
|
|
61
|
+
HEIGHT-ANIMATED VIEWPORT (opt-in via the `animate` prop)
|
|
62
|
+
|
|
63
|
+
The `autoHeight` attachment owns this element's inline `height` (and the
|
|
64
|
+
`overflow: clip` it applies *only* while that height transitions), so the
|
|
65
|
+
transition below is the entire opt-in. Without it the height simply snaps.
|
|
66
|
+
============================================================================ */
|
|
67
|
+
|
|
68
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
69
|
+
.stuic-collapsible-viewport {
|
|
70
|
+
transition: height var(--stuic-collapsible-transition, var(--stuic-transition));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.stuic-collapsible-chevron {
|
|
74
|
+
transition: transform var(--stuic-collapsible-transition, var(--stuic-transition));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
39
77
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { default as Collapsible, type Props as CollapsibleProps, } from "./Collapsible.svelte";
|
|
1
|
+
export { default as Collapsible, type Props as CollapsibleProps, type CollapsibleToggleAlign, } from "./Collapsible.svelte";
|