@ailuracode/alpine-menu 0.1.0 → 0.2.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 +12 -1
- package/dist/global.d.ts +10 -30
- package/dist/index.d.ts +11 -0
- package/dist/index.js +51 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @ailuracode/alpine-menu
|
|
2
2
|
|
|
3
|
-
Headless accessible menu store for Alpine.js — dropdowns, context menus, keyboard navigation, and roving tabindex. No markup or CSS included.
|
|
3
|
+
Headless accessible menu store for Alpine.js — dropdowns, context menus, keyboard navigation, and roving tabindex. **Only one menu open at a time by default.** No markup or CSS included.
|
|
4
4
|
|
|
5
5
|
**[Full documentation →](../../docs/plugins/menu.md)**
|
|
6
6
|
|
|
@@ -22,9 +22,20 @@ Alpine.plugin(
|
|
|
22
22
|
);
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
+
## Exclusive mode
|
|
26
|
+
|
|
27
|
+
Opening a menu closes all others by default (`exclusive: true`). Pass `exclusive: false` to allow multiple open menus, or use per-menu `group` for menubar-style exclusivity:
|
|
28
|
+
|
|
29
|
+
```js
|
|
30
|
+
menu({ exclusive: false });
|
|
31
|
+
$store.menu.register("file", { group: "menubar-1" });
|
|
32
|
+
$store.menu.register("edit", { group: "menubar-1" });
|
|
33
|
+
```
|
|
34
|
+
|
|
25
35
|
## Store API
|
|
26
36
|
|
|
27
37
|
```js
|
|
38
|
+
$store.menu.register("user-menu", { onSelect: (id) => {} });
|
|
28
39
|
$store.menu.open("user-menu");
|
|
29
40
|
$store.menu.close("user-menu");
|
|
30
41
|
$store.menu.toggle("user-menu");
|
package/dist/global.d.ts
CHANGED
|
@@ -1,49 +1,29 @@
|
|
|
1
1
|
/// <reference types="@types/alpinejs" />
|
|
2
2
|
|
|
3
|
-
export type
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
toggle(id: string): void;
|
|
10
|
-
isOpen(id: string): boolean;
|
|
11
|
-
activeItem(id: string): string | null;
|
|
12
|
-
register(id: string, options?: import("./store.js").MenuInstanceOptions): void;
|
|
13
|
-
unregister(id: string): void;
|
|
14
|
-
registerItem(
|
|
15
|
-
menuId: string,
|
|
16
|
-
itemId: string,
|
|
17
|
-
options?: import("./store.js").MenuItemOptions
|
|
18
|
-
): void;
|
|
19
|
-
unregisterItem(menuId: string, itemId: string): void;
|
|
20
|
-
bindMenu(menuId: string, container: HTMLElement | null): void;
|
|
21
|
-
bindTrigger(menuId: string, trigger: HTMLElement | null): void;
|
|
22
|
-
handleOutsideClick(menuId: string, event: MouseEvent): void;
|
|
23
|
-
setActiveItem(menuId: string, itemId: string | null): void;
|
|
24
|
-
selectItem(menuId: string, itemId: string): void;
|
|
25
|
-
handleKeydown(menuId: string, event: KeyboardEvent): void;
|
|
26
|
-
itemProps(menuId: string, itemId: string): Record<string, string | number | boolean | undefined>;
|
|
27
|
-
menuProps(menuId: string): Record<string, string | boolean | undefined>;
|
|
28
|
-
destroy(): void;
|
|
29
|
-
}
|
|
3
|
+
export type {
|
|
4
|
+
MenuInstanceOptions,
|
|
5
|
+
MenuItemOptions,
|
|
6
|
+
MenuOrientation,
|
|
7
|
+
MenuStore,
|
|
8
|
+
} from "./store.js";
|
|
30
9
|
|
|
31
10
|
export interface MenuPluginOptions {
|
|
11
|
+
exclusive?: boolean;
|
|
32
12
|
onLockChange?: (locked: boolean) => void;
|
|
33
13
|
}
|
|
34
14
|
|
|
35
15
|
export function menuOptions<const T extends MenuPluginOptions>(options: T): T;
|
|
36
|
-
export function createMenuStore(config?:
|
|
16
|
+
export function createMenuStore(config?: MenuPluginOptions): import("./store.js").MenuStore;
|
|
37
17
|
|
|
38
18
|
export default function menuPlugin(options?: MenuPluginOptions): import("alpinejs").PluginCallback;
|
|
39
19
|
|
|
40
20
|
declare global {
|
|
41
21
|
namespace Alpine {
|
|
42
22
|
interface Stores {
|
|
43
|
-
menu: MenuStore;
|
|
23
|
+
menu: import("./store.js").MenuStore;
|
|
44
24
|
}
|
|
45
25
|
interface Magics<T> {
|
|
46
|
-
$menu: MenuStore;
|
|
26
|
+
$menu: import("./store.js").MenuStore;
|
|
47
27
|
}
|
|
48
28
|
}
|
|
49
29
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ type MenuInstance = {
|
|
|
11
11
|
activeItemId: string | null;
|
|
12
12
|
orientation: MenuOrientation;
|
|
13
13
|
closeOnSelect: boolean;
|
|
14
|
+
group: string | null;
|
|
14
15
|
items: MenuItemState[];
|
|
15
16
|
container: HTMLElement | null;
|
|
16
17
|
trigger: HTMLElement | null;
|
|
@@ -25,6 +26,8 @@ type MenuItemOptions = {
|
|
|
25
26
|
type MenuInstanceOptions = {
|
|
26
27
|
orientation?: MenuOrientation;
|
|
27
28
|
closeOnSelect?: boolean;
|
|
29
|
+
/** When store `exclusive` is `false`, only one menu per group may be open at a time. */
|
|
30
|
+
group?: string;
|
|
28
31
|
onOpen?: () => void;
|
|
29
32
|
onClose?: () => void;
|
|
30
33
|
onSelect?: (itemId: string) => void;
|
|
@@ -47,17 +50,25 @@ type MenuStore = {
|
|
|
47
50
|
setActiveItem(menuId: string, itemId: string | null): void;
|
|
48
51
|
selectItem(menuId: string, itemId: string): void;
|
|
49
52
|
handleKeydown(menuId: string, event: KeyboardEvent): void;
|
|
53
|
+
/** Close open menus on outside click — pass `menuIds` when wiring multiple menus on one page. */
|
|
54
|
+
handleWindowOutsideClick(event: MouseEvent, menuIds?: readonly string[]): void;
|
|
55
|
+
/** Route keyboard events to the first open menu in `menuIds` (defaults to all registered). */
|
|
56
|
+
handleWindowKeydown(event: KeyboardEvent, menuIds?: readonly string[]): void;
|
|
50
57
|
itemProps(menuId: string, itemId: string): Record<string, string | number | boolean | undefined>;
|
|
51
58
|
menuProps(menuId: string): Record<string, string | boolean | undefined>;
|
|
52
59
|
destroy(): void;
|
|
53
60
|
};
|
|
54
61
|
type MenuStoreConfig = {
|
|
62
|
+
/** When true (default), opening a menu closes all other open menus. */
|
|
63
|
+
exclusive?: boolean;
|
|
55
64
|
onLockChange?: (locked: boolean) => void;
|
|
56
65
|
};
|
|
57
66
|
/** Creates the headless menu store. */
|
|
58
67
|
declare function createMenuStore(config?: MenuStoreConfig): MenuStore;
|
|
59
68
|
|
|
60
69
|
interface MenuPluginOptions {
|
|
70
|
+
/** When true (default), opening a menu closes all other open menus. */
|
|
71
|
+
exclusive?: boolean;
|
|
61
72
|
onLockChange?: (locked: boolean) => void;
|
|
62
73
|
}
|
|
63
74
|
/** Builds typed menu plugin options. */
|
package/dist/index.js
CHANGED
|
@@ -27,6 +27,7 @@ function createInstance(options = {}) {
|
|
|
27
27
|
activeItemId: null,
|
|
28
28
|
orientation: options.orientation ?? "vertical",
|
|
29
29
|
closeOnSelect: options.closeOnSelect ?? true,
|
|
30
|
+
group: options.group ?? null,
|
|
30
31
|
items: [],
|
|
31
32
|
container: null,
|
|
32
33
|
trigger: null,
|
|
@@ -89,6 +90,7 @@ function handleMenuKeydown(menuId, instance, event, selectItem, close) {
|
|
|
89
90
|
}
|
|
90
91
|
}
|
|
91
92
|
function createMenuStore(config = {}) {
|
|
93
|
+
const exclusive = config.exclusive ?? true;
|
|
92
94
|
let lockCount = 0;
|
|
93
95
|
function setLock(locked) {
|
|
94
96
|
if (locked) {
|
|
@@ -110,6 +112,34 @@ function createMenuStore(config = {}) {
|
|
|
110
112
|
store2.instances[id] ??= createInstance();
|
|
111
113
|
return store2.instances[id];
|
|
112
114
|
}
|
|
115
|
+
function closeMenu(menuStore, id, suppressLock = false) {
|
|
116
|
+
const instance = menuStore.instances[id];
|
|
117
|
+
if (!instance?.open) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
instance.open = false;
|
|
121
|
+
if (!suppressLock) {
|
|
122
|
+
setLock(false);
|
|
123
|
+
}
|
|
124
|
+
instance.onClose?.();
|
|
125
|
+
}
|
|
126
|
+
function closeOtherMenus(menuStore, openingId) {
|
|
127
|
+
const opening = menuStore.instances[openingId];
|
|
128
|
+
const openingGroup = opening?.group ?? null;
|
|
129
|
+
let closedCount = 0;
|
|
130
|
+
for (const menuId of Object.keys(menuStore.instances)) {
|
|
131
|
+
if (menuId === openingId || !menuStore.isOpen(menuId)) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
const other = menuStore.instances[menuId];
|
|
135
|
+
const shouldClose = exclusive !== false || openingGroup !== null && other.group === openingGroup;
|
|
136
|
+
if (shouldClose) {
|
|
137
|
+
closeMenu(menuStore, menuId, true);
|
|
138
|
+
closedCount++;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return closedCount;
|
|
142
|
+
}
|
|
113
143
|
const store = {
|
|
114
144
|
instances: {},
|
|
115
145
|
register(id, options = {}) {
|
|
@@ -150,22 +180,19 @@ function createMenuStore(config = {}) {
|
|
|
150
180
|
if (instance.open) {
|
|
151
181
|
return;
|
|
152
182
|
}
|
|
183
|
+
const closedCount = closeOtherMenus(this, id);
|
|
153
184
|
instance.open = true;
|
|
154
185
|
if (!instance.activeItemId) {
|
|
155
186
|
instance.activeItemId = firstItem(instance);
|
|
156
187
|
}
|
|
157
|
-
|
|
188
|
+
if (closedCount === 0) {
|
|
189
|
+
setLock(true);
|
|
190
|
+
}
|
|
158
191
|
instance.onOpen?.();
|
|
159
192
|
queueMicrotask(() => focusActiveMenu(instance));
|
|
160
193
|
},
|
|
161
194
|
close(id) {
|
|
162
|
-
|
|
163
|
-
if (!instance?.open) {
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
instance.open = false;
|
|
167
|
-
setLock(false);
|
|
168
|
-
instance.onClose?.();
|
|
195
|
+
closeMenu(this, id);
|
|
169
196
|
},
|
|
170
197
|
toggle(id) {
|
|
171
198
|
if (this.isOpen(id)) {
|
|
@@ -241,6 +268,21 @@ function createMenuStore(config = {}) {
|
|
|
241
268
|
queueMicrotask(() => focusActiveMenu(instance));
|
|
242
269
|
}
|
|
243
270
|
},
|
|
271
|
+
handleWindowOutsideClick(event, menuIds) {
|
|
272
|
+
const ids = menuIds ?? Object.keys(this.instances);
|
|
273
|
+
for (const menuId of ids) {
|
|
274
|
+
this.handleOutsideClick(menuId, event);
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
handleWindowKeydown(event, menuIds) {
|
|
278
|
+
const ids = menuIds ?? Object.keys(this.instances);
|
|
279
|
+
for (const menuId of ids) {
|
|
280
|
+
if (this.isOpen(menuId)) {
|
|
281
|
+
this.handleKeydown(menuId, event);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
},
|
|
244
286
|
itemProps(menuId, itemId) {
|
|
245
287
|
const instance = this.instances[menuId];
|
|
246
288
|
const item = instance?.items.find((entry) => entry.id === itemId);
|
|
@@ -276,6 +318,7 @@ function menuOptions(options) {
|
|
|
276
318
|
function menuPlugin(options = {}) {
|
|
277
319
|
return function registerMenu(Alpine) {
|
|
278
320
|
const store = createMenuStore({
|
|
321
|
+
exclusive: options.exclusive,
|
|
279
322
|
onLockChange: options.onLockChange
|
|
280
323
|
});
|
|
281
324
|
Alpine.store("menu", store);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/store.ts","../src/index.ts"],"sourcesContent":["export type MenuOrientation = \"vertical\" | \"horizontal\";\n\nexport type MenuItemState = {\n id: string;\n disabled: boolean;\n parentId: string | null;\n};\n\nexport type MenuInstance = {\n open: boolean;\n activeItemId: string | null;\n orientation: MenuOrientation;\n closeOnSelect: boolean;\n items: MenuItemState[];\n container: HTMLElement | null;\n trigger: HTMLElement | null;\n onOpen?: () => void;\n onClose?: () => void;\n onSelect?: (itemId: string) => void;\n};\n\nexport type MenuItemOptions = {\n disabled?: boolean;\n parentId?: string | null;\n};\n\nexport type MenuInstanceOptions = {\n orientation?: MenuOrientation;\n closeOnSelect?: boolean;\n onOpen?: () => void;\n onClose?: () => void;\n onSelect?: (itemId: string) => void;\n};\n\nexport type MenuStore = {\n /** Reactive registry of menu instances. */\n instances: Record<string, MenuInstance>;\n open(id: string): void;\n close(id: string): void;\n toggle(id: string): void;\n isOpen(id: string): boolean;\n activeItem(id: string): string | null;\n register(id: string, options?: MenuInstanceOptions): void;\n unregister(id: string): void;\n registerItem(menuId: string, itemId: string, options?: MenuItemOptions): void;\n unregisterItem(menuId: string, itemId: string): void;\n bindMenu(menuId: string, container: HTMLElement | null): void;\n bindTrigger(menuId: string, trigger: HTMLElement | null): void;\n handleOutsideClick(menuId: string, event: MouseEvent): void;\n setActiveItem(menuId: string, itemId: string | null): void;\n selectItem(menuId: string, itemId: string): void;\n handleKeydown(menuId: string, event: KeyboardEvent): void;\n itemProps(menuId: string, itemId: string): Record<string, string | number | boolean | undefined>;\n menuProps(menuId: string): Record<string, string | boolean | undefined>;\n destroy(): void;\n};\n\ntype MenuStoreConfig = {\n onLockChange?: (locked: boolean) => void;\n};\n\nfunction enabledItems(instance: MenuInstance): MenuItemState[] {\n return instance.items.filter((item) => !item.disabled);\n}\n\nfunction itemIndex(instance: MenuInstance, itemId: string): number {\n return enabledItems(instance).findIndex((item) => item.id === itemId);\n}\n\nfunction moveActive(instance: MenuInstance, delta: number): string | null {\n const items = enabledItems(instance);\n if (items.length === 0) {\n return null;\n }\n\n const currentIndex = instance.activeItemId ? itemIndex(instance, instance.activeItemId) : -1;\n const nextIndex = (currentIndex + delta + items.length) % items.length;\n return items[nextIndex]?.id ?? null;\n}\n\nfunction firstItem(instance: MenuInstance): string | null {\n return enabledItems(instance)[0]?.id ?? null;\n}\n\nfunction lastItem(instance: MenuInstance): string | null {\n const items = enabledItems(instance);\n return items[items.length - 1]?.id ?? null;\n}\n\nfunction createInstance(options: MenuInstanceOptions = {}): MenuInstance {\n return {\n open: false,\n activeItemId: null,\n orientation: options.orientation ?? \"vertical\",\n closeOnSelect: options.closeOnSelect ?? true,\n items: [],\n container: null,\n trigger: null,\n onOpen: options.onOpen,\n onClose: options.onClose,\n onSelect: options.onSelect,\n };\n}\n\nfunction focusActiveItem(container: HTMLElement | null): void {\n if (!container) {\n return;\n }\n\n const active = container.querySelector<HTMLElement>('[role=\"menuitem\"][tabindex=\"0\"]');\n active?.focus();\n}\n\nfunction focusActiveMenu(instance: MenuInstance): void {\n focusActiveItem(instance.container);\n}\n\nfunction handleMenuKeydown(\n menuId: string,\n instance: MenuInstance,\n event: KeyboardEvent,\n selectItem: (menuId: string, itemId: string) => void,\n close: (menuId: string) => void\n): void {\n const vertical = instance.orientation === \"vertical\";\n const horizontal = instance.orientation === \"horizontal\";\n\n if (event.key === \"ArrowDown\" && vertical) {\n event.preventDefault();\n instance.activeItemId = moveActive(instance, 1);\n return;\n }\n\n if (event.key === \"ArrowUp\" && vertical) {\n event.preventDefault();\n instance.activeItemId = moveActive(instance, -1);\n return;\n }\n\n if (event.key === \"ArrowRight\" && horizontal) {\n event.preventDefault();\n instance.activeItemId = moveActive(instance, 1);\n return;\n }\n\n if (event.key === \"ArrowLeft\" && horizontal) {\n event.preventDefault();\n instance.activeItemId = moveActive(instance, -1);\n return;\n }\n\n if (event.key === \"Home\") {\n event.preventDefault();\n instance.activeItemId = firstItem(instance);\n return;\n }\n\n if (event.key === \"End\") {\n event.preventDefault();\n instance.activeItemId = lastItem(instance);\n return;\n }\n\n if ((event.key === \"Enter\" || event.key === \" \") && instance.activeItemId) {\n event.preventDefault();\n selectItem(menuId, instance.activeItemId);\n return;\n }\n\n if (event.key === \"Escape\") {\n event.preventDefault();\n close(menuId);\n }\n}\n\n/** Creates the headless menu store. */\nexport function createMenuStore(config: MenuStoreConfig = {}): MenuStore {\n let lockCount = 0;\n\n function setLock(locked: boolean): void {\n if (locked) {\n if (lockCount === 0) {\n config.onLockChange?.(true);\n }\n lockCount++;\n return;\n }\n\n if (lockCount === 0) {\n return;\n }\n\n lockCount--;\n if (lockCount === 0) {\n config.onLockChange?.(false);\n }\n }\n\n function getOrCreate(store: MenuStore, id: string): MenuInstance {\n store.instances[id] ??= createInstance();\n return store.instances[id];\n }\n\n const store: MenuStore = {\n instances: {},\n\n register(id, options = {}) {\n this.instances[id] = createInstance(options);\n },\n\n unregister(id) {\n if (this.isOpen(id)) {\n this.close(id);\n }\n delete this.instances[id];\n },\n\n registerItem(menuId, itemId, options = {}) {\n const instance = getOrCreate(this, menuId);\n const existing = instance.items.find((item) => item.id === itemId);\n if (existing) {\n existing.disabled = options.disabled ?? existing.disabled;\n existing.parentId = options.parentId ?? existing.parentId;\n return;\n }\n\n instance.items.push({\n id: itemId,\n disabled: options.disabled ?? false,\n parentId: options.parentId ?? null,\n });\n },\n\n unregisterItem(menuId, itemId) {\n const instance = this.instances[menuId];\n if (!instance) {\n return;\n }\n\n instance.items = instance.items.filter((item) => item.id !== itemId);\n if (instance.activeItemId === itemId) {\n instance.activeItemId = firstItem(instance);\n }\n },\n\n open(id) {\n const instance = getOrCreate(this, id);\n if (instance.open) {\n return;\n }\n\n instance.open = true;\n if (!instance.activeItemId) {\n instance.activeItemId = firstItem(instance);\n }\n\n setLock(true);\n\n instance.onOpen?.();\n queueMicrotask(() => focusActiveMenu(instance));\n },\n\n close(id) {\n const instance = this.instances[id];\n if (!instance?.open) {\n return;\n }\n\n instance.open = false;\n\n setLock(false);\n\n instance.onClose?.();\n },\n\n toggle(id) {\n if (this.isOpen(id)) {\n this.close(id);\n } else {\n this.open(id);\n }\n },\n\n isOpen(id) {\n return this.instances[id]?.open ?? false;\n },\n\n activeItem(id) {\n return this.instances[id]?.activeItemId ?? null;\n },\n\n setActiveItem(menuId, itemId) {\n const instance = getOrCreate(this, menuId);\n if (itemId === null) {\n instance.activeItemId = null;\n return;\n }\n\n const item = instance.items.find((entry) => entry.id === itemId);\n if (item && !item.disabled) {\n instance.activeItemId = itemId;\n }\n },\n\n bindMenu(menuId, container) {\n const instance = getOrCreate(this, menuId);\n instance.container = container;\n\n if (instance.open) {\n queueMicrotask(() => focusActiveMenu(instance));\n }\n },\n\n bindTrigger(menuId, trigger) {\n const instance = getOrCreate(this, menuId);\n instance.trigger = trigger;\n },\n\n handleOutsideClick(menuId, event) {\n const instance = this.instances[menuId];\n if (!instance?.open) {\n return;\n }\n\n const target = event.target;\n if (!(target instanceof Node)) {\n return;\n }\n\n if (instance.trigger?.contains(target) || instance.container?.contains(target)) {\n return;\n }\n\n this.close(menuId);\n },\n\n selectItem(menuId, itemId) {\n const instance = this.instances[menuId];\n const item = instance?.items.find((entry) => entry.id === itemId);\n if (!(instance && item) || item.disabled) {\n return;\n }\n\n instance.activeItemId = itemId;\n instance.onSelect?.(itemId);\n\n if (instance.closeOnSelect) {\n this.close(menuId);\n }\n },\n\n handleKeydown(menuId, event) {\n const instance = this.instances[menuId];\n if (instance?.open) {\n handleMenuKeydown(\n menuId,\n instance,\n event,\n this.selectItem.bind(this),\n this.close.bind(this)\n );\n queueMicrotask(() => focusActiveMenu(instance));\n }\n },\n\n itemProps(menuId, itemId) {\n const instance = this.instances[menuId];\n const item = instance?.items.find((entry) => entry.id === itemId);\n const active = instance?.activeItemId === itemId;\n\n return {\n role: \"menuitem\",\n tabindex: active ? 0 : -1,\n \"aria-disabled\": item?.disabled ?? false,\n };\n },\n\n menuProps(menuId) {\n const instance = this.instances[menuId];\n return {\n role: \"menu\",\n \"aria-orientation\": instance?.orientation,\n };\n },\n\n destroy() {\n for (const id of Object.keys(this.instances)) {\n this.unregister(id);\n }\n lockCount = 0;\n config.onLockChange?.(false);\n },\n };\n\n return store;\n}\n","import type AlpineType from \"alpinejs\";\nimport { createMenuStore, type MenuStore } from \"./store.js\";\n\nexport {\n createMenuStore,\n type MenuInstanceOptions,\n type MenuItemOptions,\n type MenuOrientation,\n type MenuStore,\n} from \"./store.js\";\n\nexport interface MenuPluginOptions {\n onLockChange?: (locked: boolean) => void;\n}\n\n/** Builds typed menu plugin options. */\nexport function menuOptions<const T extends MenuPluginOptions>(options: T): T {\n return options;\n}\n\n/** Alpine.js menu plugin. Registers `$store.menu`. */\nexport default function menuPlugin(options: MenuPluginOptions = {}): AlpineType.PluginCallback {\n return function registerMenu(Alpine) {\n const store = createMenuStore({\n onLockChange: options.onLockChange,\n });\n Alpine.store(\"menu\", store);\n Alpine.magic(\"menu\", () => Alpine.store(\"menu\"));\n };\n}\n\ndeclare global {\n namespace Alpine {\n interface Stores {\n menu: MenuStore;\n }\n interface Magics<T> {\n $menu: MenuStore;\n }\n }\n}\n"],"mappings":"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","names":["store"]}
|
|
1
|
+
{"version":3,"sources":["../src/store.ts","../src/index.ts"],"sourcesContent":["export type MenuOrientation = \"vertical\" | \"horizontal\";\n\nexport type MenuItemState = {\n id: string;\n disabled: boolean;\n parentId: string | null;\n};\n\nexport type MenuInstance = {\n open: boolean;\n activeItemId: string | null;\n orientation: MenuOrientation;\n closeOnSelect: boolean;\n group: string | null;\n items: MenuItemState[];\n container: HTMLElement | null;\n trigger: HTMLElement | null;\n onOpen?: () => void;\n onClose?: () => void;\n onSelect?: (itemId: string) => void;\n};\n\nexport type MenuItemOptions = {\n disabled?: boolean;\n parentId?: string | null;\n};\n\nexport type MenuInstanceOptions = {\n orientation?: MenuOrientation;\n closeOnSelect?: boolean;\n /** When store `exclusive` is `false`, only one menu per group may be open at a time. */\n group?: string;\n onOpen?: () => void;\n onClose?: () => void;\n onSelect?: (itemId: string) => void;\n};\n\nexport type MenuStore = {\n /** Reactive registry of menu instances. */\n instances: Record<string, MenuInstance>;\n open(id: string): void;\n close(id: string): void;\n toggle(id: string): void;\n isOpen(id: string): boolean;\n activeItem(id: string): string | null;\n register(id: string, options?: MenuInstanceOptions): void;\n unregister(id: string): void;\n registerItem(menuId: string, itemId: string, options?: MenuItemOptions): void;\n unregisterItem(menuId: string, itemId: string): void;\n bindMenu(menuId: string, container: HTMLElement | null): void;\n bindTrigger(menuId: string, trigger: HTMLElement | null): void;\n handleOutsideClick(menuId: string, event: MouseEvent): void;\n setActiveItem(menuId: string, itemId: string | null): void;\n selectItem(menuId: string, itemId: string): void;\n handleKeydown(menuId: string, event: KeyboardEvent): void;\n /** Close open menus on outside click — pass `menuIds` when wiring multiple menus on one page. */\n handleWindowOutsideClick(event: MouseEvent, menuIds?: readonly string[]): void;\n /** Route keyboard events to the first open menu in `menuIds` (defaults to all registered). */\n handleWindowKeydown(event: KeyboardEvent, menuIds?: readonly string[]): void;\n itemProps(menuId: string, itemId: string): Record<string, string | number | boolean | undefined>;\n menuProps(menuId: string): Record<string, string | boolean | undefined>;\n destroy(): void;\n};\n\ntype MenuStoreConfig = {\n /** When true (default), opening a menu closes all other open menus. */\n exclusive?: boolean;\n onLockChange?: (locked: boolean) => void;\n};\n\nfunction enabledItems(instance: MenuInstance): MenuItemState[] {\n return instance.items.filter((item) => !item.disabled);\n}\n\nfunction itemIndex(instance: MenuInstance, itemId: string): number {\n return enabledItems(instance).findIndex((item) => item.id === itemId);\n}\n\nfunction moveActive(instance: MenuInstance, delta: number): string | null {\n const items = enabledItems(instance);\n if (items.length === 0) {\n return null;\n }\n\n const currentIndex = instance.activeItemId ? itemIndex(instance, instance.activeItemId) : -1;\n const nextIndex = (currentIndex + delta + items.length) % items.length;\n return items[nextIndex]?.id ?? null;\n}\n\nfunction firstItem(instance: MenuInstance): string | null {\n return enabledItems(instance)[0]?.id ?? null;\n}\n\nfunction lastItem(instance: MenuInstance): string | null {\n const items = enabledItems(instance);\n return items[items.length - 1]?.id ?? null;\n}\n\nfunction createInstance(options: MenuInstanceOptions = {}): MenuInstance {\n return {\n open: false,\n activeItemId: null,\n orientation: options.orientation ?? \"vertical\",\n closeOnSelect: options.closeOnSelect ?? true,\n group: options.group ?? null,\n items: [],\n container: null,\n trigger: null,\n onOpen: options.onOpen,\n onClose: options.onClose,\n onSelect: options.onSelect,\n };\n}\n\nfunction focusActiveItem(container: HTMLElement | null): void {\n if (!container) {\n return;\n }\n\n const active = container.querySelector<HTMLElement>('[role=\"menuitem\"][tabindex=\"0\"]');\n active?.focus();\n}\n\nfunction focusActiveMenu(instance: MenuInstance): void {\n focusActiveItem(instance.container);\n}\n\nfunction handleMenuKeydown(\n menuId: string,\n instance: MenuInstance,\n event: KeyboardEvent,\n selectItem: (menuId: string, itemId: string) => void,\n close: (menuId: string) => void\n): void {\n const vertical = instance.orientation === \"vertical\";\n const horizontal = instance.orientation === \"horizontal\";\n\n if (event.key === \"ArrowDown\" && vertical) {\n event.preventDefault();\n instance.activeItemId = moveActive(instance, 1);\n return;\n }\n\n if (event.key === \"ArrowUp\" && vertical) {\n event.preventDefault();\n instance.activeItemId = moveActive(instance, -1);\n return;\n }\n\n if (event.key === \"ArrowRight\" && horizontal) {\n event.preventDefault();\n instance.activeItemId = moveActive(instance, 1);\n return;\n }\n\n if (event.key === \"ArrowLeft\" && horizontal) {\n event.preventDefault();\n instance.activeItemId = moveActive(instance, -1);\n return;\n }\n\n if (event.key === \"Home\") {\n event.preventDefault();\n instance.activeItemId = firstItem(instance);\n return;\n }\n\n if (event.key === \"End\") {\n event.preventDefault();\n instance.activeItemId = lastItem(instance);\n return;\n }\n\n if ((event.key === \"Enter\" || event.key === \" \") && instance.activeItemId) {\n event.preventDefault();\n selectItem(menuId, instance.activeItemId);\n return;\n }\n\n if (event.key === \"Escape\") {\n event.preventDefault();\n close(menuId);\n }\n}\n\n/** Creates the headless menu store. */\nexport function createMenuStore(config: MenuStoreConfig = {}): MenuStore {\n const exclusive = config.exclusive ?? true;\n let lockCount = 0;\n\n function setLock(locked: boolean): void {\n if (locked) {\n if (lockCount === 0) {\n config.onLockChange?.(true);\n }\n lockCount++;\n return;\n }\n\n if (lockCount === 0) {\n return;\n }\n\n lockCount--;\n if (lockCount === 0) {\n config.onLockChange?.(false);\n }\n }\n\n function getOrCreate(store: MenuStore, id: string): MenuInstance {\n store.instances[id] ??= createInstance();\n return store.instances[id];\n }\n\n function closeMenu(menuStore: MenuStore, id: string, suppressLock = false): void {\n const instance = menuStore.instances[id];\n if (!instance?.open) {\n return;\n }\n\n instance.open = false;\n\n if (!suppressLock) {\n setLock(false);\n }\n\n instance.onClose?.();\n }\n\n function closeOtherMenus(menuStore: MenuStore, openingId: string): number {\n const opening = menuStore.instances[openingId];\n const openingGroup = opening?.group ?? null;\n let closedCount = 0;\n\n for (const menuId of Object.keys(menuStore.instances)) {\n if (menuId === openingId || !menuStore.isOpen(menuId)) {\n continue;\n }\n\n const other = menuStore.instances[menuId];\n const shouldClose =\n exclusive !== false || (openingGroup !== null && other.group === openingGroup);\n\n if (shouldClose) {\n closeMenu(menuStore, menuId, true);\n closedCount++;\n }\n }\n\n return closedCount;\n }\n\n const store: MenuStore = {\n instances: {},\n\n register(id, options = {}) {\n this.instances[id] = createInstance(options);\n },\n\n unregister(id) {\n if (this.isOpen(id)) {\n this.close(id);\n }\n delete this.instances[id];\n },\n\n registerItem(menuId, itemId, options = {}) {\n const instance = getOrCreate(this, menuId);\n const existing = instance.items.find((item) => item.id === itemId);\n if (existing) {\n existing.disabled = options.disabled ?? existing.disabled;\n existing.parentId = options.parentId ?? existing.parentId;\n return;\n }\n\n instance.items.push({\n id: itemId,\n disabled: options.disabled ?? false,\n parentId: options.parentId ?? null,\n });\n },\n\n unregisterItem(menuId, itemId) {\n const instance = this.instances[menuId];\n if (!instance) {\n return;\n }\n\n instance.items = instance.items.filter((item) => item.id !== itemId);\n if (instance.activeItemId === itemId) {\n instance.activeItemId = firstItem(instance);\n }\n },\n\n open(id) {\n const instance = getOrCreate(this, id);\n if (instance.open) {\n return;\n }\n\n const closedCount = closeOtherMenus(this, id);\n\n instance.open = true;\n if (!instance.activeItemId) {\n instance.activeItemId = firstItem(instance);\n }\n\n if (closedCount === 0) {\n setLock(true);\n }\n\n instance.onOpen?.();\n queueMicrotask(() => focusActiveMenu(instance));\n },\n\n close(id) {\n closeMenu(this, id);\n },\n\n toggle(id) {\n if (this.isOpen(id)) {\n this.close(id);\n } else {\n this.open(id);\n }\n },\n\n isOpen(id) {\n return this.instances[id]?.open ?? false;\n },\n\n activeItem(id) {\n return this.instances[id]?.activeItemId ?? null;\n },\n\n setActiveItem(menuId, itemId) {\n const instance = getOrCreate(this, menuId);\n if (itemId === null) {\n instance.activeItemId = null;\n return;\n }\n\n const item = instance.items.find((entry) => entry.id === itemId);\n if (item && !item.disabled) {\n instance.activeItemId = itemId;\n }\n },\n\n bindMenu(menuId, container) {\n const instance = getOrCreate(this, menuId);\n instance.container = container;\n\n if (instance.open) {\n queueMicrotask(() => focusActiveMenu(instance));\n }\n },\n\n bindTrigger(menuId, trigger) {\n const instance = getOrCreate(this, menuId);\n instance.trigger = trigger;\n },\n\n handleOutsideClick(menuId, event) {\n const instance = this.instances[menuId];\n if (!instance?.open) {\n return;\n }\n\n const target = event.target;\n if (!(target instanceof Node)) {\n return;\n }\n\n if (instance.trigger?.contains(target) || instance.container?.contains(target)) {\n return;\n }\n\n this.close(menuId);\n },\n\n selectItem(menuId, itemId) {\n const instance = this.instances[menuId];\n const item = instance?.items.find((entry) => entry.id === itemId);\n if (!(instance && item) || item.disabled) {\n return;\n }\n\n instance.activeItemId = itemId;\n instance.onSelect?.(itemId);\n\n if (instance.closeOnSelect) {\n this.close(menuId);\n }\n },\n\n handleKeydown(menuId, event) {\n const instance = this.instances[menuId];\n if (instance?.open) {\n handleMenuKeydown(\n menuId,\n instance,\n event,\n this.selectItem.bind(this),\n this.close.bind(this)\n );\n queueMicrotask(() => focusActiveMenu(instance));\n }\n },\n\n handleWindowOutsideClick(event, menuIds) {\n const ids = menuIds ?? Object.keys(this.instances);\n for (const menuId of ids) {\n this.handleOutsideClick(menuId, event);\n }\n },\n\n handleWindowKeydown(event, menuIds) {\n const ids = menuIds ?? Object.keys(this.instances);\n for (const menuId of ids) {\n if (this.isOpen(menuId)) {\n this.handleKeydown(menuId, event);\n return;\n }\n }\n },\n\n itemProps(menuId, itemId) {\n const instance = this.instances[menuId];\n const item = instance?.items.find((entry) => entry.id === itemId);\n const active = instance?.activeItemId === itemId;\n\n return {\n role: \"menuitem\",\n tabindex: active ? 0 : -1,\n \"aria-disabled\": item?.disabled ?? false,\n };\n },\n\n menuProps(menuId) {\n const instance = this.instances[menuId];\n return {\n role: \"menu\",\n \"aria-orientation\": instance?.orientation,\n };\n },\n\n destroy() {\n for (const id of Object.keys(this.instances)) {\n this.unregister(id);\n }\n lockCount = 0;\n config.onLockChange?.(false);\n },\n };\n\n return store;\n}\n","import type AlpineType from \"alpinejs\";\nimport { createMenuStore, type MenuStore } from \"./store.js\";\n\nexport {\n createMenuStore,\n type MenuInstanceOptions,\n type MenuItemOptions,\n type MenuOrientation,\n type MenuStore,\n} from \"./store.js\";\n\nexport interface MenuPluginOptions {\n /** When true (default), opening a menu closes all other open menus. */\n exclusive?: boolean;\n onLockChange?: (locked: boolean) => void;\n}\n\n/** Builds typed menu plugin options. */\nexport function menuOptions<const T extends MenuPluginOptions>(options: T): T {\n return options;\n}\n\n/** Alpine.js menu plugin. Registers `$store.menu`. */\nexport default function menuPlugin(options: MenuPluginOptions = {}): AlpineType.PluginCallback {\n return function registerMenu(Alpine) {\n const store = createMenuStore({\n exclusive: options.exclusive,\n onLockChange: options.onLockChange,\n });\n Alpine.store(\"menu\", store);\n Alpine.magic(\"menu\", () => Alpine.store(\"menu\"));\n };\n}\n\ndeclare global {\n namespace Alpine {\n interface Stores {\n menu: MenuStore;\n }\n interface Magics<T> {\n $menu: MenuStore;\n }\n }\n}\n"],"mappings":"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","names":["store"]}
|