@ailuracode/alpine-accordion 0.1.0 → 2.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 +171 -10
- package/dist/global.d.ts +6 -4
- package/dist/index.d.ts +153 -21
- package/dist/index.js +1 -194
- package/package.json +32 -3
- package/dist/index.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,23 +1,184 @@
|
|
|
1
1
|
# @ailuracode/alpine-accordion
|
|
2
2
|
|
|
3
|
-
Headless
|
|
4
|
-
|
|
5
|
-
**[Full documentation →](../../docs/plugins/accordion.md)**
|
|
3
|
+
Headless accordion store with **single** or **multiple** open panels, optional **default open** items, keyboard focus management, and ARIA helpers.
|
|
6
4
|
|
|
7
5
|
## Install
|
|
8
6
|
|
|
9
7
|
```bash
|
|
10
|
-
|
|
8
|
+
pnpm add @ailuracode/alpine-accordion @ailuracode/alpine-core alpinejs
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Open panel state is backed by an inline lightweight state — no extra dependency.
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
import Alpine from "alpinejs";
|
|
17
|
+
import { accordionPlugin } from "@ailuracode/alpine-accordion";
|
|
18
|
+
|
|
19
|
+
Alpine.plugin(accordionPlugin());
|
|
20
|
+
Alpine.start();
|
|
11
21
|
```
|
|
12
22
|
|
|
13
23
|
## Store API
|
|
14
24
|
|
|
25
|
+
| Method | Description |
|
|
26
|
+
|--------|-------------|
|
|
27
|
+
| `register(accordionId, options?)` | Create a group (`mode`, `defaultOpen`, `onChange`) |
|
|
28
|
+
| `registerItem(accordionId, itemId, disabled?)` | Register a panel trigger |
|
|
29
|
+
| `open(accordionId, itemId)` / `close` / `toggle` | Panel visibility |
|
|
30
|
+
| `isOpen(accordionId, itemId)` | Whether a panel is expanded |
|
|
31
|
+
| `openIds(accordionId)` | Array of currently open item ids |
|
|
32
|
+
| `activeItem(accordionId)` | Focused trigger id |
|
|
33
|
+
| `handleKeydown(accordionId, event)` | `ArrowUp`/`ArrowDown`/`Home`/`End` |
|
|
34
|
+
| `triggerProps(accordionId, itemId)` | `aria-expanded`, `aria-controls`, `tabindex` |
|
|
35
|
+
| `panelProps(accordionId, itemId)` | `role`, `aria-labelledby`, `aria-hidden` |
|
|
36
|
+
|
|
37
|
+
## Options
|
|
38
|
+
|
|
39
|
+
| Option | Default | Description |
|
|
40
|
+
|--------|---------|-------------|
|
|
41
|
+
| `mode` | `'single'` | `'single'` closes other panels when one opens; `'multiple'` allows several open |
|
|
42
|
+
| `defaultOpen` | — | Item id or array of ids open after `registerItem` |
|
|
43
|
+
| `onChange` | — | `(openIds: string[]) => void` when open state changes |
|
|
44
|
+
| `storeKey` | `'accordion'` | `$store` key the Alpine plugin registers under |
|
|
45
|
+
| `magicKey` | `'accordion'` (or `storeKey` when renamed) | `$accordion` magic key the Alpine plugin registers under |
|
|
46
|
+
|
|
47
|
+
In **single** mode, only the **first** id in `defaultOpen` is used when an array is passed.
|
|
48
|
+
|
|
49
|
+
### Avoiding name collisions
|
|
50
|
+
|
|
51
|
+
If your application already owns a `$store.accordion` or another toolkit plugin registers on that name, rename the integration surface without touching the controller:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
Alpine.plugin(accordionPlugin({
|
|
55
|
+
storeKey: "faq", // → $store.faq
|
|
56
|
+
// magicKey follows storeKey by default → $faq
|
|
57
|
+
magicKey: "disclosure", // explicit override → $disclosure
|
|
58
|
+
}));
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`storeKey` is the only argument most hosts need. `magicKey` moves independently only when both names must be freed. The exposed constants `DEFAULT_ACCORDION_STORE_KEY` and `DEFAULT_ACCORDION_MAGIC_KEY` keep the rename discoverable from TypeScript.
|
|
62
|
+
|
|
63
|
+
## Single mode
|
|
64
|
+
|
|
65
|
+
Only one panel open at a time.
|
|
66
|
+
|
|
67
|
+
```js
|
|
68
|
+
$store.accordion.register("faq", { mode: "single" });
|
|
69
|
+
["item-1", "item-2"].forEach((id) => $store.accordion.registerItem("faq", id));
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
```html
|
|
73
|
+
<div
|
|
74
|
+
x-data
|
|
75
|
+
x-init="
|
|
76
|
+
$store.accordion.register('faq', { mode: 'single' });
|
|
77
|
+
['item-1','item-2'].forEach(id => $store.accordion.registerItem('faq', id));
|
|
78
|
+
"
|
|
79
|
+
@keydown="$store.accordion.handleKeydown('faq', $event)"
|
|
80
|
+
>
|
|
81
|
+
<template x-for="id in ['item-1','item-2']" :key="id">
|
|
82
|
+
<div>
|
|
83
|
+
<button
|
|
84
|
+
x-bind="$store.accordion.triggerProps('faq', id)"
|
|
85
|
+
@click="$store.accordion.toggle('faq', id)"
|
|
86
|
+
x-text="id"
|
|
87
|
+
></button>
|
|
88
|
+
<div
|
|
89
|
+
class="overflow-hidden"
|
|
90
|
+
x-show="$store.accordion.isOpen('faq', id)"
|
|
91
|
+
x-collapse
|
|
92
|
+
x-bind="$store.accordion.panelProps('faq', id)"
|
|
93
|
+
>
|
|
94
|
+
<p class="px-4 py-3">Answer for <span x-text="id"></span></p>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
</template>
|
|
98
|
+
</div>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Multiple mode
|
|
102
|
+
|
|
103
|
+
Several panels can stay open.
|
|
104
|
+
|
|
105
|
+
```js
|
|
106
|
+
$store.accordion.register("settings", { mode: "multiple" });
|
|
107
|
+
["notifications", "privacy"].forEach((id) => $store.accordion.registerItem("settings", id));
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
```html
|
|
111
|
+
<div
|
|
112
|
+
x-init="
|
|
113
|
+
$store.accordion.register('settings', { mode: 'multiple' });
|
|
114
|
+
['notifications','privacy'].forEach(id => $store.accordion.registerItem('settings', id));
|
|
115
|
+
"
|
|
116
|
+
>
|
|
117
|
+
<!-- same markup as single mode -->
|
|
118
|
+
</div>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Default open
|
|
122
|
+
|
|
123
|
+
Pass `defaultOpen` when registering. Panels open automatically when their item is registered.
|
|
124
|
+
|
|
15
125
|
```js
|
|
16
|
-
|
|
17
|
-
$store.accordion.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
126
|
+
// Single — one panel open on init
|
|
127
|
+
$store.accordion.register("faq", {
|
|
128
|
+
mode: "single",
|
|
129
|
+
defaultOpen: "item-2",
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// Multiple — several panels open on init
|
|
133
|
+
$store.accordion.register("settings", {
|
|
134
|
+
mode: "multiple",
|
|
135
|
+
defaultOpen: ["notifications", "integrations"],
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
["item-1", "item-2", "item-3"].forEach((id) => $store.accordion.registerItem("faq", id));
|
|
21
139
|
```
|
|
22
140
|
|
|
23
|
-
|
|
141
|
+
Read open ids reactively:
|
|
142
|
+
|
|
143
|
+
```html
|
|
144
|
+
<span x-text="$store.accordion.openIds('faq').join(', ')"></span>
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Panel animation
|
|
148
|
+
|
|
149
|
+
`@ailuracode/alpine-accordion` is headless — it does not animate panels. Use the official [`@alpinejs/collapse`](https://alpinejs.dev/plugins/collapse) plugin: `x-collapse` must sit on the **same element** as `x-show`.
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
pnpm add @alpinejs/collapse
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
```js
|
|
156
|
+
import collapse from "@alpinejs/collapse";
|
|
157
|
+
|
|
158
|
+
Alpine.plugin(collapse);
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
```html
|
|
162
|
+
<div
|
|
163
|
+
x-show="$store.accordion.isOpen('faq', id)"
|
|
164
|
+
x-collapse
|
|
165
|
+
x-bind="$store.accordion.panelProps('faq', id)"
|
|
166
|
+
class="overflow-hidden"
|
|
167
|
+
>
|
|
168
|
+
<div class="px-4 py-3">Panel content</div>
|
|
169
|
+
</div>
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Put padding on an **inner** wrapper — vertical padding on the same node as `x-collapse` prevents height from reaching `0` and the close animation can appear stuck.
|
|
173
|
+
|
|
174
|
+
Optional modifiers: `x-collapse.duration.300ms`, `x-collapse.min.50px`. See the [Collapse plugin docs](https://alpinejs.dev/plugins/collapse).
|
|
175
|
+
|
|
176
|
+
`panelProps()` does not set `hidden` — visibility is driven by `x-show` on the client.
|
|
177
|
+
|
|
178
|
+
## SSR
|
|
179
|
+
|
|
180
|
+
Safe when panels start collapsed; visibility is controlled with `x-show` on the client. Use `defaultOpen` only when markup is hydrated on the client.
|
|
181
|
+
|
|
182
|
+
## License
|
|
183
|
+
|
|
184
|
+
MIT
|
package/dist/global.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/// <reference types="@types/alpinejs" />
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import type { AccordionGroup, AccordionGroupOptions } from "./types";
|
|
4
|
+
|
|
5
|
+
export type { AccordionGroup, AccordionGroupOptions, AccordionMode } from "./types";
|
|
4
6
|
|
|
5
7
|
export interface AccordionStore {
|
|
6
|
-
groups: Record<string,
|
|
7
|
-
register(accordionId: string, options?:
|
|
8
|
+
readonly groups: Record<string, AccordionGroup>;
|
|
9
|
+
register(accordionId: string, options?: AccordionGroupOptions): void;
|
|
8
10
|
unregister(accordionId: string): void;
|
|
9
11
|
registerItem(accordionId: string, itemId: string, disabled?: boolean): void;
|
|
10
12
|
unregisterItem(accordionId: string, itemId: string): void;
|
|
@@ -24,7 +26,7 @@ export interface AccordionStore {
|
|
|
24
26
|
destroy(): void;
|
|
25
27
|
}
|
|
26
28
|
|
|
27
|
-
export function
|
|
29
|
+
export function createAccordionController(id?: string): import("./types").AccordionController;
|
|
28
30
|
|
|
29
31
|
export default function accordionPlugin(): import("alpinejs").PluginCallback;
|
|
30
32
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,16 +1,29 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Alpine, PluginCallback, BaseController } from '@ailuracode/alpine-core';
|
|
2
|
+
export { Unsubscribe } from '@ailuracode/alpine-core';
|
|
3
|
+
import { Alpine as Alpine$1 } from 'alpinejs';
|
|
2
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Public type contracts for `@ailuracode/alpine-accordion`.
|
|
7
|
+
*
|
|
8
|
+
* Every public type lives in a `types.ts` module so consumers can import
|
|
9
|
+
* them without pulling the implementation. The shape IS the contract.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** Accordion display mode. */
|
|
3
13
|
type AccordionMode = "single" | "multiple";
|
|
14
|
+
/** An accordion item's registration state. */
|
|
4
15
|
type AccordionItem = {
|
|
5
16
|
id: string;
|
|
6
17
|
disabled: boolean;
|
|
7
18
|
};
|
|
19
|
+
/** Options passed when registering an accordion group. */
|
|
8
20
|
type AccordionGroupOptions = {
|
|
9
|
-
mode?: AccordionMode;
|
|
21
|
+
readonly mode?: AccordionMode;
|
|
10
22
|
/** Item id or ids open on init. In `single` mode only the first id is used. */
|
|
11
|
-
defaultOpen?: string | string[];
|
|
12
|
-
onChange?: (openIds: string[]) => void;
|
|
23
|
+
readonly defaultOpen?: string | string[];
|
|
24
|
+
readonly onChange?: (openIds: string[]) => void;
|
|
13
25
|
};
|
|
26
|
+
/** Internal representation of an accordion group. */
|
|
14
27
|
type AccordionGroup = {
|
|
15
28
|
mode: AccordionMode;
|
|
16
29
|
open: Record<string, boolean>;
|
|
@@ -19,9 +32,17 @@ type AccordionGroup = {
|
|
|
19
32
|
defaultOpen: string[];
|
|
20
33
|
onChange?: (openIds: string[]) => void;
|
|
21
34
|
};
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
35
|
+
/** Discriminator for change events. */
|
|
36
|
+
type AccordionChangeSource = "user" | "initialization";
|
|
37
|
+
/** Detail payload for the `change` event. */
|
|
38
|
+
interface AccordionChangeDetail {
|
|
39
|
+
readonly groupId: string;
|
|
40
|
+
readonly openIds: string[];
|
|
41
|
+
readonly source: AccordionChangeSource;
|
|
42
|
+
}
|
|
43
|
+
/** Alpine-facing store surface. */
|
|
44
|
+
interface AccordionStore {
|
|
45
|
+
readonly groups: Record<string, AccordionGroup>;
|
|
25
46
|
register(accordionId: string, options?: AccordionGroupOptions): void;
|
|
26
47
|
unregister(accordionId: string): void;
|
|
27
48
|
registerItem(accordionId: string, itemId: string, disabled?: boolean): void;
|
|
@@ -37,21 +58,132 @@ type AccordionStore = {
|
|
|
37
58
|
triggerProps(accordionId: string, itemId: string): Record<string, string | number | boolean | undefined>;
|
|
38
59
|
panelProps(accordionId: string, itemId: string): Record<string, string | boolean | undefined>;
|
|
39
60
|
destroy(): void;
|
|
61
|
+
}
|
|
62
|
+
/** Options accepted by the accordion plugin factory. */
|
|
63
|
+
interface CreateAccordionOptions {
|
|
64
|
+
readonly id?: string;
|
|
65
|
+
/**
|
|
66
|
+
* `$store` key the Alpine plugin registers under. Defaults to
|
|
67
|
+
* {@link DEFAULT_ACCORDION_STORE_KEY}. Set when the host already
|
|
68
|
+
* owns an `accordion` store or another toolkit plugin would
|
|
69
|
+
* collide on that name — the rename avoids the collision without
|
|
70
|
+
* touching the controller. Ignored by the standalone
|
|
71
|
+
* `createAccordionController` factory.
|
|
72
|
+
*/
|
|
73
|
+
readonly storeKey?: string;
|
|
74
|
+
/**
|
|
75
|
+
* `$accordion` magic key the Alpine plugin registers under.
|
|
76
|
+
* Defaults to {@link DEFAULT_ACCORDION_MAGIC_KEY}, or to `storeKey`
|
|
77
|
+
* when that is renamed (the magic follows the store so consumers
|
|
78
|
+
* only rename one). Ignored by the standalone factory.
|
|
79
|
+
*/
|
|
80
|
+
readonly magicKey?: string;
|
|
81
|
+
}
|
|
82
|
+
/** Default `$store` key registered by {@link accordionPlugin}. */
|
|
83
|
+
declare const DEFAULT_ACCORDION_STORE_KEY = "accordion";
|
|
84
|
+
/** Default `$accordion` magic key registered by {@link accordionPlugin}. */
|
|
85
|
+
declare const DEFAULT_ACCORDION_MAGIC_KEY = "accordion";
|
|
86
|
+
/** Typed view of `Alpine` the accordion plugin uses internally. */
|
|
87
|
+
type AccordionAlpine = Alpine<{
|
|
88
|
+
accordion: AccordionStore;
|
|
89
|
+
}> & {
|
|
90
|
+
cleanup?(callback: () => void): void;
|
|
40
91
|
};
|
|
41
|
-
/**
|
|
42
|
-
|
|
92
|
+
/** `Alpine.plugin()` callback signature. */
|
|
93
|
+
type AccordionPluginCallback = PluginCallback<Alpine$1>;
|
|
43
94
|
|
|
44
|
-
/**
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
95
|
+
/**
|
|
96
|
+
* Strongly-typed event map for the accordion controller.
|
|
97
|
+
*/
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Event map for accordion state changes. Single-key contract:
|
|
101
|
+
* every transition emits `change` with the same
|
|
102
|
+
* {@link AccordionChangeDetail} payload.
|
|
103
|
+
*/
|
|
104
|
+
interface AccordionEvents extends Record<string, unknown> {
|
|
105
|
+
change: AccordionChangeDetail;
|
|
55
106
|
}
|
|
56
107
|
|
|
57
|
-
|
|
108
|
+
/**
|
|
109
|
+
* Accordion controller — the framework-agnostic core of
|
|
110
|
+
* `@ailuracode/alpine-accordion`. Manages accordion groups with
|
|
111
|
+
* open/close state, keyboard navigation, and ARIA props.
|
|
112
|
+
*
|
|
113
|
+
* Emits a typed `change` event on every open/close transition so
|
|
114
|
+
* consumers can react programmatically.
|
|
115
|
+
*
|
|
116
|
+
* Selection state is a plain per-group object — no controller class,
|
|
117
|
+
* no event emission. The full `@ailuracode/alpine-selection`
|
|
118
|
+
* dependency was dropped in favour of this lightweight state to keep
|
|
119
|
+
* the bundle slim.
|
|
120
|
+
*/
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Headless accordion controller. Manages multiple accordion groups
|
|
124
|
+
* with open/close state, keyboard navigation, and ARIA props.
|
|
125
|
+
*/
|
|
126
|
+
declare class AccordionController extends BaseController<AccordionEvents> {
|
|
127
|
+
#private;
|
|
128
|
+
constructor(id?: string);
|
|
129
|
+
/** Whether a group is registered. */
|
|
130
|
+
hasGroup(accordionId: string): boolean;
|
|
131
|
+
/**
|
|
132
|
+
* Returns shallow snapshots of all groups for adapter sync.
|
|
133
|
+
* Mutating the returned objects does not affect controller state.
|
|
134
|
+
*/
|
|
135
|
+
snapshotGroups(): Record<string, AccordionGroup>;
|
|
136
|
+
register(accordionId: string, options?: AccordionGroupOptions): void;
|
|
137
|
+
unregister(accordionId: string): void;
|
|
138
|
+
registerItem(accordionId: string, itemId: string, disabled?: boolean): void;
|
|
139
|
+
unregisterItem(accordionId: string, itemId: string): void;
|
|
140
|
+
open(accordionId: string, itemId: string): void;
|
|
141
|
+
close(accordionId: string, itemId: string): void;
|
|
142
|
+
toggle(accordionId: string, itemId: string): void;
|
|
143
|
+
isOpen(accordionId: string, itemId: string): boolean;
|
|
144
|
+
openIds(accordionId: string): string[];
|
|
145
|
+
activeItem(accordionId: string): string | null;
|
|
146
|
+
setActiveItem(accordionId: string, itemId: string | null): void;
|
|
147
|
+
handleKeydown(accordionId: string, event: KeyboardEvent): void;
|
|
148
|
+
triggerProps(accordionId: string, itemId: string): Record<string, string | number | boolean | undefined>;
|
|
149
|
+
panelProps(accordionId: string, itemId: string): Record<string, string | boolean | undefined>;
|
|
150
|
+
/**
|
|
151
|
+
* Returns a store-shaped object for Alpine's `$store.accordion`.
|
|
152
|
+
* Prefer {@link createAccordionStore} for new code.
|
|
153
|
+
*
|
|
154
|
+
* @deprecated Use `createAccordionStore()` or `createAccordionStoreFromController()`.
|
|
155
|
+
*/
|
|
156
|
+
toStore(): AccordionStore;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Creates an AccordionController and returns a store-shaped object.
|
|
160
|
+
* Convenience for non-Alpine consumers.
|
|
161
|
+
*/
|
|
162
|
+
declare function createAccordionController(id?: string): AccordionController;
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Alpine.js integration for `@ailuracode/alpine-accordion`.
|
|
166
|
+
*
|
|
167
|
+
* Thin adapter that wires {@link AccordionController} into
|
|
168
|
+
* `$store.accordion` and the `$accordion` magic.
|
|
169
|
+
*/
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Plugin factory — returns the `Alpine.plugin()` callback. Pass
|
|
173
|
+
* {@link CreateAccordionOptions} to configure the controller,
|
|
174
|
+
* or `{}` for the package defaults.
|
|
175
|
+
*/
|
|
176
|
+
declare function accordionPlugin(options?: CreateAccordionOptions): AccordionPluginCallback;
|
|
177
|
+
/** Builds typed accordion plugin options. */
|
|
178
|
+
declare function accordionOptions<const T extends CreateAccordionOptions>(options: T): T;
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Store factory for `@ailuracode/alpine-accordion`.
|
|
182
|
+
*/
|
|
183
|
+
|
|
184
|
+
/** Builds an {@link AccordionStore} backed by a new {@link AccordionController}. */
|
|
185
|
+
declare function createAccordionStore(): AccordionStore;
|
|
186
|
+
/** Builds an {@link AccordionStore} that mirrors the given controller. */
|
|
187
|
+
declare function createAccordionStoreFromController(controller: AccordionController): AccordionStore;
|
|
188
|
+
|
|
189
|
+
export { type AccordionAlpine, type AccordionChangeDetail, type AccordionChangeSource, AccordionController, type AccordionEvents, type AccordionGroup, type AccordionGroupOptions, type AccordionItem, type AccordionMode, type AccordionPluginCallback, type AccordionStore, type CreateAccordionOptions, DEFAULT_ACCORDION_MAGIC_KEY, DEFAULT_ACCORDION_STORE_KEY, accordionOptions, accordionPlugin, createAccordionController, createAccordionStore, createAccordionStoreFromController, accordionPlugin as default };
|
package/dist/index.js
CHANGED
|
@@ -1,194 +1 @@
|
|
|
1
|
-
|
|
2
|
-
function enabledItems(group) {
|
|
3
|
-
return group.items.filter((item) => !item.disabled);
|
|
4
|
-
}
|
|
5
|
-
function itemIndex(group, itemId) {
|
|
6
|
-
return enabledItems(group).findIndex((item) => item.id === itemId);
|
|
7
|
-
}
|
|
8
|
-
function notify(group) {
|
|
9
|
-
group.onChange?.(
|
|
10
|
-
Object.entries(group.open).filter(([, isOpen]) => isOpen).map(([id]) => id)
|
|
11
|
-
);
|
|
12
|
-
}
|
|
13
|
-
function normalizeDefaultOpen(mode, value) {
|
|
14
|
-
if (!value) {
|
|
15
|
-
return [];
|
|
16
|
-
}
|
|
17
|
-
const ids = Array.isArray(value) ? value : [value];
|
|
18
|
-
return mode === "single" && ids.length > 0 ? [ids[0]] : ids;
|
|
19
|
-
}
|
|
20
|
-
function createGroup(options = {}) {
|
|
21
|
-
const mode = options.mode ?? "single";
|
|
22
|
-
return {
|
|
23
|
-
mode,
|
|
24
|
-
open: {},
|
|
25
|
-
activeItemId: null,
|
|
26
|
-
items: [],
|
|
27
|
-
defaultOpen: normalizeDefaultOpen(mode, options.defaultOpen),
|
|
28
|
-
onChange: options.onChange
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
function createAccordionStore() {
|
|
32
|
-
function getOrCreate(store2, accordionId) {
|
|
33
|
-
store2.groups[accordionId] ??= createGroup();
|
|
34
|
-
return store2.groups[accordionId];
|
|
35
|
-
}
|
|
36
|
-
const store = {
|
|
37
|
-
groups: {},
|
|
38
|
-
register(accordionId, options = {}) {
|
|
39
|
-
this.groups[accordionId] = createGroup(options);
|
|
40
|
-
},
|
|
41
|
-
unregister(accordionId) {
|
|
42
|
-
delete this.groups[accordionId];
|
|
43
|
-
},
|
|
44
|
-
registerItem(accordionId, itemId, disabled = false) {
|
|
45
|
-
const group = getOrCreate(this, accordionId);
|
|
46
|
-
const existing = group.items.find((item) => item.id === itemId);
|
|
47
|
-
if (existing) {
|
|
48
|
-
existing.disabled = disabled;
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
group.items.push({ id: itemId, disabled });
|
|
52
|
-
if (group.defaultOpen.includes(itemId) && !disabled) {
|
|
53
|
-
this.open(accordionId, itemId);
|
|
54
|
-
group.activeItemId ??= itemId;
|
|
55
|
-
}
|
|
56
|
-
},
|
|
57
|
-
unregisterItem(accordionId, itemId) {
|
|
58
|
-
const group = this.groups[accordionId];
|
|
59
|
-
if (!group) {
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
group.items = group.items.filter((item) => item.id !== itemId);
|
|
63
|
-
delete group.open[itemId];
|
|
64
|
-
},
|
|
65
|
-
open(accordionId, itemId) {
|
|
66
|
-
const group = this.groups[accordionId];
|
|
67
|
-
const item = group?.items.find((entry) => entry.id === itemId);
|
|
68
|
-
if (!(group && item) || item.disabled) {
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
if (group.mode === "single") {
|
|
72
|
-
for (const id of Object.keys(group.open)) {
|
|
73
|
-
if (group.open[id]) {
|
|
74
|
-
group.open[id] = false;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
group.open[itemId] = true;
|
|
78
|
-
} else {
|
|
79
|
-
group.open[itemId] = true;
|
|
80
|
-
}
|
|
81
|
-
notify(group);
|
|
82
|
-
},
|
|
83
|
-
close(accordionId, itemId) {
|
|
84
|
-
const group = this.groups[accordionId];
|
|
85
|
-
if (!group?.open[itemId]) {
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
group.open[itemId] = false;
|
|
89
|
-
notify(group);
|
|
90
|
-
},
|
|
91
|
-
toggle(accordionId, itemId) {
|
|
92
|
-
if (this.isOpen(accordionId, itemId)) {
|
|
93
|
-
this.close(accordionId, itemId);
|
|
94
|
-
} else {
|
|
95
|
-
this.open(accordionId, itemId);
|
|
96
|
-
}
|
|
97
|
-
},
|
|
98
|
-
isOpen(accordionId, itemId) {
|
|
99
|
-
return this.groups[accordionId]?.open[itemId] ?? false;
|
|
100
|
-
},
|
|
101
|
-
openIds(accordionId) {
|
|
102
|
-
const open = this.groups[accordionId]?.open ?? {};
|
|
103
|
-
return Object.entries(open).filter(([, isOpen]) => isOpen).map(([id]) => id);
|
|
104
|
-
},
|
|
105
|
-
activeItem(accordionId) {
|
|
106
|
-
return this.groups[accordionId]?.activeItemId ?? null;
|
|
107
|
-
},
|
|
108
|
-
setActiveItem(accordionId, itemId) {
|
|
109
|
-
const group = this.groups[accordionId];
|
|
110
|
-
if (!group) {
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
if (itemId === null) {
|
|
114
|
-
group.activeItemId = null;
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
const item = group.items.find((entry) => entry.id === itemId);
|
|
118
|
-
if (item && !item.disabled) {
|
|
119
|
-
group.activeItemId = itemId;
|
|
120
|
-
}
|
|
121
|
-
},
|
|
122
|
-
handleKeydown(accordionId, event) {
|
|
123
|
-
const group = this.groups[accordionId];
|
|
124
|
-
if (!group) {
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
const items = enabledItems(group);
|
|
128
|
-
if (items.length === 0) {
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
if (!group.activeItemId) {
|
|
132
|
-
group.activeItemId = items[0]?.id ?? null;
|
|
133
|
-
}
|
|
134
|
-
const currentIndex = group.activeItemId ? itemIndex(group, group.activeItemId) : 0;
|
|
135
|
-
switch (event.key) {
|
|
136
|
-
case "ArrowDown":
|
|
137
|
-
event.preventDefault();
|
|
138
|
-
group.activeItemId = items[(currentIndex + 1) % items.length]?.id ?? null;
|
|
139
|
-
break;
|
|
140
|
-
case "ArrowUp":
|
|
141
|
-
event.preventDefault();
|
|
142
|
-
group.activeItemId = items[(currentIndex - 1 + items.length) % items.length]?.id ?? null;
|
|
143
|
-
break;
|
|
144
|
-
case "Home":
|
|
145
|
-
event.preventDefault();
|
|
146
|
-
group.activeItemId = items[0]?.id ?? null;
|
|
147
|
-
break;
|
|
148
|
-
case "End":
|
|
149
|
-
event.preventDefault();
|
|
150
|
-
group.activeItemId = items[items.length - 1]?.id ?? null;
|
|
151
|
-
break;
|
|
152
|
-
default:
|
|
153
|
-
break;
|
|
154
|
-
}
|
|
155
|
-
},
|
|
156
|
-
triggerProps(accordionId, itemId) {
|
|
157
|
-
const open = this.isOpen(accordionId, itemId);
|
|
158
|
-
const active = this.activeItem(accordionId) === itemId;
|
|
159
|
-
return {
|
|
160
|
-
"aria-expanded": open,
|
|
161
|
-
"aria-controls": `${accordionId}-panel-${itemId}`,
|
|
162
|
-
id: `${accordionId}-trigger-${itemId}`,
|
|
163
|
-
tabindex: active ? 0 : -1
|
|
164
|
-
};
|
|
165
|
-
},
|
|
166
|
-
panelProps(accordionId, itemId) {
|
|
167
|
-
const open = this.isOpen(accordionId, itemId);
|
|
168
|
-
return {
|
|
169
|
-
id: `${accordionId}-panel-${itemId}`,
|
|
170
|
-
role: "region",
|
|
171
|
-
"aria-labelledby": `${accordionId}-trigger-${itemId}`,
|
|
172
|
-
"aria-hidden": open ? void 0 : true
|
|
173
|
-
};
|
|
174
|
-
},
|
|
175
|
-
destroy() {
|
|
176
|
-
this.groups = {};
|
|
177
|
-
}
|
|
178
|
-
};
|
|
179
|
-
return store;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// src/index.ts
|
|
183
|
-
function accordionPlugin() {
|
|
184
|
-
return function registerAccordion(Alpine) {
|
|
185
|
-
const store = createAccordionStore();
|
|
186
|
-
Alpine.store("accordion", store);
|
|
187
|
-
Alpine.magic("accordion", () => Alpine.store("accordion"));
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
export {
|
|
191
|
-
createAccordionStore,
|
|
192
|
-
accordionPlugin as default
|
|
193
|
-
};
|
|
194
|
-
//# sourceMappingURL=index.js.map
|
|
1
|
+
import{BaseController as C,generateId as I}from"@ailuracode/alpine-core";function K(r,e){return r==="single"?{mode:r,value:typeof e=="string"?e:null,selectedKeys:[],keys:[],disabledKeys:new Set}:{mode:r,value:null,selectedKeys:Array.isArray(e)?[...e]:[],keys:[],disabledKeys:new Set}}function x(r,e,n){r.keys=e,r.disabledKeys=new Set(n),r.value!==null&&(!r.keys.includes(r.value)||r.disabledKeys.has(r.value))&&(r.value=null),r.selectedKeys.some(o=>!r.keys.includes(o)||r.disabledKeys.has(o))&&(r.selectedKeys=r.selectedKeys.filter(o=>r.keys.includes(o)&&!r.disabledKeys.has(o)))}function h(r){return r.items.filter(e=>!e.disabled)}function G(r,e){return h(r).findIndex(n=>n.id===e)}function S(r,e){if(!e)return[];let n=Array.isArray(e)?e:[e];return r==="single"&&n.length>0?[n[0]]:n}function y(r={}){let e=r.mode??"single";return{mode:e,open:{},activeItemId:null,items:[],defaultOpen:S(e,r.defaultOpen),onChange:r.onChange}}function k(r){return{...r,open:{...r.open},items:r.items.map(e=>({...e})),defaultOpen:[...r.defaultOpen]}}function g(r){return r.mode==="single"?r.value===null?[]:[r.value]:r.selectedKeys}function a(r,e){let n=new Set(g(e)),o={};for(let t of r.items)o[t.id]=n.has(t.id);r.open=o}var p=class extends C{#e={};#o={};constructor(e){super(e??I("accordion"))}hasGroup(e){return e in this.#e}snapshotGroups(){let e={};for(let[n,o]of Object.entries(this.#e))e[n]=k(o);return e}register(e,n={}){if(this.isDestroyed)return;let o=y(n);this.#e[e]=o;let t=o.mode==="single"?o.defaultOpen[0]??null:o.defaultOpen,i=K(o.mode,t);this.#o[e]=i,this.#r(e),a(o,i),this.#n(e,"initialization")}unregister(e){this.isDestroyed||(this.#n(e,"initialization"),delete this.#o[e],delete this.#e[e])}registerItem(e,n,o=!1){if(this.isDestroyed)return;let t=this.#t(e),i=t.items.find(d=>d.id===n);if(i){i.disabled=o,this.#n(e,"user");return}if(t.items.push({id:n,disabled:o}),this.#r(e),t.defaultOpen.includes(n)&&!o){this.open(e,n),t.activeItemId??=n;return}this.#n(e,"user")}unregisterItem(e,n){if(this.isDestroyed)return;let o=this.#e[e],t=this.#o[e];o&&t&&(o.items=o.items.filter(i=>i.id!==n),this.#r(e),a(o,t),this.#n(e,"user"))}open(e,n){if(this.isDestroyed)return;let o=this.#e[e],t=this.#o[e],i=o?.items.find(d=>d.id===n);!(o&&t&&i)||i.disabled||(o.mode==="single"?(t.mode="single",t.value=n,t.selectedKeys=[]):g(t).includes(n)||(t.mode="multiple",t.value=null,t.selectedKeys=[...g(t),n]),a(o,t),this.#n(e,"user"),o.onChange?.(this.openIds(e)))}close(e,n){if(this.isDestroyed)return;let o=this.#e[e],t=this.#o[e];o&&t&&o.open[n]&&(o.mode==="single"?(t.value=null,t.selectedKeys=[]):t.selectedKeys=g(t).filter(i=>i!==n),a(o,t),this.#n(e,"user"),o.onChange?.(this.openIds(e)))}toggle(e,n){this.#e[e]&&(this.isOpen(e,n)?this.close(e,n):this.open(e,n))}isOpen(e,n){let o=this.#o[e];return o?o.mode==="single"?o.value===n:o.selectedKeys.includes(n):!1}openIds(e){let n=this.#o[e];return n?[...g(n)]:[]}activeItem(e){return this.#e[e]?.activeItemId??null}setActiveItem(e,n){if(this.isDestroyed)return;let o=this.#e[e];if(!o)return;if(n===null){o.activeItemId=null,this.#n(e,"user");return}let t=o.items.find(i=>i.id===n);t&&!t.disabled&&(o.activeItemId=n,this.#n(e,"user"))}handleKeydown(e,n){let o=this.#e[e];if(!o)return;let t=h(o);if(t.length===0)return;o.activeItemId||(o.activeItemId=t[0]?.id??null);let i=o.activeItemId?G(o,o.activeItemId):0;switch(n.key){case"ArrowDown":n.preventDefault(),o.activeItemId=t[(i+1)%t.length]?.id??null;break;case"ArrowUp":n.preventDefault(),o.activeItemId=t[(i-1+t.length)%t.length]?.id??null;break;case"Home":n.preventDefault(),o.activeItemId=t[0]?.id??null;break;case"End":n.preventDefault(),o.activeItemId=t[t.length-1]?.id??null;break;default:break}this.#n(e,"user")}triggerProps(e,n){let o=this.isOpen(e,n),t=this.activeItem(e)===n;return{"aria-expanded":o,"aria-controls":`${e}-panel-${n}`,id:`${e}-trigger-${n}`,tabindex:t?0:-1}}panelProps(e,n){let o=this.isOpen(e,n);return{id:`${e}-panel-${n}`,role:"region","aria-labelledby":`${e}-trigger-${n}`,"aria-hidden":o?void 0:!0}}toStore(){return{groups:this.#e,register:(e,n)=>this.register(e,n),unregister:e=>this.unregister(e),registerItem:(e,n,o)=>this.registerItem(e,n,o),unregisterItem:(e,n)=>this.unregisterItem(e,n),open:(e,n)=>this.open(e,n),close:(e,n)=>this.close(e,n),toggle:(e,n)=>this.toggle(e,n),isOpen:(e,n)=>this.isOpen(e,n),openIds:e=>this.openIds(e),activeItem:e=>this.activeItem(e),setActiveItem:(e,n)=>this.setActiveItem(e,n),handleKeydown:(e,n)=>this.handleKeydown(e,n),triggerProps:(e,n)=>this.triggerProps(e,n),panelProps:(e,n)=>this.panelProps(e,n),destroy:()=>this.destroy()}}#t(e){return this.#e[e]??=y(),this.#e[e]}#n(e,n){this.emit("change",{groupId:e,openIds:this.openIds(e),source:n})}#r(e){let n=this.#e[e],o=this.#o[e];if(!(n&&o))return;let t=n.items.map(d=>d.id),i=n.items.filter(d=>d.disabled).map(d=>d.id);x(o,t,i)}};function D(r){return new p(r)}import{bridgeControllerStore as R}from"@ailuracode/alpine-core";var A="accordion",f="accordion";function m(r={}){let e=r.storeKey??A,n=r.magicKey??r.storeKey??f;return function(t){let i=t,d=new p(r.id),v={...d.toStore(),groups:{}};R({alpine:i,storeKey:e,magicKey:n,store:v,controller:d,packageName:"accordion",subscribe:l=>(l.isOpen=(s,c)=>l.groups[s]?.open[c]??!1,l.openIds=s=>{let c=l.groups[s]?.open??{};return Object.entries(c).filter(([,u])=>u).map(([u])=>u)},l.activeItem=s=>l.groups[s]?.activeItemId??null,l.triggerProps=(s,c)=>{let u=l.isOpen(s,c),O=l.activeItem(s)===c;return{"aria-expanded":u,"aria-controls":`${s}-panel-${c}`,id:`${s}-trigger-${c}`,tabindex:O?0:-1}},l.panelProps=(s,c)=>{let u=l.isOpen(s,c);return{id:`${s}-panel-${c}`,role:"region","aria-labelledby":`${s}-trigger-${c}`,"aria-hidden":u?void 0:!0}},d.on("change",()=>{let s=d.snapshotGroups();for(let c of Object.keys(s))l.groups[c]=s[c];for(let c of Object.keys(l.groups))c in s||delete l.groups[c]}))})}}function E(r){return r}function P(r,e){for(let n of Object.keys(e))r[n]=e[n];for(let n of Object.keys(r))n in e||delete r[n]}function _(){return b(new p)}function b(r){let e={},n={...r.toStore(),groups:e};return r.on("change",()=>{P(e,r.snapshotGroups())}),n}export{p as AccordionController,f as DEFAULT_ACCORDION_MAGIC_KEY,A as DEFAULT_ACCORDION_STORE_KEY,E as accordionOptions,m as accordionPlugin,D as createAccordionController,_ as createAccordionStore,b as createAccordionStoreFromController,m as default};
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ailuracode/alpine-accordion",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Alpine.js headless accessible accordion store",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "ailuracode",
|
|
8
|
+
"sideEffects": false,
|
|
8
9
|
"publishConfig": {
|
|
9
10
|
"access": "public"
|
|
10
11
|
},
|
|
@@ -31,7 +32,13 @@
|
|
|
31
32
|
}
|
|
32
33
|
},
|
|
33
34
|
"types": "./dist/global.d.ts",
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/alpinejs": "^3.13.11",
|
|
37
|
+
"alpinejs": "^3.15.12",
|
|
38
|
+
"@ailuracode/alpine-core": "0.3.0"
|
|
39
|
+
},
|
|
34
40
|
"peerDependencies": {
|
|
41
|
+
"@ailuracode/alpine-core": "^0.3.0",
|
|
35
42
|
"alpinejs": "^3.0.0",
|
|
36
43
|
"@types/alpinejs": "^3.13.11"
|
|
37
44
|
},
|
|
@@ -48,8 +55,30 @@
|
|
|
48
55
|
"accessibility",
|
|
49
56
|
"headless"
|
|
50
57
|
],
|
|
58
|
+
"toolkit": {
|
|
59
|
+
"bundleBudget": {
|
|
60
|
+
"category": "small-feature"
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"size-limit": [
|
|
64
|
+
{
|
|
65
|
+
"name": "full surface",
|
|
66
|
+
"path": "dist/index.js",
|
|
67
|
+
"import": "*",
|
|
68
|
+
"ignore": [
|
|
69
|
+
"alpinejs",
|
|
70
|
+
"@ailuracode/alpine-core",
|
|
71
|
+
"@types/alpinejs"
|
|
72
|
+
],
|
|
73
|
+
"gzip": true,
|
|
74
|
+
"brotli": true,
|
|
75
|
+
"limit": "2.3 kB"
|
|
76
|
+
}
|
|
77
|
+
],
|
|
51
78
|
"scripts": {
|
|
52
|
-
"build": "tsup src/index.ts --format esm --dts --clean --
|
|
53
|
-
"
|
|
79
|
+
"build": "tsup src/index.ts --format esm --dts --clean --out-dir dist --minify && cp src/global.d.ts dist/global.d.ts",
|
|
80
|
+
"size": "size-limit",
|
|
81
|
+
"test": "vitest run --config ../../vitest.config.ts packages/accordion",
|
|
82
|
+
"test:e2e": "playwright test --config playwright.config.ts"
|
|
54
83
|
}
|
|
55
84
|
}
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/store.ts","../src/index.ts"],"sourcesContent":["export type AccordionMode = \"single\" | \"multiple\";\n\nexport type AccordionItem = {\n id: string;\n disabled: boolean;\n};\n\nexport type AccordionGroupOptions = {\n mode?: AccordionMode;\n /** Item id or ids open on init. In `single` mode only the first id is used. */\n defaultOpen?: string | string[];\n onChange?: (openIds: string[]) => void;\n};\n\nexport type AccordionGroup = {\n mode: AccordionMode;\n open: Record<string, boolean>;\n activeItemId: string | null;\n items: AccordionItem[];\n defaultOpen: string[];\n onChange?: (openIds: string[]) => void;\n};\n\nexport type AccordionStore = {\n /** Reactive registry of accordion groups. */\n groups: Record<string, AccordionGroup>;\n register(accordionId: string, options?: AccordionGroupOptions): void;\n unregister(accordionId: string): void;\n registerItem(accordionId: string, itemId: string, disabled?: boolean): void;\n unregisterItem(accordionId: string, itemId: string): void;\n open(accordionId: string, itemId: string): void;\n close(accordionId: string, itemId: string): void;\n toggle(accordionId: string, itemId: string): void;\n isOpen(accordionId: string, itemId: string): boolean;\n openIds(accordionId: string): string[];\n activeItem(accordionId: string): string | null;\n setActiveItem(accordionId: string, itemId: string | null): void;\n handleKeydown(accordionId: string, event: KeyboardEvent): void;\n triggerProps(\n accordionId: string,\n itemId: string\n ): Record<string, string | number | boolean | undefined>;\n panelProps(accordionId: string, itemId: string): Record<string, string | boolean | undefined>;\n destroy(): void;\n};\n\nfunction enabledItems(group: AccordionGroup): AccordionItem[] {\n return group.items.filter((item) => !item.disabled);\n}\n\nfunction itemIndex(group: AccordionGroup, itemId: string): number {\n return enabledItems(group).findIndex((item) => item.id === itemId);\n}\n\nfunction notify(group: AccordionGroup): void {\n group.onChange?.(\n Object.entries(group.open)\n .filter(([, isOpen]) => isOpen)\n .map(([id]) => id)\n );\n}\n\nfunction normalizeDefaultOpen(mode: AccordionMode, value?: string | string[]): string[] {\n if (!value) {\n return [];\n }\n\n const ids = Array.isArray(value) ? value : [value];\n return mode === \"single\" && ids.length > 0 ? [ids[0]] : ids;\n}\n\nfunction createGroup(options: AccordionGroupOptions = {}): AccordionGroup {\n const mode = options.mode ?? \"single\";\n return {\n mode,\n open: {},\n activeItemId: null,\n items: [],\n defaultOpen: normalizeDefaultOpen(mode, options.defaultOpen),\n onChange: options.onChange,\n };\n}\n\n/** Creates the headless accordion store. */\nexport function createAccordionStore(): AccordionStore {\n function getOrCreate(store: AccordionStore, accordionId: string): AccordionGroup {\n store.groups[accordionId] ??= createGroup();\n return store.groups[accordionId];\n }\n\n const store: AccordionStore = {\n groups: {},\n\n register(accordionId, options = {}) {\n this.groups[accordionId] = createGroup(options);\n },\n\n unregister(accordionId) {\n delete this.groups[accordionId];\n },\n\n registerItem(accordionId, itemId, disabled = false) {\n const group = getOrCreate(this, accordionId);\n const existing = group.items.find((item) => item.id === itemId);\n if (existing) {\n existing.disabled = disabled;\n return;\n }\n group.items.push({ id: itemId, disabled });\n\n if (group.defaultOpen.includes(itemId) && !disabled) {\n this.open(accordionId, itemId);\n group.activeItemId ??= itemId;\n }\n },\n\n unregisterItem(accordionId, itemId) {\n const group = this.groups[accordionId];\n if (!group) {\n return;\n }\n\n group.items = group.items.filter((item) => item.id !== itemId);\n delete group.open[itemId];\n },\n\n open(accordionId, itemId) {\n const group = this.groups[accordionId];\n const item = group?.items.find((entry) => entry.id === itemId);\n if (!(group && item) || item.disabled) {\n return;\n }\n\n if (group.mode === \"single\") {\n for (const id of Object.keys(group.open)) {\n if (group.open[id]) {\n group.open[id] = false;\n }\n }\n group.open[itemId] = true;\n } else {\n group.open[itemId] = true;\n }\n\n notify(group);\n },\n\n close(accordionId, itemId) {\n const group = this.groups[accordionId];\n if (!group?.open[itemId]) {\n return;\n }\n\n group.open[itemId] = false;\n notify(group);\n },\n\n toggle(accordionId, itemId) {\n if (this.isOpen(accordionId, itemId)) {\n this.close(accordionId, itemId);\n } else {\n this.open(accordionId, itemId);\n }\n },\n\n isOpen(accordionId, itemId) {\n return this.groups[accordionId]?.open[itemId] ?? false;\n },\n\n openIds(accordionId) {\n const open = this.groups[accordionId]?.open ?? {};\n return Object.entries(open)\n .filter(([, isOpen]) => isOpen)\n .map(([id]) => id);\n },\n\n activeItem(accordionId) {\n return this.groups[accordionId]?.activeItemId ?? null;\n },\n\n setActiveItem(accordionId, itemId) {\n const group = this.groups[accordionId];\n if (!group) {\n return;\n }\n\n if (itemId === null) {\n group.activeItemId = null;\n return;\n }\n\n const item = group.items.find((entry) => entry.id === itemId);\n if (item && !item.disabled) {\n group.activeItemId = itemId;\n }\n },\n\n handleKeydown(accordionId, event) {\n const group = this.groups[accordionId];\n if (!group) {\n return;\n }\n\n const items = enabledItems(group);\n if (items.length === 0) {\n return;\n }\n\n if (!group.activeItemId) {\n group.activeItemId = items[0]?.id ?? null;\n }\n\n const currentIndex = group.activeItemId ? itemIndex(group, group.activeItemId) : 0;\n\n switch (event.key) {\n case \"ArrowDown\":\n event.preventDefault();\n group.activeItemId = items[(currentIndex + 1) % items.length]?.id ?? null;\n break;\n case \"ArrowUp\":\n event.preventDefault();\n group.activeItemId = items[(currentIndex - 1 + items.length) % items.length]?.id ?? null;\n break;\n case \"Home\":\n event.preventDefault();\n group.activeItemId = items[0]?.id ?? null;\n break;\n case \"End\":\n event.preventDefault();\n group.activeItemId = items[items.length - 1]?.id ?? null;\n break;\n default:\n break;\n }\n },\n\n triggerProps(accordionId, itemId) {\n const open = this.isOpen(accordionId, itemId);\n const active = this.activeItem(accordionId) === itemId;\n return {\n \"aria-expanded\": open,\n \"aria-controls\": `${accordionId}-panel-${itemId}`,\n id: `${accordionId}-trigger-${itemId}`,\n tabindex: active ? 0 : -1,\n };\n },\n\n panelProps(accordionId, itemId) {\n const open = this.isOpen(accordionId, itemId);\n return {\n id: `${accordionId}-panel-${itemId}`,\n role: \"region\",\n \"aria-labelledby\": `${accordionId}-trigger-${itemId}`,\n \"aria-hidden\": open ? undefined : true,\n };\n },\n\n destroy() {\n this.groups = {};\n },\n };\n\n return store;\n}\n","import type AlpineType from \"alpinejs\";\nimport { type AccordionStore, createAccordionStore } from \"./store.js\";\n\nexport {\n type AccordionGroupOptions,\n type AccordionMode,\n type AccordionStore,\n createAccordionStore,\n} from \"./store.js\";\n\n/** Alpine.js accordion plugin. Registers `$store.accordion`. */\nexport default function accordionPlugin(): AlpineType.PluginCallback {\n return function registerAccordion(Alpine) {\n const store = createAccordionStore();\n Alpine.store(\"accordion\", store);\n Alpine.magic(\"accordion\", () => Alpine.store(\"accordion\"));\n };\n}\n\ndeclare global {\n namespace Alpine {\n interface Stores {\n accordion: AccordionStore;\n }\n interface Magics<T> {\n $accordion: AccordionStore;\n }\n }\n}\n"],"mappings":"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","names":["store"]}
|