@sveltia/ui 0.73.1 → 0.74.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.
|
@@ -1,21 +1,33 @@
|
|
|
1
1
|
<!--
|
|
2
2
|
@component
|
|
3
|
-
The interactive version of `<TableBody>`.
|
|
3
|
+
The interactive version of `<TableBody>`. A labelled row group can be made collapsible, in which
|
|
4
|
+
case the caption doubles as an expander button that shows or hides the rows.
|
|
4
5
|
@see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tbody
|
|
5
6
|
@see https://w3c.github.io/aria/#rowgroup
|
|
7
|
+
@see https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/
|
|
6
8
|
-->
|
|
7
9
|
<script>
|
|
8
|
-
import { _ } from '@sveltia/i18n';
|
|
10
|
+
import { _, isRTL } from '@sveltia/i18n';
|
|
11
|
+
import Button from '../button/button.svelte';
|
|
12
|
+
import Icon from '../icon/icon.svelte';
|
|
9
13
|
|
|
10
14
|
/**
|
|
11
15
|
* @import { Snippet } from 'svelte';
|
|
16
|
+
* @import { Attachment } from 'svelte/attachments';
|
|
12
17
|
*/
|
|
13
18
|
|
|
14
19
|
/**
|
|
15
20
|
* @typedef {object} Props
|
|
16
21
|
* @property {string} [class] The `class` attribute on the wrapper element.
|
|
17
22
|
* @property {string} [label] Display label for the row group.
|
|
23
|
+
* @property {boolean} [collapsible] Whether the rows can be hidden with an expander in the
|
|
24
|
+
* caption. Requires `label`.
|
|
25
|
+
* @property {boolean} [expanded] Whether the rows are shown. Only applies when `collapsible`. An
|
|
26
|
+
* alias of the `aria-expanded` attribute on the expander button.
|
|
18
27
|
* @property {Snippet} [children] Primary slot content.
|
|
28
|
+
* @property {Snippet} [chevronIcon] Chevron slot content for the expander.
|
|
29
|
+
* @property {(event: CustomEvent) => void} [onChange] Custom `Change` event handler, called with
|
|
30
|
+
* the new `expanded` state in `detail` when the expander is toggled.
|
|
19
31
|
*/
|
|
20
32
|
|
|
21
33
|
/**
|
|
@@ -23,14 +35,52 @@
|
|
|
23
35
|
*/
|
|
24
36
|
let {
|
|
25
37
|
/* eslint-disable prefer-const */
|
|
38
|
+
expanded = $bindable(true),
|
|
26
39
|
class: className,
|
|
27
40
|
label = '',
|
|
41
|
+
collapsible = false,
|
|
28
42
|
children,
|
|
43
|
+
chevronIcon,
|
|
44
|
+
onChange,
|
|
29
45
|
...restProps
|
|
30
46
|
/* eslint-enable prefer-const */
|
|
31
47
|
} = $props();
|
|
32
48
|
|
|
33
49
|
const id = $props.id();
|
|
50
|
+
const hasExpander = $derived(collapsible && !!label);
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Toggle the `expanded` state and notify the consumer.
|
|
54
|
+
*/
|
|
55
|
+
const toggle = () => {
|
|
56
|
+
expanded = !expanded;
|
|
57
|
+
onChange?.(new CustomEvent('change', { detail: { expanded } }));
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Toggle the expander when the caption row itself is clicked. The `Grid` treats the caption as a
|
|
62
|
+
* row: the arrow keys land on it, and Enter clicks it, so this is how a keyboard user who has
|
|
63
|
+
* moved to the caption toggles the group. A click bubbling up from the button is left alone, as
|
|
64
|
+
* the button has toggled the group already.
|
|
65
|
+
* @type {Attachment}
|
|
66
|
+
*/
|
|
67
|
+
const handleCaptionClick = (element) => {
|
|
68
|
+
/**
|
|
69
|
+
* Handle the `click` event on the caption row.
|
|
70
|
+
* @param {Event} event `click` event.
|
|
71
|
+
*/
|
|
72
|
+
const listener = (event) => {
|
|
73
|
+
if (hasExpander && event.target === element) {
|
|
74
|
+
toggle();
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
element.addEventListener('click', listener);
|
|
79
|
+
|
|
80
|
+
return () => {
|
|
81
|
+
element.removeEventListener('click', listener);
|
|
82
|
+
};
|
|
83
|
+
};
|
|
34
84
|
</script>
|
|
35
85
|
|
|
36
86
|
<div
|
|
@@ -41,14 +91,34 @@
|
|
|
41
91
|
aria-roledescription={_('_sui.role_descriptions.grid_body')}
|
|
42
92
|
>
|
|
43
93
|
{#if label}
|
|
44
|
-
<div role="row" class="row-group-caption">
|
|
94
|
+
<div role="row" class="row-group-caption" {@attach handleCaptionClick}>
|
|
45
95
|
<!-- We need `colspan` here but cannot place `<th>` under `<div>`, so use a hack -->
|
|
46
96
|
<svelte:element this={"th"} role="columnheader" id="{id}-label" colspan="9999">
|
|
47
|
-
{
|
|
97
|
+
{#if hasExpander}
|
|
98
|
+
<!--
|
|
99
|
+
The button is not a tab stop: the grid has a single one, and a keyboard user reaches
|
|
100
|
+
the caption row with the arrow keys and toggles it with Enter
|
|
101
|
+
-->
|
|
102
|
+
<Button class="expander" {label} tabindex="-1" aria-expanded={expanded} onclick={toggle}>
|
|
103
|
+
{#snippet startIcon()}
|
|
104
|
+
<span role="none" class="chevron">
|
|
105
|
+
{#if chevronIcon}
|
|
106
|
+
{@render chevronIcon()}
|
|
107
|
+
{:else}
|
|
108
|
+
<Icon name={isRTL() ? 'chevron_left' : 'chevron_right'} />
|
|
109
|
+
{/if}
|
|
110
|
+
</span>
|
|
111
|
+
{/snippet}
|
|
112
|
+
</Button>
|
|
113
|
+
{:else}
|
|
114
|
+
{label}
|
|
115
|
+
{/if}
|
|
48
116
|
</svelte:element>
|
|
49
117
|
</div>
|
|
50
118
|
{/if}
|
|
51
|
-
{
|
|
119
|
+
{#if !hasExpander || expanded}
|
|
120
|
+
{@render children?.()}
|
|
121
|
+
{/if}
|
|
52
122
|
</div>
|
|
53
123
|
|
|
54
124
|
<style>[role=rowgroup] {
|
|
@@ -66,4 +136,32 @@
|
|
|
66
136
|
background-color: var(--sui-secondary-background-color);
|
|
67
137
|
font-size: var(--sui-font-size-default);
|
|
68
138
|
text-align: start;
|
|
139
|
+
}
|
|
140
|
+
[role=columnheader]:has(> :global(.expander)) {
|
|
141
|
+
padding: 0;
|
|
142
|
+
}
|
|
143
|
+
[role=columnheader] :global(.expander) {
|
|
144
|
+
justify-content: flex-start;
|
|
145
|
+
margin: 0;
|
|
146
|
+
border-radius: 0;
|
|
147
|
+
padding: 4px 8px 4px 4px;
|
|
148
|
+
width: 100%;
|
|
149
|
+
height: auto;
|
|
150
|
+
color: inherit;
|
|
151
|
+
font-size: inherit;
|
|
152
|
+
text-align: start;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.chevron {
|
|
156
|
+
flex: none;
|
|
157
|
+
display: flex;
|
|
158
|
+
align-items: center;
|
|
159
|
+
justify-content: center;
|
|
160
|
+
transition: transform 200ms;
|
|
161
|
+
}
|
|
162
|
+
:global(.expander[aria-expanded='true']) > .chevron:dir(ltr) {
|
|
163
|
+
transform: rotate(90deg);
|
|
164
|
+
}
|
|
165
|
+
:global(.expander[aria-expanded='true']) > .chevron:dir(rtl) {
|
|
166
|
+
transform: rotate(-90deg);
|
|
69
167
|
}</style>
|
|
@@ -4,9 +4,11 @@ type GridBody = {
|
|
|
4
4
|
$set?(props: Partial<Props & Record<string, any>>): void;
|
|
5
5
|
};
|
|
6
6
|
/**
|
|
7
|
-
* The interactive version of `<TableBody>`.
|
|
7
|
+
* The interactive version of `<TableBody>`. A labelled row group can be made collapsible, in which
|
|
8
|
+
* case the caption doubles as an expander button that shows or hides the rows.
|
|
8
9
|
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tbody
|
|
9
10
|
* @see https://w3c.github.io/aria/#rowgroup
|
|
11
|
+
* @see https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/
|
|
10
12
|
*/
|
|
11
13
|
declare const GridBody: import("svelte").Component<{
|
|
12
14
|
/**
|
|
@@ -17,11 +19,30 @@ declare const GridBody: import("svelte").Component<{
|
|
|
17
19
|
* Display label for the row group.
|
|
18
20
|
*/
|
|
19
21
|
label?: string | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* Whether the rows can be hidden with an expander in the
|
|
24
|
+
* caption. Requires `label`.
|
|
25
|
+
*/
|
|
26
|
+
collapsible?: boolean | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* Whether the rows are shown. Only applies when `collapsible`. An
|
|
29
|
+
* alias of the `aria-expanded` attribute on the expander button.
|
|
30
|
+
*/
|
|
31
|
+
expanded?: boolean | undefined;
|
|
20
32
|
/**
|
|
21
33
|
* Primary slot content.
|
|
22
34
|
*/
|
|
23
35
|
children?: Snippet<[]> | undefined;
|
|
24
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Chevron slot content for the expander.
|
|
38
|
+
*/
|
|
39
|
+
chevronIcon?: Snippet<[]> | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* Custom `Change` event handler, called with
|
|
42
|
+
* the new `expanded` state in `detail` when the expander is toggled.
|
|
43
|
+
*/
|
|
44
|
+
onChange?: ((event: CustomEvent) => void) | undefined;
|
|
45
|
+
} & Record<string, any>, {}, "expanded">;
|
|
25
46
|
type Props = {
|
|
26
47
|
/**
|
|
27
48
|
* The `class` attribute on the wrapper element.
|
|
@@ -31,9 +52,28 @@ type Props = {
|
|
|
31
52
|
* Display label for the row group.
|
|
32
53
|
*/
|
|
33
54
|
label?: string | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* Whether the rows can be hidden with an expander in the
|
|
57
|
+
* caption. Requires `label`.
|
|
58
|
+
*/
|
|
59
|
+
collapsible?: boolean | undefined;
|
|
60
|
+
/**
|
|
61
|
+
* Whether the rows are shown. Only applies when `collapsible`. An
|
|
62
|
+
* alias of the `aria-expanded` attribute on the expander button.
|
|
63
|
+
*/
|
|
64
|
+
expanded?: boolean | undefined;
|
|
34
65
|
/**
|
|
35
66
|
* Primary slot content.
|
|
36
67
|
*/
|
|
37
68
|
children?: Snippet<[]> | undefined;
|
|
69
|
+
/**
|
|
70
|
+
* Chevron slot content for the expander.
|
|
71
|
+
*/
|
|
72
|
+
chevronIcon?: Snippet<[]> | undefined;
|
|
73
|
+
/**
|
|
74
|
+
* Custom `Change` event handler, called with
|
|
75
|
+
* the new `expanded` state in `detail` when the expander is toggled.
|
|
76
|
+
*/
|
|
77
|
+
onChange?: ((event: CustomEvent) => void) | undefined;
|
|
38
78
|
};
|
|
39
79
|
import type { Snippet } from 'svelte';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sveltia/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.74.0",
|
|
4
4
|
"description": "A collection of Svelte components and utilities for building user interfaces.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -57,9 +57,9 @@
|
|
|
57
57
|
"@sveltejs/kit": "^2.70.3",
|
|
58
58
|
"@sveltejs/package": "^2.5.8",
|
|
59
59
|
"@sveltejs/vite-plugin-svelte": "^7.3.0",
|
|
60
|
-
"@vitest/browser-playwright": "^5.0.
|
|
61
|
-
"@vitest/coverage-v8": "^5.0.
|
|
62
|
-
"cspell": "^10.3.
|
|
60
|
+
"@vitest/browser-playwright": "^5.0.1",
|
|
61
|
+
"@vitest/coverage-v8": "^5.0.1",
|
|
62
|
+
"cspell": "^10.3.2",
|
|
63
63
|
"eslint": "^9.39.5",
|
|
64
64
|
"eslint-config-airbnb-extended": "^3.2.0",
|
|
65
65
|
"eslint-config-prettier": "^10.1.8",
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"svelte-preprocess": "^6.0.5",
|
|
87
87
|
"tslib": "^2.8.1",
|
|
88
88
|
"vite": "^8.3.0",
|
|
89
|
-
"vitest": "^5.0.
|
|
89
|
+
"vitest": "^5.0.1",
|
|
90
90
|
"vitest-browser-svelte": "^3.1.0"
|
|
91
91
|
},
|
|
92
92
|
"peerDependencies": {
|