@dorsk/tsumikit 0.57.0 → 0.58.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/README.md +125 -10
- package/dist/avatar.d.ts +6 -0
- package/dist/avatar.js +17 -0
- package/dist/components/atoms/Avatar.svelte +140 -0
- package/dist/components/atoms/Avatar.svelte.d.ts +25 -0
- package/dist/components/atoms/Badge.svelte +30 -8
- package/dist/components/atoms/Badge.svelte.d.ts +7 -0
- package/dist/components/atoms/Button.svelte +72 -1
- package/dist/components/atoms/Button.svelte.d.ts +2 -0
- package/dist/components/atoms/Input.svelte +22 -4
- package/dist/components/atoms/Swatch.svelte +114 -0
- package/dist/components/atoms/Swatch.svelte.d.ts +23 -0
- package/dist/components/atoms/Textarea.svelte +33 -6
- package/dist/components/molecules/Accordion.svelte +80 -62
- package/dist/components/molecules/Accordion.svelte.d.ts +15 -2
- package/dist/components/molecules/Combobox.svelte +299 -0
- package/dist/components/molecules/Combobox.svelte.d.ts +108 -0
- package/dist/components/molecules/Composer.svelte +32 -29
- package/dist/components/molecules/Composer.svelte.d.ts +2 -0
- package/dist/components/molecules/Disclosure.svelte +155 -0
- package/dist/components/molecules/Disclosure.svelte.d.ts +26 -0
- package/dist/components/molecules/IconButton.svelte +8 -0
- package/dist/components/molecules/IconButton.svelte.d.ts +4 -0
- package/dist/components/molecules/InputGroup.svelte +159 -0
- package/dist/components/molecules/InputGroup.svelte.d.ts +23 -0
- package/dist/components/molecules/Menu.svelte +26 -4
- package/dist/components/molecules/Menu.svelte.d.ts +9 -1
- package/dist/components/molecules/OptionButton.svelte +42 -1
- package/dist/components/molecules/OptionButton.svelte.d.ts +8 -1
- package/dist/components/molecules/Popover.svelte +49 -1
- package/dist/components/molecules/Popover.svelte.d.ts +4 -0
- package/dist/components/molecules/RadioGroup.svelte +59 -7
- package/dist/components/molecules/RadioGroup.svelte.d.ts +4 -0
- package/dist/components/molecules/SplitButton.svelte +181 -0
- package/dist/components/molecules/SplitButton.svelte.d.ts +41 -0
- package/dist/components/molecules/Tabs.svelte +155 -27
- package/dist/components/molecules/Tabs.svelte.d.ts +16 -1
- package/dist/components/molecules/combobox-keyboard.d.ts +100 -0
- package/dist/components/molecules/combobox-keyboard.js +170 -0
- package/dist/components/molecules/menu-item.d.ts +11 -0
- package/dist/components/molecules/menu-item.js +9 -0
- package/dist/count.d.ts +2 -0
- package/dist/count.js +7 -0
- package/dist/floating.d.ts +4 -0
- package/dist/floating.js +4 -1
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/input-group-context.d.ts +10 -0
- package/dist/input-group-context.js +8 -0
- package/package.json +1 -1
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
9
9
|
import Icon, { type IconName } from './Icon.svelte';
|
|
10
10
|
import { getFieldContext, warnUnlabelled } from '../../field-context';
|
|
11
|
+
import { getInputGroupContext } from '../../input-group-context';
|
|
11
12
|
|
|
12
13
|
// `size` shadows the native char-width attribute (unused in token-sized
|
|
13
14
|
// layouts) to expose a height preset instead.
|
|
@@ -43,9 +44,10 @@
|
|
|
43
44
|
|
|
44
45
|
let {
|
|
45
46
|
mono = false,
|
|
46
|
-
size
|
|
47
|
+
size,
|
|
47
48
|
grow = false,
|
|
48
49
|
invalid = false,
|
|
50
|
+
disabled = false,
|
|
49
51
|
icon,
|
|
50
52
|
clearable = false,
|
|
51
53
|
onclear,
|
|
@@ -65,7 +67,10 @@
|
|
|
65
67
|
}: Props = $props();
|
|
66
68
|
|
|
67
69
|
const field = getFieldContext();
|
|
68
|
-
const
|
|
70
|
+
const group = getInputGroupContext();
|
|
71
|
+
const sizeEff = $derived(size ?? group?.size ?? 'md');
|
|
72
|
+
const isInvalid = $derived(invalid || !!field?.invalid || !!group?.invalid);
|
|
73
|
+
const isDisabled = $derived(disabled || !!group?.disabled);
|
|
69
74
|
|
|
70
75
|
$effect(() => warnUnlabelled(el, 'Input'));
|
|
71
76
|
|
|
@@ -90,8 +95,9 @@
|
|
|
90
95
|
data-tsu="Input"
|
|
91
96
|
class="input {klass}"
|
|
92
97
|
class:mono
|
|
93
|
-
class:input-sm={
|
|
94
|
-
class:input-lg={
|
|
98
|
+
class:input-sm={sizeEff === 'sm'}
|
|
99
|
+
class:input-lg={sizeEff === 'lg'}
|
|
100
|
+
class:grouped={!!group}
|
|
95
101
|
class:input-grow={grow}
|
|
96
102
|
class:input-fixed={!!width && !wrapped}
|
|
97
103
|
class:input-pill={shape === 'pill'}
|
|
@@ -100,6 +106,7 @@
|
|
|
100
106
|
style:width={wrapped ? undefined : width}
|
|
101
107
|
bind:value
|
|
102
108
|
{...rest}
|
|
109
|
+
disabled={isDisabled}
|
|
103
110
|
onkeydown={onenter || onsubmit || onkeydown ? handleKeydown : undefined}
|
|
104
111
|
id={id ?? field?.id}
|
|
105
112
|
aria-describedby={ariaDescribedby ?? field?.describedBy}
|
|
@@ -182,6 +189,17 @@
|
|
|
182
189
|
.input[aria-invalid='true']:focus {
|
|
183
190
|
border-color: var(--danger);
|
|
184
191
|
}
|
|
192
|
+
/* Inside an InputGroup the group draws the focus ring and the overlaid
|
|
193
|
+
adornments report their widths; the text stays clear of them. */
|
|
194
|
+
.input.grouped {
|
|
195
|
+
width: 100%;
|
|
196
|
+
border-radius: var(--ig-radius);
|
|
197
|
+
padding-inline: max(var(--ig-pad-x), var(--ig-leading-w, 0px) + var(--ig-gap))
|
|
198
|
+
max(var(--ig-pad-x), var(--ig-trailing-w, 0px) + var(--ig-gap));
|
|
199
|
+
}
|
|
200
|
+
.input.grouped:focus-visible {
|
|
201
|
+
outline: none;
|
|
202
|
+
}
|
|
185
203
|
.mono {
|
|
186
204
|
font-family: var(--font-mono);
|
|
187
205
|
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
import type { ControlSize } from '../../size';
|
|
5
|
+
// Colour-swatch primitive: a filled chip that shows a colour. With a `label`
|
|
6
|
+
// it is an image (role=img + aria-label); without one it is decorative
|
|
7
|
+
// (aria-hidden). `interactive` renders a <button> so a palette picker can
|
|
8
|
+
// build a row of swatches with `selected` mirrored to aria-pressed. The
|
|
9
|
+
// hairline token border keeps pale colours visible on light themes.
|
|
10
|
+
|
|
11
|
+
type Own = {
|
|
12
|
+
/** Any CSS colour (or var()). */
|
|
13
|
+
color: string;
|
|
14
|
+
size?: ControlSize;
|
|
15
|
+
shape?: 'circle' | 'square';
|
|
16
|
+
/** Accessible name; without it the swatch is hidden from assistive tech. */
|
|
17
|
+
label?: string;
|
|
18
|
+
/** Draws the selection ring; `aria-pressed` when interactive. */
|
|
19
|
+
selected?: boolean;
|
|
20
|
+
/** Renders a `<button>` and wires `onclick`. */
|
|
21
|
+
interactive?: boolean;
|
|
22
|
+
onclick?: (e: MouseEvent) => void;
|
|
23
|
+
class?: string;
|
|
24
|
+
/** Optional glyph drawn over the colour (e.g. an "A" for auto). */
|
|
25
|
+
children?: Snippet;
|
|
26
|
+
};
|
|
27
|
+
let {
|
|
28
|
+
color,
|
|
29
|
+
size = 'md',
|
|
30
|
+
shape = 'circle',
|
|
31
|
+
label,
|
|
32
|
+
selected = false,
|
|
33
|
+
interactive = false,
|
|
34
|
+
onclick,
|
|
35
|
+
class: klass = '',
|
|
36
|
+
children,
|
|
37
|
+
...rest
|
|
38
|
+
}: Omit<HTMLAttributes<HTMLElement>, keyof Own> & Own = $props();
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<svelte:element
|
|
42
|
+
this={interactive ? 'button' : 'span'}
|
|
43
|
+
type={interactive ? 'button' : undefined}
|
|
44
|
+
data-tsu="Swatch"
|
|
45
|
+
class="swatch {klass}"
|
|
46
|
+
class:sm={size === 'sm'}
|
|
47
|
+
class:lg={size === 'lg'}
|
|
48
|
+
class:square={shape === 'square'}
|
|
49
|
+
class:selected
|
|
50
|
+
class:interactive
|
|
51
|
+
style:--swatch-color={color}
|
|
52
|
+
role={!interactive && label ? 'img' : undefined}
|
|
53
|
+
aria-label={interactive ? (label ?? color) : label}
|
|
54
|
+
aria-hidden={!interactive && !label ? 'true' : undefined}
|
|
55
|
+
aria-pressed={interactive ? selected : undefined}
|
|
56
|
+
onclick={interactive ? onclick : undefined}
|
|
57
|
+
{...rest}
|
|
58
|
+
>
|
|
59
|
+
{@render children?.()}
|
|
60
|
+
</svelte:element>
|
|
61
|
+
|
|
62
|
+
<style>
|
|
63
|
+
.swatch {
|
|
64
|
+
--swatch-size: 1rem;
|
|
65
|
+
display: inline-flex;
|
|
66
|
+
align-items: center;
|
|
67
|
+
justify-content: center;
|
|
68
|
+
flex: none;
|
|
69
|
+
width: var(--swatch-size);
|
|
70
|
+
height: var(--swatch-size);
|
|
71
|
+
padding: 0;
|
|
72
|
+
margin: 0;
|
|
73
|
+
border: 1px solid var(--border);
|
|
74
|
+
border-radius: var(--r-pill);
|
|
75
|
+
background: var(--swatch-color);
|
|
76
|
+
color: var(--text);
|
|
77
|
+
font: inherit;
|
|
78
|
+
font-size: calc(var(--swatch-size) * 0.6);
|
|
79
|
+
line-height: 1;
|
|
80
|
+
vertical-align: middle;
|
|
81
|
+
box-sizing: border-box;
|
|
82
|
+
}
|
|
83
|
+
.sm {
|
|
84
|
+
--swatch-size: 0.75rem;
|
|
85
|
+
}
|
|
86
|
+
.lg {
|
|
87
|
+
--swatch-size: 1.5rem;
|
|
88
|
+
}
|
|
89
|
+
.square {
|
|
90
|
+
border-radius: var(--r-sm);
|
|
91
|
+
}
|
|
92
|
+
.sm.square {
|
|
93
|
+
border-radius: 3px;
|
|
94
|
+
}
|
|
95
|
+
.selected {
|
|
96
|
+
border-color: var(--text);
|
|
97
|
+
box-shadow:
|
|
98
|
+
0 0 0 2px var(--bg),
|
|
99
|
+
0 0 0 3px var(--text);
|
|
100
|
+
}
|
|
101
|
+
.interactive {
|
|
102
|
+
cursor: pointer;
|
|
103
|
+
transition:
|
|
104
|
+
transform 0.1s var(--ease),
|
|
105
|
+
box-shadow 0.12s var(--ease);
|
|
106
|
+
}
|
|
107
|
+
.interactive:hover {
|
|
108
|
+
transform: scale(1.15);
|
|
109
|
+
}
|
|
110
|
+
.interactive:focus-visible {
|
|
111
|
+
outline: 2px solid var(--accent);
|
|
112
|
+
outline-offset: 3px;
|
|
113
|
+
}
|
|
114
|
+
</style>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import type { ControlSize } from '../../size';
|
|
4
|
+
type Own = {
|
|
5
|
+
/** Any CSS colour (or var()). */
|
|
6
|
+
color: string;
|
|
7
|
+
size?: ControlSize;
|
|
8
|
+
shape?: 'circle' | 'square';
|
|
9
|
+
/** Accessible name; without it the swatch is hidden from assistive tech. */
|
|
10
|
+
label?: string;
|
|
11
|
+
/** Draws the selection ring; `aria-pressed` when interactive. */
|
|
12
|
+
selected?: boolean;
|
|
13
|
+
/** Renders a `<button>` and wires `onclick`. */
|
|
14
|
+
interactive?: boolean;
|
|
15
|
+
onclick?: (e: MouseEvent) => void;
|
|
16
|
+
class?: string;
|
|
17
|
+
/** Optional glyph drawn over the colour (e.g. an "A" for auto). */
|
|
18
|
+
children?: Snippet;
|
|
19
|
+
};
|
|
20
|
+
type $$ComponentProps = Omit<HTMLAttributes<HTMLElement>, keyof Own> & Own;
|
|
21
|
+
declare const Swatch: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
22
|
+
type Swatch = ReturnType<typeof Swatch>;
|
|
23
|
+
export default Swatch;
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
import type { HTMLTextareaAttributes } from 'svelte/elements';
|
|
22
22
|
import { autoresize as autoresizeAction } from '../../autoresize';
|
|
23
23
|
import { getFieldContext, warnUnlabelled } from '../../field-context';
|
|
24
|
+
import { getInputGroupContext } from '../../input-group-context';
|
|
24
25
|
|
|
25
26
|
type Props = HTMLTextareaAttributes & {
|
|
26
27
|
mono?: boolean;
|
|
@@ -55,12 +56,13 @@
|
|
|
55
56
|
let {
|
|
56
57
|
mono = false,
|
|
57
58
|
autoresize = false,
|
|
58
|
-
size
|
|
59
|
+
size,
|
|
59
60
|
grow = false,
|
|
60
61
|
shrink = true,
|
|
61
62
|
block = false,
|
|
62
63
|
resize = 'bottom',
|
|
63
64
|
invalid = false,
|
|
65
|
+
disabled = false,
|
|
64
66
|
maxHeight,
|
|
65
67
|
onsubmit,
|
|
66
68
|
submitOn = 'none',
|
|
@@ -75,7 +77,10 @@
|
|
|
75
77
|
}: Props = $props();
|
|
76
78
|
|
|
77
79
|
const field = getFieldContext();
|
|
78
|
-
const
|
|
80
|
+
const group = getInputGroupContext();
|
|
81
|
+
const sizeEff = $derived(size ?? group?.size ?? 'md');
|
|
82
|
+
const isInvalid = $derived(invalid || !!field?.invalid || !!group?.invalid);
|
|
83
|
+
const isDisabled = $derived(disabled || !!group?.disabled);
|
|
79
84
|
|
|
80
85
|
$effect(() => warnUnlabelled(el, 'Textarea'));
|
|
81
86
|
|
|
@@ -150,6 +155,7 @@
|
|
|
150
155
|
class:grow={grow}
|
|
151
156
|
class:no-shrink={!shrink}
|
|
152
157
|
class:block={block}
|
|
158
|
+
class:grouped={!!group}
|
|
153
159
|
data-tsu="Textarea"
|
|
154
160
|
>
|
|
155
161
|
{#if autoresize}
|
|
@@ -157,9 +163,11 @@
|
|
|
157
163
|
bind:this={el}
|
|
158
164
|
class="textarea {klass}"
|
|
159
165
|
class:mono
|
|
160
|
-
class:textarea-sm={
|
|
161
|
-
class:textarea-lg={
|
|
166
|
+
class:textarea-sm={sizeEff === 'sm'}
|
|
167
|
+
class:textarea-lg={sizeEff === 'lg'}
|
|
168
|
+
class:grouped={!!group}
|
|
162
169
|
class:capped={!!maxHeight}
|
|
170
|
+
disabled={isDisabled}
|
|
163
171
|
style:max-height={maxHeight}
|
|
164
172
|
bind:value
|
|
165
173
|
use:autoresizeAction={typeof value === 'string' ? value : ''}
|
|
@@ -174,9 +182,11 @@
|
|
|
174
182
|
bind:this={el}
|
|
175
183
|
class="textarea {klass}"
|
|
176
184
|
class:mono
|
|
177
|
-
class:textarea-sm={
|
|
178
|
-
class:textarea-lg={
|
|
185
|
+
class:textarea-sm={sizeEff === 'sm'}
|
|
186
|
+
class:textarea-lg={sizeEff === 'lg'}
|
|
187
|
+
class:grouped={!!group}
|
|
179
188
|
class:capped={!!maxHeight}
|
|
189
|
+
disabled={isDisabled}
|
|
180
190
|
style:max-height={maxHeight}
|
|
181
191
|
bind:value
|
|
182
192
|
{...rest}
|
|
@@ -268,6 +278,23 @@
|
|
|
268
278
|
.textarea[aria-invalid='true']:focus {
|
|
269
279
|
border-color: var(--danger);
|
|
270
280
|
}
|
|
281
|
+
/* Inside an InputGroup the group draws the focus ring and the overlaid
|
|
282
|
+
adornments report their widths; the text stays clear of them. */
|
|
283
|
+
.textarea.grouped {
|
|
284
|
+
border-radius: var(--ig-radius);
|
|
285
|
+
padding-inline: max(var(--ig-pad-x), var(--ig-leading-w, 0px) + var(--ig-gap))
|
|
286
|
+
max(var(--ig-pad-x), var(--ig-trailing-w, 0px) + var(--ig-gap));
|
|
287
|
+
}
|
|
288
|
+
.textarea.grouped:focus-visible {
|
|
289
|
+
outline: none;
|
|
290
|
+
}
|
|
291
|
+
.textarea-wrap.grouped {
|
|
292
|
+
width: 100%;
|
|
293
|
+
}
|
|
294
|
+
.textarea-wrap.grouped .resize-handle {
|
|
295
|
+
left: var(--ig-leading-w, 0px);
|
|
296
|
+
right: var(--ig-trailing-w, 0px);
|
|
297
|
+
}
|
|
271
298
|
.mono {
|
|
272
299
|
font-family: var(--font-mono);
|
|
273
300
|
}
|
|
@@ -1,46 +1,99 @@
|
|
|
1
1
|
<script lang="ts" module>
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { DisclosureHeaderContext } from './Disclosure.svelte';
|
|
3
4
|
export interface AccordionItem {
|
|
4
5
|
id: string;
|
|
5
|
-
|
|
6
|
+
/** Plain-text header; `summary` wins when both are given. */
|
|
7
|
+
title?: string;
|
|
8
|
+
/** Rich header content; receives the live open state. */
|
|
9
|
+
summary?: Snippet<[DisclosureHeaderContext]>;
|
|
6
10
|
/** Panel content for this item. */
|
|
7
11
|
content: Snippet;
|
|
12
|
+
/** Open state. Re-passing a new value takes over from any local toggle. */
|
|
8
13
|
open?: boolean;
|
|
14
|
+
onchange?: (open: boolean) => void;
|
|
15
|
+
disabled?: boolean;
|
|
9
16
|
}
|
|
10
17
|
</script>
|
|
11
18
|
|
|
12
19
|
<script lang="ts">
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
import
|
|
20
|
+
// A stack of Disclosures. Each item's `open` is the parent's word: local
|
|
21
|
+
// toggles override it until the parent passes a new value. `multiple=false`
|
|
22
|
+
// closes the other items when one opens.
|
|
23
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
24
|
+
import Disclosure from './Disclosure.svelte';
|
|
25
|
+
import type { DisclosureChevron } from './Disclosure.svelte';
|
|
19
26
|
|
|
20
27
|
let {
|
|
21
28
|
items,
|
|
22
29
|
multiple = true,
|
|
23
|
-
|
|
24
|
-
|
|
30
|
+
chevron = 'end',
|
|
31
|
+
onchange,
|
|
32
|
+
class: klass = '',
|
|
33
|
+
style: styleProp = '',
|
|
34
|
+
...rest
|
|
35
|
+
}: Omit<HTMLAttributes<HTMLDivElement>, keyof Own> & Own = $props();
|
|
36
|
+
|
|
37
|
+
type Own = {
|
|
25
38
|
items: AccordionItem[];
|
|
26
39
|
multiple?: boolean;
|
|
40
|
+
chevron?: DisclosureChevron;
|
|
41
|
+
onchange?: (id: string, open: boolean) => void;
|
|
27
42
|
class?: string;
|
|
28
|
-
|
|
43
|
+
style?: string;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
let local = $state<Record<string, boolean>>({});
|
|
47
|
+
let seen: Record<string, boolean | undefined> = {};
|
|
48
|
+
|
|
49
|
+
$effect.pre(() => {
|
|
50
|
+
for (const item of items) {
|
|
51
|
+
if (seen[item.id] !== item.open) {
|
|
52
|
+
seen[item.id] = item.open;
|
|
53
|
+
delete local[item.id];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const isOpen = (item: AccordionItem) => local[item.id] ?? item.open ?? false;
|
|
29
59
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
60
|
+
function set(item: AccordionItem, open: boolean) {
|
|
61
|
+
local[item.id] = open;
|
|
62
|
+
item.onchange?.(open);
|
|
63
|
+
onchange?.(item.id, open);
|
|
64
|
+
if (!open || multiple) return;
|
|
65
|
+
for (const other of items) {
|
|
66
|
+
if (other.id === item.id || !isOpen(other)) continue;
|
|
67
|
+
local[other.id] = false;
|
|
68
|
+
other.onchange?.(false);
|
|
69
|
+
onchange?.(other.id, false);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
33
72
|
</script>
|
|
34
73
|
|
|
35
|
-
|
|
74
|
+
{#snippet plain(title: string | undefined)}
|
|
75
|
+
<span class="acc-title">{title}</span>
|
|
76
|
+
{/snippet}
|
|
77
|
+
|
|
78
|
+
<div {...rest} class="accordion {klass}" style={styleProp} data-tsu="Accordion">
|
|
36
79
|
{#each items as item (item.id)}
|
|
37
|
-
<
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
80
|
+
<Disclosure
|
|
81
|
+
class="acc-item"
|
|
82
|
+
id={`acc-${item.id}`}
|
|
83
|
+
open={isOpen(item)}
|
|
84
|
+
onchange={(o) => set(item, o)}
|
|
85
|
+
disabled={item.disabled}
|
|
86
|
+
{chevron}
|
|
87
|
+
>
|
|
88
|
+
{#snippet header(ctx)}
|
|
89
|
+
{#if item.summary}
|
|
90
|
+
{@render item.summary(ctx)}
|
|
91
|
+
{:else}
|
|
92
|
+
{@render plain(item.title)}
|
|
93
|
+
{/if}
|
|
94
|
+
{/snippet}
|
|
95
|
+
{@render item.content()}
|
|
96
|
+
</Disclosure>
|
|
44
97
|
{/each}
|
|
45
98
|
</div>
|
|
46
99
|
|
|
@@ -50,48 +103,13 @@
|
|
|
50
103
|
border-radius: var(--r-lg);
|
|
51
104
|
overflow: hidden;
|
|
52
105
|
}
|
|
53
|
-
|
|
106
|
+
.accordion > :global(.acc-item + .acc-item) {
|
|
54
107
|
border-top: 1px solid var(--border);
|
|
55
108
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
padding: var(--sp-3) var(--sp-4);
|
|
62
|
-
cursor: pointer;
|
|
63
|
-
font-weight: var(--fw-medium);
|
|
64
|
-
font-size: var(--fs-sm);
|
|
65
|
-
list-style: none;
|
|
66
|
-
user-select: none;
|
|
67
|
-
}
|
|
68
|
-
/* Hide the default disclosure triangle across engines. */
|
|
69
|
-
summary::-webkit-details-marker {
|
|
70
|
-
display: none;
|
|
71
|
-
}
|
|
72
|
-
summary::marker {
|
|
73
|
-
content: '';
|
|
74
|
-
}
|
|
75
|
-
summary:hover {
|
|
76
|
-
background: var(--bg-elevated-2);
|
|
77
|
-
}
|
|
78
|
-
summary:focus-visible {
|
|
79
|
-
outline: 2px solid var(--accent);
|
|
80
|
-
outline-offset: -2px;
|
|
81
|
-
}
|
|
82
|
-
/* Wrap the chevron in our own element so rotation is a scoped rule (no
|
|
83
|
-
:global into the Icon child). The Icon inherits the color via currentColor. */
|
|
84
|
-
.acc-chevron {
|
|
85
|
-
display: inline-flex;
|
|
86
|
-
color: var(--text-muted);
|
|
87
|
-
transition: transform 0.15s var(--ease);
|
|
88
|
-
}
|
|
89
|
-
details[open] summary .acc-chevron {
|
|
90
|
-
transform: rotate(180deg);
|
|
91
|
-
}
|
|
92
|
-
.acc-panel {
|
|
93
|
-
padding: 0 var(--sp-4) var(--sp-4);
|
|
94
|
-
font-size: var(--fs-sm);
|
|
95
|
-
color: var(--text-muted);
|
|
109
|
+
.acc-title {
|
|
110
|
+
min-width: 0;
|
|
111
|
+
overflow: hidden;
|
|
112
|
+
text-overflow: ellipsis;
|
|
113
|
+
white-space: nowrap;
|
|
96
114
|
}
|
|
97
115
|
</style>
|
|
@@ -1,16 +1,29 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { DisclosureHeaderContext } from './Disclosure.svelte';
|
|
2
3
|
export interface AccordionItem {
|
|
3
4
|
id: string;
|
|
4
|
-
|
|
5
|
+
/** Plain-text header; `summary` wins when both are given. */
|
|
6
|
+
title?: string;
|
|
7
|
+
/** Rich header content; receives the live open state. */
|
|
8
|
+
summary?: Snippet<[DisclosureHeaderContext]>;
|
|
5
9
|
/** Panel content for this item. */
|
|
6
10
|
content: Snippet;
|
|
11
|
+
/** Open state. Re-passing a new value takes over from any local toggle. */
|
|
7
12
|
open?: boolean;
|
|
13
|
+
onchange?: (open: boolean) => void;
|
|
14
|
+
disabled?: boolean;
|
|
8
15
|
}
|
|
9
|
-
type
|
|
16
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
17
|
+
import type { DisclosureChevron } from './Disclosure.svelte';
|
|
18
|
+
type Own = {
|
|
10
19
|
items: AccordionItem[];
|
|
11
20
|
multiple?: boolean;
|
|
21
|
+
chevron?: DisclosureChevron;
|
|
22
|
+
onchange?: (id: string, open: boolean) => void;
|
|
12
23
|
class?: string;
|
|
24
|
+
style?: string;
|
|
13
25
|
};
|
|
26
|
+
type $$ComponentProps = Omit<HTMLAttributes<HTMLDivElement>, keyof Own> & Own;
|
|
14
27
|
declare const Accordion: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
15
28
|
type Accordion = ReturnType<typeof Accordion>;
|
|
16
29
|
export default Accordion;
|