@zealakit/disclosure-svelte 0.0.0-stage → 1.0.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 +261 -2
- package/dist/accordion-content.svelte +79 -0
- package/dist/accordion-content.svelte.d.ts +12 -0
- package/dist/accordion-context.d.ts +26 -0
- package/dist/accordion-context.js +23 -0
- package/dist/accordion-item.svelte +52 -0
- package/dist/accordion-item.svelte.d.ts +10 -0
- package/dist/accordion-trigger.svelte +111 -0
- package/dist/accordion-trigger.svelte.d.ts +12 -0
- package/dist/accordion-types.d.ts +54 -0
- package/dist/accordion-types.js +1 -0
- package/dist/accordion.svelte +173 -0
- package/dist/accordion.svelte.d.ts +19 -0
- package/dist/animation.d.ts +39 -0
- package/dist/animation.js +22 -0
- package/dist/collapsible-content.svelte +74 -0
- package/dist/collapsible-content.svelte.d.ts +12 -0
- package/dist/collapsible-context.d.ts +14 -0
- package/dist/collapsible-context.js +12 -0
- package/dist/collapsible-styles.d.ts +2 -0
- package/dist/collapsible-styles.js +51 -0
- package/dist/collapsible-trigger.svelte +48 -0
- package/dist/collapsible-trigger.svelte.d.ts +11 -0
- package/dist/collapsible-types.d.ts +39 -0
- package/dist/collapsible-types.js +1 -0
- package/dist/collapsible.svelte +91 -0
- package/dist/collapsible.svelte.d.ts +17 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +10 -0
- package/dist/use-presence.svelte.d.ts +16 -0
- package/dist/use-presence.svelte.js +55 -0
- package/package.json +65 -4
package/README.md
CHANGED
|
@@ -1,3 +1,262 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @zealakit/disclosure-svelte
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[English](https://github.com/zealakit/disclosure/blob/main/packages/svelte/README.md) | [Tiếng Việt](https://github.com/zealakit/disclosure/blob/main/packages/svelte/README_VI.md) | [中文](https://github.com/zealakit/disclosure/blob/main/packages/svelte/README_CN.md)
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
Headless Collapsible and Accordion for Svelte 5. Built-in height animation is enabled by default. You own visual styling.
|
|
8
|
+
|
|
9
|
+
Apply padding, borders, and backgrounds via `class` on `CollapsibleContent` / `AccordionContent` (or `[data-collapsible-content-wrapper]`). Those styles land on the clipped content panel so exit animations collapse cleanly.
|
|
10
|
+
|
|
11
|
+
> **Note**
|
|
12
|
+
>
|
|
13
|
+
> Disclosure is not a replacement for a full UI library. Use it when Accordion or Collapsible in your kit is hard to restyle — or when you only need that one primitive and don’t want to pull in a large design system.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm i @zealakit/disclosure-svelte
|
|
19
|
+
# or: pnpm add / yarn add / bun add
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Peer dependency: `svelte` (^5).
|
|
23
|
+
|
|
24
|
+
## Collapsible
|
|
25
|
+
|
|
26
|
+
### Quick start
|
|
27
|
+
|
|
28
|
+
```svelte
|
|
29
|
+
<script lang="ts">
|
|
30
|
+
import {
|
|
31
|
+
Collapsible,
|
|
32
|
+
CollapsibleTrigger,
|
|
33
|
+
CollapsibleContent,
|
|
34
|
+
} from "@zealakit/disclosure-svelte";
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<Collapsible>
|
|
38
|
+
<CollapsibleTrigger>Toggle</CollapsibleTrigger>
|
|
39
|
+
<CollapsibleContent>Content</CollapsibleContent>
|
|
40
|
+
</Collapsible>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### `Collapsible` props
|
|
44
|
+
|
|
45
|
+
| Prop | Type | Default | Description |
|
|
46
|
+
| --- | --- | --- | --- |
|
|
47
|
+
| `defaultOpen` | `boolean` | `false` | Initial open state when uncontrolled |
|
|
48
|
+
| `open` | `boolean` | — | Controlled open state (`bind:open`) |
|
|
49
|
+
| `onOpenChange` | `(open: boolean) => void` | — | Called when open state should change |
|
|
50
|
+
| `disabled` | `boolean` | `false` | Disables the whole collapsible |
|
|
51
|
+
| `animated` | `boolean` | `true` | Enables height expand/collapse animation |
|
|
52
|
+
| `animationTimingFunction` | `"linear" \| "ease-in-out"` | `"ease-in-out"` | Transition easing |
|
|
53
|
+
| `animationDuration` | `number` | `200` | Duration in milliseconds |
|
|
54
|
+
| `animationDelay` | `number` | `0` | Delay in milliseconds |
|
|
55
|
+
| `class` / other attrs | — | — | Forwarded to the root element |
|
|
56
|
+
|
|
57
|
+
Root attributes: `data-slot="collapsible"`, `data-state="open" | "closed"`, `data-disabled` when disabled.
|
|
58
|
+
|
|
59
|
+
### `CollapsibleTrigger` props
|
|
60
|
+
|
|
61
|
+
| Prop | Type | Default | Description |
|
|
62
|
+
| --- | --- | --- | --- |
|
|
63
|
+
| `disabled` | `boolean` | inherits root | Disables this trigger |
|
|
64
|
+
| `class` / other attrs | — | — | Forwarded to the native `<button>` |
|
|
65
|
+
|
|
66
|
+
Sets `aria-expanded`, `aria-controls`, `data-slot="collapsible-trigger"`, and `data-state`.
|
|
67
|
+
|
|
68
|
+
### `CollapsibleContent` props
|
|
69
|
+
|
|
70
|
+
| Prop | Type | Default | Description |
|
|
71
|
+
| --- | --- | --- | --- |
|
|
72
|
+
| `forceMount` | `boolean` | `false` | Keep content in the DOM while closed |
|
|
73
|
+
| `class` / `style` | — | — | Applied to the **inner** content wrapper (clipped during animation) |
|
|
74
|
+
| other attrs | — | — | Forwarded to the inner wrapper |
|
|
75
|
+
|
|
76
|
+
Put borders/padding on the content wrapper, not only on the animated viewport.
|
|
77
|
+
|
|
78
|
+
### Controlled usage
|
|
79
|
+
|
|
80
|
+
```svelte
|
|
81
|
+
<script lang="ts">
|
|
82
|
+
import {
|
|
83
|
+
Collapsible,
|
|
84
|
+
CollapsibleTrigger,
|
|
85
|
+
CollapsibleContent,
|
|
86
|
+
} from "@zealakit/disclosure-svelte";
|
|
87
|
+
|
|
88
|
+
let open = $state(false);
|
|
89
|
+
</script>
|
|
90
|
+
|
|
91
|
+
<Collapsible bind:open>
|
|
92
|
+
<CollapsibleTrigger>Toggle</CollapsibleTrigger>
|
|
93
|
+
<CollapsibleContent>Content</CollapsibleContent>
|
|
94
|
+
</Collapsible>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Animation configuration
|
|
98
|
+
|
|
99
|
+
```svelte
|
|
100
|
+
<Collapsible
|
|
101
|
+
animationTimingFunction="linear"
|
|
102
|
+
animationDuration={300}
|
|
103
|
+
animationDelay={50}
|
|
104
|
+
>
|
|
105
|
+
<CollapsibleTrigger>Toggle</CollapsibleTrigger>
|
|
106
|
+
<CollapsibleContent>Content</CollapsibleContent>
|
|
107
|
+
</Collapsible>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
```svelte
|
|
111
|
+
<Collapsible animated={false}>
|
|
112
|
+
<CollapsibleTrigger>Instant</CollapsibleTrigger>
|
|
113
|
+
<CollapsibleContent>No height transition</CollapsibleContent>
|
|
114
|
+
</Collapsible>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
When `animated={false}`, open/close updates immediately and duration/delay are ignored. `prefers-reduced-motion` also disables motion.
|
|
118
|
+
|
|
119
|
+
CSS variables on the content viewport:
|
|
120
|
+
|
|
121
|
+
- `--collapsible-animation-duration`
|
|
122
|
+
- `--collapsible-animation-easing`
|
|
123
|
+
- `--collapsible-animation-delay`
|
|
124
|
+
|
|
125
|
+
### `forceMount`
|
|
126
|
+
|
|
127
|
+
```svelte
|
|
128
|
+
<CollapsibleContent forceMount>
|
|
129
|
+
Stays mounted while closed (`inert`; `hidden` when not animated).
|
|
130
|
+
</CollapsibleContent>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Accordion
|
|
134
|
+
|
|
135
|
+
### Quick start
|
|
136
|
+
|
|
137
|
+
```svelte
|
|
138
|
+
<script lang="ts">
|
|
139
|
+
import {
|
|
140
|
+
Accordion,
|
|
141
|
+
AccordionItem,
|
|
142
|
+
AccordionTrigger,
|
|
143
|
+
AccordionContent,
|
|
144
|
+
} from "@zealakit/disclosure-svelte";
|
|
145
|
+
</script>
|
|
146
|
+
|
|
147
|
+
<Accordion type="single" collapsible>
|
|
148
|
+
<AccordionItem value="general">
|
|
149
|
+
<AccordionTrigger>General</AccordionTrigger>
|
|
150
|
+
<AccordionContent>General content</AccordionContent>
|
|
151
|
+
</AccordionItem>
|
|
152
|
+
<AccordionItem value="advanced">
|
|
153
|
+
<AccordionTrigger>Advanced</AccordionTrigger>
|
|
154
|
+
<AccordionContent>Advanced content</AccordionContent>
|
|
155
|
+
</AccordionItem>
|
|
156
|
+
</Accordion>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### `Accordion` props (shared)
|
|
160
|
+
|
|
161
|
+
| Prop | Type | Default | Description |
|
|
162
|
+
| --- | --- | --- | --- |
|
|
163
|
+
| `type` | `"single" \| "multiple"` | `"single"` | Selection mode |
|
|
164
|
+
| `animated` | `boolean` | `true` | Height animation for item panels |
|
|
165
|
+
| `animationTimingFunction` | `"linear" \| "ease-in-out"` | `"ease-in-out"` | Easing for all items |
|
|
166
|
+
| `animationDuration` | `number` | `200` | Duration in milliseconds |
|
|
167
|
+
| `animationDelay` | `number` | `0` | Delay in milliseconds |
|
|
168
|
+
| `disabled` | `boolean` | `false` | Disables the whole accordion |
|
|
169
|
+
| `class` / other attrs | — | — | Forwarded to the root element |
|
|
170
|
+
|
|
171
|
+
Animation props are owned by the Accordion root and inherited by every item.
|
|
172
|
+
|
|
173
|
+
### `Accordion` props (`type="single"`)
|
|
174
|
+
|
|
175
|
+
| Prop | Type | Default | Description |
|
|
176
|
+
| --- | --- | --- | --- |
|
|
177
|
+
| `value` | `string \| null` | — | Controlled open item (`bind:value`) |
|
|
178
|
+
| `defaultValue` | `string \| null` | `null` | Uncontrolled initial item |
|
|
179
|
+
| `collapsible` | `boolean` | `false` | Allow closing the open item |
|
|
180
|
+
| `onValueChange` | `(value: string \| null) => void` | — | Value change callback |
|
|
181
|
+
|
|
182
|
+
### `Accordion` props (`type="multiple"`)
|
|
183
|
+
|
|
184
|
+
| Prop | Type | Default | Description |
|
|
185
|
+
| --- | --- | --- | --- |
|
|
186
|
+
| `value` | `string[]` | — | Controlled open items (`bind:value`) |
|
|
187
|
+
| `defaultValue` | `string[]` | `[]` | Uncontrolled initial items |
|
|
188
|
+
| `onValueChange` | `(value: string[]) => void` | — | Value change callback |
|
|
189
|
+
|
|
190
|
+
`collapsible` does not apply in multiple mode.
|
|
191
|
+
|
|
192
|
+
### `AccordionItem` props
|
|
193
|
+
|
|
194
|
+
| Prop | Type | Default | Description |
|
|
195
|
+
| --- | --- | --- | --- |
|
|
196
|
+
| `value` | `string` | **required** | Stable item id |
|
|
197
|
+
| `disabled` | `boolean` | `false` | Disables this item |
|
|
198
|
+
| `class` / other attrs | — | — | Forwarded to the item root |
|
|
199
|
+
|
|
200
|
+
### `AccordionTrigger` props
|
|
201
|
+
|
|
202
|
+
| Prop | Type | Default | Description |
|
|
203
|
+
| --- | --- | --- | --- |
|
|
204
|
+
| `disabled` | `boolean` | inherits | Disables this trigger |
|
|
205
|
+
| `class` / other attrs | — | — | Forwarded to the native `<button>` |
|
|
206
|
+
|
|
207
|
+
Keyboard: Arrow Up/Down, Home, End.
|
|
208
|
+
|
|
209
|
+
### `AccordionContent` props
|
|
210
|
+
|
|
211
|
+
| Prop | Type | Default | Description |
|
|
212
|
+
| --- | --- | --- | --- |
|
|
213
|
+
| `forceMount` | `boolean` | `false` | Keep content mounted while closed |
|
|
214
|
+
| `class` / `style` | — | — | Applied to the inner clipped wrapper |
|
|
215
|
+
| other attrs | — | — | Forwarded to the inner wrapper |
|
|
216
|
+
|
|
217
|
+
### Multiple mode example
|
|
218
|
+
|
|
219
|
+
```svelte
|
|
220
|
+
<Accordion type="multiple" defaultValue={["general"]}>
|
|
221
|
+
<AccordionItem value="general">
|
|
222
|
+
<AccordionTrigger>General</AccordionTrigger>
|
|
223
|
+
<AccordionContent>General content</AccordionContent>
|
|
224
|
+
</AccordionItem>
|
|
225
|
+
<AccordionItem value="advanced">
|
|
226
|
+
<AccordionTrigger>Advanced</AccordionTrigger>
|
|
227
|
+
<AccordionContent>Advanced content</AccordionContent>
|
|
228
|
+
</AccordionItem>
|
|
229
|
+
</Accordion>
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Accordion with custom animation
|
|
233
|
+
|
|
234
|
+
```svelte
|
|
235
|
+
<Accordion
|
|
236
|
+
type="single"
|
|
237
|
+
collapsible
|
|
238
|
+
animationTimingFunction="ease-in-out"
|
|
239
|
+
animationDuration={300}
|
|
240
|
+
animationDelay={0}
|
|
241
|
+
>
|
|
242
|
+
...
|
|
243
|
+
</Accordion>
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Styling hooks
|
|
247
|
+
|
|
248
|
+
| Attribute | Where | Purpose |
|
|
249
|
+
| --- | --- | --- |
|
|
250
|
+
| `data-slot` | root / trigger / content | Target parts in CSS |
|
|
251
|
+
| `data-state` | `"open" \| "closed"` | Open/closed styling |
|
|
252
|
+
| `data-disabled` | when disabled | Disabled styling |
|
|
253
|
+
| `data-collapsible-content-wrapper` | inner panel | Borders, padding, backgrounds |
|
|
254
|
+
| `data-animated` | content viewport | Present when height animation is active |
|
|
255
|
+
|
|
256
|
+
## AI Agent Skill
|
|
257
|
+
|
|
258
|
+
Practical Svelte usage for AI coding agents: [disclosure-svelte skill](https://github.com/zealakit/disclosure/blob/main/skills/disclosure-svelte.md).
|
|
259
|
+
|
|
260
|
+
## License
|
|
261
|
+
|
|
262
|
+
MIT © Zealakit
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount } from "svelte";
|
|
3
|
+
import {
|
|
4
|
+
getAccordionContext,
|
|
5
|
+
getAccordionItemContext,
|
|
6
|
+
} from "./accordion-context.js";
|
|
7
|
+
import { getCollapsibleAnimationStyleText } from "./animation.js";
|
|
8
|
+
import { ensureCollapsibleStyles } from "./collapsible-styles.js";
|
|
9
|
+
import { createPresence } from "./use-presence.svelte.js";
|
|
10
|
+
|
|
11
|
+
type Props = {
|
|
12
|
+
forceMount?: boolean;
|
|
13
|
+
children?: import("svelte").Snippet;
|
|
14
|
+
class?: string;
|
|
15
|
+
id?: string;
|
|
16
|
+
style?: string | null;
|
|
17
|
+
ontransitionend?: (event: TransitionEvent) => void;
|
|
18
|
+
[key: string]: unknown;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
let {
|
|
22
|
+
forceMount = false,
|
|
23
|
+
children,
|
|
24
|
+
class: className,
|
|
25
|
+
id,
|
|
26
|
+
style,
|
|
27
|
+
ontransitionend,
|
|
28
|
+
...rest
|
|
29
|
+
}: Props = $props();
|
|
30
|
+
|
|
31
|
+
const accordion = getAccordionContext();
|
|
32
|
+
const item = getAccordionItemContext();
|
|
33
|
+
const presence = createPresence({
|
|
34
|
+
open: () => item.open,
|
|
35
|
+
animated: () => accordion.animated,
|
|
36
|
+
forceMount: () => forceMount,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const animationStyle = $derived(
|
|
40
|
+
getCollapsibleAnimationStyleText({
|
|
41
|
+
animationTimingFunction: accordion.animationTimingFunction,
|
|
42
|
+
animationDuration: accordion.animationDuration,
|
|
43
|
+
animationDelay: accordion.animationDelay,
|
|
44
|
+
}),
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
onMount(() => {
|
|
48
|
+
ensureCollapsibleStyles();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
function handleTransitionEnd(event: TransitionEvent) {
|
|
52
|
+
ontransitionend?.(event);
|
|
53
|
+
presence.onTransitionEnd(event);
|
|
54
|
+
}
|
|
55
|
+
</script>
|
|
56
|
+
|
|
57
|
+
{#if presence.mounted}
|
|
58
|
+
<section
|
|
59
|
+
id={id ?? item.contentId}
|
|
60
|
+
aria-labelledby={item.triggerId}
|
|
61
|
+
data-slot="accordion-content"
|
|
62
|
+
data-state={presence.present ? "open" : "closed"}
|
|
63
|
+
data-animated={presence.animated ? "" : undefined}
|
|
64
|
+
hidden={!presence.present && !presence.animated}
|
|
65
|
+
inert={!item.open ? true : undefined}
|
|
66
|
+
style={animationStyle}
|
|
67
|
+
ontransitionend={handleTransitionEnd}
|
|
68
|
+
>
|
|
69
|
+
<div
|
|
70
|
+
{...rest}
|
|
71
|
+
class={className}
|
|
72
|
+
{style}
|
|
73
|
+
data-collapsible-content-wrapper=""
|
|
74
|
+
data-state={presence.present ? "open" : "closed"}
|
|
75
|
+
>
|
|
76
|
+
{@render children?.()}
|
|
77
|
+
</div>
|
|
78
|
+
</section>
|
|
79
|
+
{/if}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
type Props = {
|
|
2
|
+
forceMount?: boolean;
|
|
3
|
+
children?: import("svelte").Snippet;
|
|
4
|
+
class?: string;
|
|
5
|
+
id?: string;
|
|
6
|
+
style?: string | null;
|
|
7
|
+
ontransitionend?: (event: TransitionEvent) => void;
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
};
|
|
10
|
+
declare const AccordionContent: import("svelte").Component<Props, {}, "">;
|
|
11
|
+
type AccordionContent = ReturnType<typeof AccordionContent>;
|
|
12
|
+
export default AccordionContent;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { AnimationTimingFunction } from "./animation.js";
|
|
2
|
+
export type AccordionContextValue = {
|
|
3
|
+
get type(): "single" | "multiple";
|
|
4
|
+
get animated(): boolean;
|
|
5
|
+
get animationTimingFunction(): AnimationTimingFunction;
|
|
6
|
+
get animationDuration(): number;
|
|
7
|
+
get animationDelay(): number;
|
|
8
|
+
get disabled(): boolean;
|
|
9
|
+
get collapsible(): boolean;
|
|
10
|
+
isItemOpen: (value: string) => boolean;
|
|
11
|
+
toggleItem: (value: string) => void;
|
|
12
|
+
registerTrigger: (value: string, node: HTMLButtonElement | null) => void;
|
|
13
|
+
getEnabledTriggerValues: () => string[];
|
|
14
|
+
focusTrigger: (value: string) => void;
|
|
15
|
+
};
|
|
16
|
+
export type AccordionItemContextValue = {
|
|
17
|
+
get value(): string;
|
|
18
|
+
get open(): boolean;
|
|
19
|
+
get disabled(): boolean;
|
|
20
|
+
contentId: string;
|
|
21
|
+
triggerId: string;
|
|
22
|
+
};
|
|
23
|
+
export declare function setAccordionContext(value: AccordionContextValue): void;
|
|
24
|
+
export declare function getAccordionContext(): AccordionContextValue;
|
|
25
|
+
export declare function setAccordionItemContext(value: AccordionItemContextValue): void;
|
|
26
|
+
export declare function getAccordionItemContext(): AccordionItemContextValue;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { getContext, setContext } from "svelte";
|
|
2
|
+
const ACCORDION_CONTEXT = Symbol("zealakit-accordion");
|
|
3
|
+
const ACCORDION_ITEM_CONTEXT = Symbol("zealakit-accordion-item");
|
|
4
|
+
export function setAccordionContext(value) {
|
|
5
|
+
setContext(ACCORDION_CONTEXT, value);
|
|
6
|
+
}
|
|
7
|
+
export function getAccordionContext() {
|
|
8
|
+
const context = getContext(ACCORDION_CONTEXT);
|
|
9
|
+
if (!context) {
|
|
10
|
+
throw new Error("Accordion components must be used within an <Accordion>");
|
|
11
|
+
}
|
|
12
|
+
return context;
|
|
13
|
+
}
|
|
14
|
+
export function setAccordionItemContext(value) {
|
|
15
|
+
setContext(ACCORDION_ITEM_CONTEXT, value);
|
|
16
|
+
}
|
|
17
|
+
export function getAccordionItemContext() {
|
|
18
|
+
const context = getContext(ACCORDION_ITEM_CONTEXT);
|
|
19
|
+
if (!context) {
|
|
20
|
+
throw new Error("AccordionItem parts must be used within an <AccordionItem>");
|
|
21
|
+
}
|
|
22
|
+
return context;
|
|
23
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
getAccordionContext,
|
|
4
|
+
setAccordionItemContext,
|
|
5
|
+
} from "./accordion-context.js";
|
|
6
|
+
|
|
7
|
+
type Props = {
|
|
8
|
+
value: string;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
children?: import("svelte").Snippet;
|
|
11
|
+
class?: string;
|
|
12
|
+
[key: string]: unknown;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
let {
|
|
16
|
+
value,
|
|
17
|
+
disabled: disabledProp,
|
|
18
|
+
children,
|
|
19
|
+
class: className,
|
|
20
|
+
...rest
|
|
21
|
+
}: Props = $props();
|
|
22
|
+
|
|
23
|
+
const accordion = getAccordionContext();
|
|
24
|
+
const instanceId = crypto.randomUUID();
|
|
25
|
+
|
|
26
|
+
const open = $derived(accordion.isItemOpen(value));
|
|
27
|
+
const disabled = $derived(Boolean(accordion.disabled || disabledProp));
|
|
28
|
+
|
|
29
|
+
setAccordionItemContext({
|
|
30
|
+
get value() {
|
|
31
|
+
return value;
|
|
32
|
+
},
|
|
33
|
+
get open() {
|
|
34
|
+
return open;
|
|
35
|
+
},
|
|
36
|
+
get disabled() {
|
|
37
|
+
return disabled;
|
|
38
|
+
},
|
|
39
|
+
triggerId: `accordion-trigger-${instanceId}`,
|
|
40
|
+
contentId: `accordion-content-${instanceId}`,
|
|
41
|
+
});
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<div
|
|
45
|
+
{...rest}
|
|
46
|
+
class={className}
|
|
47
|
+
data-slot="accordion-item"
|
|
48
|
+
data-state={open ? "open" : "closed"}
|
|
49
|
+
data-disabled={disabled ? "" : undefined}
|
|
50
|
+
>
|
|
51
|
+
{@render children?.()}
|
|
52
|
+
</div>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type Props = {
|
|
2
|
+
value: string;
|
|
3
|
+
disabled?: boolean;
|
|
4
|
+
children?: import("svelte").Snippet;
|
|
5
|
+
class?: string;
|
|
6
|
+
[key: string]: unknown;
|
|
7
|
+
};
|
|
8
|
+
declare const AccordionItem: import("svelte").Component<Props, {}, "">;
|
|
9
|
+
type AccordionItem = ReturnType<typeof AccordionItem>;
|
|
10
|
+
export default AccordionItem;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
getAccordionContext,
|
|
4
|
+
getAccordionItemContext,
|
|
5
|
+
} from "./accordion-context.js";
|
|
6
|
+
|
|
7
|
+
type Props = {
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
children?: import("svelte").Snippet;
|
|
10
|
+
class?: string;
|
|
11
|
+
id?: string;
|
|
12
|
+
onclick?: (event: MouseEvent) => void;
|
|
13
|
+
onkeydown?: (event: KeyboardEvent) => void;
|
|
14
|
+
[key: string]: unknown;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
let {
|
|
18
|
+
disabled: disabledProp,
|
|
19
|
+
children,
|
|
20
|
+
class: className,
|
|
21
|
+
id,
|
|
22
|
+
onclick,
|
|
23
|
+
onkeydown,
|
|
24
|
+
...rest
|
|
25
|
+
}: Props = $props();
|
|
26
|
+
|
|
27
|
+
const accordion = getAccordionContext();
|
|
28
|
+
const item = getAccordionItemContext();
|
|
29
|
+
|
|
30
|
+
const isDisabled = $derived(disabledProp ?? item.disabled);
|
|
31
|
+
|
|
32
|
+
let node = $state<HTMLButtonElement | null>(null);
|
|
33
|
+
|
|
34
|
+
$effect(() => {
|
|
35
|
+
const element = node;
|
|
36
|
+
if (!element || isDisabled) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const itemValue = item.value;
|
|
40
|
+
accordion.registerTrigger(itemValue, element);
|
|
41
|
+
return () => {
|
|
42
|
+
accordion.registerTrigger(itemValue, null);
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
function handleClick(event: MouseEvent) {
|
|
47
|
+
onclick?.(event);
|
|
48
|
+
if (event.defaultPrevented || isDisabled) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
accordion.toggleItem(item.value);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function handleKeyDown(event: KeyboardEvent) {
|
|
55
|
+
onkeydown?.(event);
|
|
56
|
+
if (event.defaultPrevented || isDisabled) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const enabled = accordion.getEnabledTriggerValues();
|
|
61
|
+
const currentIndex = enabled.indexOf(item.value);
|
|
62
|
+
if (currentIndex === -1 || enabled.length === 0) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const focusAt = (index: number) => {
|
|
67
|
+
const next = enabled[index];
|
|
68
|
+
if (next) {
|
|
69
|
+
event.preventDefault();
|
|
70
|
+
accordion.focusTrigger(next);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
switch (event.key) {
|
|
75
|
+
case "ArrowDown":
|
|
76
|
+
focusAt((currentIndex + 1) % enabled.length);
|
|
77
|
+
break;
|
|
78
|
+
case "ArrowUp":
|
|
79
|
+
focusAt((currentIndex - 1 + enabled.length) % enabled.length);
|
|
80
|
+
break;
|
|
81
|
+
case "Home":
|
|
82
|
+
focusAt(0);
|
|
83
|
+
break;
|
|
84
|
+
case "End":
|
|
85
|
+
focusAt(enabled.length - 1);
|
|
86
|
+
break;
|
|
87
|
+
default:
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
</script>
|
|
92
|
+
|
|
93
|
+
<h3 data-slot="accordion-heading">
|
|
94
|
+
<button
|
|
95
|
+
type="button"
|
|
96
|
+
{...rest}
|
|
97
|
+
bind:this={node}
|
|
98
|
+
id={id ?? item.triggerId}
|
|
99
|
+
class={className}
|
|
100
|
+
aria-expanded={item.open}
|
|
101
|
+
aria-controls={item.contentId}
|
|
102
|
+
disabled={isDisabled}
|
|
103
|
+
data-slot="accordion-trigger"
|
|
104
|
+
data-state={item.open ? "open" : "closed"}
|
|
105
|
+
data-disabled={isDisabled ? "" : undefined}
|
|
106
|
+
onclick={handleClick}
|
|
107
|
+
onkeydown={handleKeyDown}
|
|
108
|
+
>
|
|
109
|
+
{@render children?.()}
|
|
110
|
+
</button>
|
|
111
|
+
</h3>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
type Props = {
|
|
2
|
+
disabled?: boolean;
|
|
3
|
+
children?: import("svelte").Snippet;
|
|
4
|
+
class?: string;
|
|
5
|
+
id?: string;
|
|
6
|
+
onclick?: (event: MouseEvent) => void;
|
|
7
|
+
onkeydown?: (event: KeyboardEvent) => void;
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
};
|
|
10
|
+
declare const AccordionTrigger: import("svelte").Component<Props, {}, "">;
|
|
11
|
+
type AccordionTrigger = ReturnType<typeof AccordionTrigger>;
|
|
12
|
+
export default AccordionTrigger;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { CollapsibleAnimationProps } from "./animation.js";
|
|
2
|
+
export type { AnimationTimingFunction, CollapsibleAnimationProps, } from "./animation.js";
|
|
3
|
+
type AccordionSharedProps = CollapsibleAnimationProps & {
|
|
4
|
+
/**
|
|
5
|
+
* Whether the entire accordion ignores user interaction.
|
|
6
|
+
* @default false
|
|
7
|
+
*/
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Additional attributes forwarded to the root element.
|
|
11
|
+
*/
|
|
12
|
+
[key: string]: unknown;
|
|
13
|
+
};
|
|
14
|
+
export type AccordionSingleProps = AccordionSharedProps & {
|
|
15
|
+
type?: "single";
|
|
16
|
+
/**
|
|
17
|
+
* Controlled value. Use `bind:value` for two-way binding.
|
|
18
|
+
*/
|
|
19
|
+
value?: string | null;
|
|
20
|
+
defaultValue?: string | null;
|
|
21
|
+
/**
|
|
22
|
+
* Allow collapsing the open item in single mode.
|
|
23
|
+
* @default false
|
|
24
|
+
*/
|
|
25
|
+
collapsible?: boolean;
|
|
26
|
+
onValueChange?: (value: string | null) => void;
|
|
27
|
+
};
|
|
28
|
+
export type AccordionMultipleProps = AccordionSharedProps & {
|
|
29
|
+
type: "multiple";
|
|
30
|
+
/**
|
|
31
|
+
* Controlled value. Use `bind:value` for two-way binding.
|
|
32
|
+
*/
|
|
33
|
+
value?: string[];
|
|
34
|
+
defaultValue?: string[];
|
|
35
|
+
onValueChange?: (value: string[]) => void;
|
|
36
|
+
};
|
|
37
|
+
export type AccordionProps = AccordionSingleProps | AccordionMultipleProps;
|
|
38
|
+
export type AccordionItemProps = {
|
|
39
|
+
value: string;
|
|
40
|
+
disabled?: boolean;
|
|
41
|
+
[key: string]: unknown;
|
|
42
|
+
};
|
|
43
|
+
export type AccordionTriggerProps = {
|
|
44
|
+
disabled?: boolean;
|
|
45
|
+
[key: string]: unknown;
|
|
46
|
+
};
|
|
47
|
+
export type AccordionContentProps = {
|
|
48
|
+
/**
|
|
49
|
+
* Keep content mounted while closed.
|
|
50
|
+
* @default false
|
|
51
|
+
*/
|
|
52
|
+
forceMount?: boolean;
|
|
53
|
+
[key: string]: unknown;
|
|
54
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|