react-aria-menubutton 7.0.2 → 8.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 +140 -122
- package/dist/index.d.ts +59 -0
- package/dist/react-aria-menubutton.cjs.js +1 -0
- package/dist/react-aria-menubutton.es.js +566 -0
- package/dist/react-aria-menubutton.umd.js +1 -0
- package/package.json +62 -57
- package/CHANGELOG.md +0 -173
- package/CODE_OF_CONDUCT.md +0 -22
- package/dist/Button.js +0 -140
- package/dist/ManagerContext.js +0 -7
- package/dist/Menu.js +0 -131
- package/dist/MenuItem.js +0 -96
- package/dist/Wrapper.js +0 -66
- package/dist/createManager.js +0 -154
- package/dist/externalStateControl.js +0 -32
- package/dist/index.js +0 -12
- package/dist/propTypes.js +0 -7
- package/dist/specialAssign.js +0 -11
- package/src/Button.js +0 -129
- package/src/ManagerContext.js +0 -5
- package/src/Menu.js +0 -118
- package/src/MenuItem.js +0 -84
- package/src/Wrapper.js +0 -54
- package/src/__tests__/Button.test.js +0 -169
- package/src/__tests__/Menu.test.js +0 -130
- package/src/__tests__/MenuItem.test.js +0 -106
- package/src/__tests__/__snapshots__/Button.test.js.snap +0 -41
- package/src/__tests__/__snapshots__/Menu.test.js.snap +0 -54
- package/src/__tests__/__snapshots__/MenuItem.test.js.snap +0 -37
- package/src/__tests__/createManager.test.js +0 -190
- package/src/__tests__/helpers/MockWrapper.js +0 -24
- package/src/__tests__/helpers/createMockKeyEvent.js +0 -7
- package/src/__tests__/helpers/createMockManager.js +0 -22
- package/src/__tests__/helpers/jest-setup.js +0 -5
- package/src/__tests__/helpers/raf.js +0 -3
- package/src/createManager.js +0 -162
- package/src/externalStateControl.js +0 -31
- package/src/index.js +0 -10
- package/src/propTypes.js +0 -8
- package/src/specialAssign.js +0 -9
- package/umd/ReactAriaMenuButton.js +0 -1
- package/webpack-demo.config.js +0 -14
- package/webpack-umd.config.js +0 -35
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# react-aria-menubutton [](https://github.com/davidtheclark/react-aria-menubutton/actions)
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A fully accessible, flexible React menu button component with built-in **TypeScript support**. Provides keyboard interactions and ARIA attributes aligned with [the WAI-ARIA Menu Button Design Pattern](http://www.w3.org/TR/wai-aria-practices/#menubutton).
|
|
4
4
|
|
|
5
5
|
Please check out [the demo](https://davidtheclark.github.io/react-aria-menubutton/demo/).
|
|
6
6
|
|
|
@@ -58,25 +58,27 @@ It does not provide any classes or a stylesheet that you'll have to figure out h
|
|
|
58
58
|
npm install react-aria-menubutton
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
**TypeScript users:** This library is written in TypeScript and ships with built-in type declarations. No need to install `@types` packages!
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
If you don't know about unpkg, [read about it here](https://unpkg.com).
|
|
63
|
+
The modular approach of this library means you're much better off building it into your code with a module bundling system like Vite, webpack, or similar.
|
|
65
64
|
|
|
66
65
|
## Browser Support
|
|
67
66
|
|
|
68
|
-
|
|
67
|
+
Modern browsers (ES2020+). For older browser support, you may need to transpile the library.
|
|
69
68
|
|
|
70
69
|
## Usage
|
|
71
70
|
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
// Now use AriaMenuButton.Wrapper, AriaMenuButton.Button,
|
|
76
|
-
// AriaMenuButton.Menu, and AriaMenuButton.MenuItem ...
|
|
71
|
+
```tsx
|
|
72
|
+
import { Wrapper, Button, Menu, MenuItem } from 'react-aria-menubutton';
|
|
77
73
|
|
|
78
|
-
//
|
|
79
|
-
import {
|
|
74
|
+
// TypeScript: You can also import types
|
|
75
|
+
import type {
|
|
76
|
+
WrapperProps,
|
|
77
|
+
ButtonProps,
|
|
78
|
+
MenuProps,
|
|
79
|
+
MenuItemProps,
|
|
80
|
+
MenuChildrenState
|
|
81
|
+
} from 'react-aria-menubutton';
|
|
80
82
|
```
|
|
81
83
|
|
|
82
84
|
## Examples
|
|
@@ -85,128 +87,114 @@ For details about why the examples work, read the API documentation below.
|
|
|
85
87
|
|
|
86
88
|
You can also see more examples by looking in `demo/`.
|
|
87
89
|
|
|
88
|
-
|
|
89
|
-
// Very simple ES2015 example
|
|
90
|
+
### Basic Example (TypeScript)
|
|
90
91
|
|
|
91
|
-
|
|
92
|
+
```tsx
|
|
93
|
+
import { useState } from 'react';
|
|
92
94
|
import { Wrapper, Button, Menu, MenuItem } from 'react-aria-menubutton';
|
|
93
95
|
|
|
94
96
|
const menuItemWords = ['foo', 'bar', 'baz'];
|
|
95
97
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const menuItems = menuItemWords.map((word, i) => {
|
|
99
|
-
return (
|
|
100
|
-
<li key={i}>
|
|
101
|
-
<MenuItem className='MyMenuButton-menuItem'>
|
|
102
|
-
{word}
|
|
103
|
-
</MenuItem>
|
|
104
|
-
</li>
|
|
105
|
-
);
|
|
106
|
-
});
|
|
98
|
+
function MyMenuButton() {
|
|
99
|
+
const [selected, setSelected] = useState('');
|
|
107
100
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
101
|
+
const handleSelection = (value: unknown) => {
|
|
102
|
+
setSelected(value as string);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const menuItems = menuItemWords.map((word, i) => (
|
|
106
|
+
<li key={i}>
|
|
107
|
+
<MenuItem className="MyMenuButton-menuItem">{word}</MenuItem>
|
|
108
|
+
</li>
|
|
109
|
+
));
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<div>
|
|
113
|
+
<Wrapper className="MyMenuButton" onSelection={handleSelection}>
|
|
114
|
+
<Button className="MyMenuButton-button">click me</Button>
|
|
115
|
+
<Menu className="MyMenuButton-menu">
|
|
117
116
|
<ul>{menuItems}</ul>
|
|
118
117
|
</Menu>
|
|
119
118
|
</Wrapper>
|
|
120
|
-
|
|
121
|
-
|
|
119
|
+
<p>Selected: {selected}</p>
|
|
120
|
+
</div>
|
|
121
|
+
);
|
|
122
122
|
}
|
|
123
|
+
```
|
|
123
124
|
|
|
124
|
-
|
|
125
|
+
### Advanced Example with Typed Values
|
|
126
|
+
|
|
127
|
+
```tsx
|
|
128
|
+
import { useState } from 'react';
|
|
129
|
+
import {
|
|
130
|
+
Wrapper,
|
|
131
|
+
Button,
|
|
132
|
+
Menu,
|
|
133
|
+
MenuItem,
|
|
134
|
+
type MenuChildrenState
|
|
135
|
+
} from 'react-aria-menubutton';
|
|
136
|
+
|
|
137
|
+
interface Person {
|
|
138
|
+
name: string;
|
|
139
|
+
id: number;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const people: Person[] = [
|
|
143
|
+
{ name: 'Charles Choo-Choo', id: 1242 },
|
|
144
|
+
{ name: 'Mina Meowmers', id: 8372 },
|
|
145
|
+
{ name: 'Susan Sailor', id: 2435 },
|
|
146
|
+
];
|
|
147
|
+
|
|
148
|
+
function PeopleMenu() {
|
|
149
|
+
const [selectedId, setSelectedId] = useState<number | null>(null);
|
|
150
|
+
|
|
151
|
+
const handleSelection = (value: unknown) => {
|
|
152
|
+
setSelectedId(value as number);
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const menuItems = people.map((person) => (
|
|
156
|
+
<MenuItem
|
|
157
|
+
key={person.id}
|
|
158
|
+
tag="li"
|
|
159
|
+
value={person.id}
|
|
160
|
+
text={person.name}
|
|
161
|
+
className="PeopleMenu-person"
|
|
162
|
+
>
|
|
163
|
+
<div className="PeopleMenu-personName">{person.name}</div>
|
|
164
|
+
</MenuItem>
|
|
165
|
+
));
|
|
166
|
+
|
|
167
|
+
// Using function children with typed state
|
|
168
|
+
const menuContent = (menuState: MenuChildrenState) => {
|
|
169
|
+
if (!menuState.isOpen) return null;
|
|
170
|
+
return <ul className="PeopleMenu-menu">{menuItems}</ul>;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
return (
|
|
174
|
+
<Wrapper className="PeopleMenu" onSelection={handleSelection}>
|
|
175
|
+
<Button className="PeopleMenu-trigger">Select a person</Button>
|
|
176
|
+
<Menu>{menuContent}</Menu>
|
|
177
|
+
</Wrapper>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
125
180
|
```
|
|
126
181
|
|
|
127
|
-
|
|
128
|
-
// Slightly more complex, ES5 example:
|
|
129
|
-
// - MenuItems have hidden values that are passed
|
|
130
|
-
// to the selection handler
|
|
131
|
-
// - User can navigate the MenuItems by typing the
|
|
132
|
-
// first letter of a person's name, even though
|
|
133
|
-
// each MenuItem's child is not simple text
|
|
134
|
-
// - Menu has a function for a child
|
|
135
|
-
// - React's CSSTransitionGroup is used for open-close animation
|
|
136
|
-
|
|
137
|
-
var React = require('react');
|
|
138
|
-
var CSSTransitionGroup = require('react-transition-group/CSSTransitionGroup');
|
|
139
|
-
var AriaMenuButton = require('react-aria-menubutton');
|
|
140
|
-
|
|
141
|
-
var people = [{
|
|
142
|
-
name: 'Charles Choo-Choo',
|
|
143
|
-
id: 1242
|
|
144
|
-
}, {
|
|
145
|
-
name: 'Mina Meowmers',
|
|
146
|
-
id: 8372
|
|
147
|
-
}, {
|
|
148
|
-
name: 'Susan Sailor',
|
|
149
|
-
id: 2435
|
|
150
|
-
}];
|
|
151
|
-
|
|
152
|
-
var MyMenuButton = React.createClass({
|
|
153
|
-
render: function() {
|
|
154
|
-
var peopleMenuItems = people.map(function(person, i) {
|
|
155
|
-
return (
|
|
156
|
-
<AriaMenuButton.MenuItem
|
|
157
|
-
key={i}
|
|
158
|
-
tag='li'
|
|
159
|
-
value={person.id}
|
|
160
|
-
text={person.name}
|
|
161
|
-
className='PeopleMenu-person'
|
|
162
|
-
>
|
|
163
|
-
<div className='PeopleMenu-personPhoto'>
|
|
164
|
-
<img src={'/people/pictures/' + person.id + '.jpg'}/ >
|
|
165
|
-
</div>
|
|
166
|
-
<div className='PeopleMenu-personName'>
|
|
167
|
-
{person.name}
|
|
168
|
-
</div>
|
|
169
|
-
</AriaMenuButton.MenuItem>
|
|
170
|
-
);
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
var peopleMenuInnards = function(menuState) {
|
|
174
|
-
var menu = (!menuState.isOpen) ? false : (
|
|
175
|
-
<div
|
|
176
|
-
className='PeopleMenu-menu'
|
|
177
|
-
key='menu'
|
|
178
|
-
>
|
|
179
|
-
{peopleMenuItems}
|
|
180
|
-
</div>
|
|
181
|
-
);
|
|
182
|
-
return (
|
|
183
|
-
<CSSTransitionGroup transitionName='people'>
|
|
184
|
-
{menu}
|
|
185
|
-
</CSSTransitionGroup>
|
|
186
|
-
);
|
|
187
|
-
};
|
|
182
|
+
### Programmatic Control
|
|
188
183
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
Select a person
|
|
198
|
-
</span>
|
|
199
|
-
<span className='PeopleMenu-triggerIcon' />
|
|
200
|
-
</AriaMenuButton.Button>
|
|
201
|
-
<AriaMenuButton.Menu>
|
|
202
|
-
{peopleMenuInnards}
|
|
203
|
-
</AriaMenuButton.Menu>
|
|
204
|
-
</AriaMenuButton.Wrapper>
|
|
205
|
-
);
|
|
206
|
-
}
|
|
207
|
-
});
|
|
184
|
+
```tsx
|
|
185
|
+
import { openMenu, closeMenu } from 'react-aria-menubutton';
|
|
186
|
+
|
|
187
|
+
// Open menu with id "my-menu"
|
|
188
|
+
openMenu('my-menu');
|
|
189
|
+
|
|
190
|
+
// Open without focusing the first item
|
|
191
|
+
openMenu('my-menu', { focusMenu: false });
|
|
208
192
|
|
|
209
|
-
|
|
193
|
+
// Close menu
|
|
194
|
+
closeMenu('my-menu');
|
|
195
|
+
|
|
196
|
+
// Close and focus the button
|
|
197
|
+
closeMenu('my-menu', { focusButton: true });
|
|
210
198
|
```
|
|
211
199
|
|
|
212
200
|
## API
|
|
@@ -373,10 +361,40 @@ These are the `closeOptions`:
|
|
|
373
361
|
|
|
374
362
|
- **focusButton** { Boolean }: If `true`, the widget's button will receive focus when the menu closes. Default: `false`.
|
|
375
363
|
|
|
364
|
+
## TypeScript
|
|
365
|
+
|
|
366
|
+
This library is written in TypeScript and provides built-in type declarations.
|
|
367
|
+
|
|
368
|
+
### Exported Types
|
|
369
|
+
|
|
370
|
+
```tsx
|
|
371
|
+
import type {
|
|
372
|
+
// Component Props
|
|
373
|
+
WrapperProps,
|
|
374
|
+
ButtonProps,
|
|
375
|
+
MenuProps,
|
|
376
|
+
MenuItemProps,
|
|
377
|
+
|
|
378
|
+
// Menu children function state
|
|
379
|
+
MenuChildren,
|
|
380
|
+
MenuChildrenState,
|
|
381
|
+
|
|
382
|
+
// Programmatic control options
|
|
383
|
+
OpenMenuOptions,
|
|
384
|
+
CloseMenuOptions,
|
|
385
|
+
} from 'react-aria-menubutton';
|
|
386
|
+
```
|
|
387
|
+
|
|
376
388
|
## Contributing & Development
|
|
377
389
|
|
|
378
390
|
Please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms.
|
|
379
391
|
|
|
380
|
-
|
|
392
|
+
### Scripts
|
|
381
393
|
|
|
382
|
-
|
|
394
|
+
- `npm run typecheck` - Type check with TypeScript
|
|
395
|
+
- `npm run lint` - Lint with ESLint
|
|
396
|
+
- `npm run format` - Format with Prettier
|
|
397
|
+
- `npm run format:check` - Check formatting
|
|
398
|
+
- `npm test` - Run tests
|
|
399
|
+
- `npm run build` - Build the library
|
|
400
|
+
- `npm start` - Start the demo dev server
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { ForwardRefExoticComponent } from 'react';
|
|
2
|
+
import { RefAttributes } from 'react';
|
|
3
|
+
|
|
4
|
+
export declare const Button: ForwardRefExoticComponent<ButtonProps & RefAttributes<HTMLElement>>;
|
|
5
|
+
|
|
6
|
+
export declare interface ButtonProps extends React.HTMLAttributes<HTMLElement> {
|
|
7
|
+
children: React.ReactNode;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
tag?: keyof React.JSX.IntrinsicElements;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export declare function closeMenu(menuId: string, closeOptions?: CloseMenuOptions): void;
|
|
13
|
+
|
|
14
|
+
export declare interface CloseMenuOptions {
|
|
15
|
+
focusButton?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export declare const Menu: ForwardRefExoticComponent<MenuProps & RefAttributes<HTMLElement>>;
|
|
19
|
+
|
|
20
|
+
export declare type MenuChildren = React.ReactNode | ((state: MenuChildrenState) => React.ReactNode);
|
|
21
|
+
|
|
22
|
+
export declare interface MenuChildrenState {
|
|
23
|
+
isOpen: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export declare const MenuItem: ForwardRefExoticComponent<MenuItemProps & RefAttributes<HTMLElement>>;
|
|
27
|
+
|
|
28
|
+
export declare interface MenuItemProps extends React.HTMLAttributes<HTMLElement> {
|
|
29
|
+
children: React.ReactNode;
|
|
30
|
+
tag?: keyof React.JSX.IntrinsicElements;
|
|
31
|
+
text?: string;
|
|
32
|
+
value?: unknown;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export declare interface MenuProps extends Omit<React.HTMLAttributes<HTMLElement>, "children"> {
|
|
36
|
+
children: MenuChildren;
|
|
37
|
+
tag?: keyof React.JSX.IntrinsicElements;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export declare function openMenu(menuId: string, openOptions?: OpenMenuOptions): void;
|
|
41
|
+
|
|
42
|
+
export declare interface OpenMenuOptions {
|
|
43
|
+
focusMenu?: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export declare const Wrapper: ForwardRefExoticComponent<WrapperProps & RefAttributes<HTMLElement>>;
|
|
47
|
+
|
|
48
|
+
export declare interface WrapperProps extends Omit<React.HTMLAttributes<HTMLElement>, "onSelect"> {
|
|
49
|
+
children: React.ReactNode;
|
|
50
|
+
onMenuToggle?: (state: {
|
|
51
|
+
isOpen: boolean;
|
|
52
|
+
}) => void;
|
|
53
|
+
onSelection?: (value: unknown, event: React.SyntheticEvent) => void;
|
|
54
|
+
closeOnSelection?: boolean;
|
|
55
|
+
closeOnBlur?: boolean;
|
|
56
|
+
tag?: keyof React.JSX.IntrinsicElements;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export { }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const y=require("react/jsx-runtime"),u=require("react"),_=new Map,k="a menu outside a mounted Wrapper with an id, or a menu that does not exist";function B(e,r){_.set(e,r)}function K(e){_.delete(e)}function M(e,r){const s=_.get(e);if(!s)throw new Error("Cannot open "+k);s.openMenu(r)}function L(e,r){const s=_.get(e);if(!s)throw new Error("Cannot close "+k);s.closeMenu(r)}function x(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var w,b;function F(){if(b)return w;b=1;function e(t){t=t||{};var n=t.keybindings||{};this._settings={keybindings:{next:n.next||{keyCode:40},prev:n.prev||{keyCode:38},first:n.first,last:n.last},wrap:t.wrap,stringSearch:t.stringSearch,stringSearchDelay:800},this._keybindingsLookup=[];var o,c;for(o in this._settings.keybindings)c=this._settings.keybindings[o],c&&[].concat(c).forEach((function(a){a.metaKey=a.metaKey||!1,a.ctrlKey=a.ctrlKey||!1,a.altKey=a.altKey||!1,a.shiftKey=a.shiftKey||!1,this._keybindingsLookup.push({action:o,eventMatcher:a})}).bind(this));this._searchString="",this._members=[],t.members&&this.setMembers(t.members),this._boundHandleKeydownEvent=this._handleKeydownEvent.bind(this)}e.prototype.activate=function(){return document.addEventListener("keydown",this._boundHandleKeydownEvent,!0),this},e.prototype.deactivate=function(){return document.removeEventListener("keydown",this._boundHandleKeydownEvent,!0),this._clearSearchStringRefreshTimer(),this},e.prototype._handleKeydownEvent=function(t){var n=this._getActiveElementIndex();if(n!==-1){var o=!1;this._keybindingsLookup.forEach((function(c){if(r(c.eventMatcher,t))switch(o=!0,t.preventDefault(),c.action){case"next":this.moveFocusForward();break;case"prev":this.moveFocusBack();break;case"first":this.moveFocusToFirst();break;case"last":this.moveFocusToLast();break;default:return}}).bind(this)),o||this._handleUnboundKey(t)}},e.prototype.moveFocusForward=function(){var t=this._getActiveElementIndex(),n;return t<this._members.length-1?n=t+1:this._settings.wrap?n=0:n=t,this.focusNodeAtIndex(n),n},e.prototype.moveFocusBack=function(){var t=this._getActiveElementIndex(),n;return t>0?n=t-1:this._settings.wrap?n=this._members.length-1:n=t,this.focusNodeAtIndex(n),n},e.prototype.moveFocusToFirst=function(){this.focusNodeAtIndex(0)},e.prototype.moveFocusToLast=function(){this.focusNodeAtIndex(this._members.length-1)},e.prototype._handleUnboundKey=function(t){if(this._settings.stringSearch){if(this._searchString!==""&&(t.key===" "||t.keyCode===32))return t.preventDefault(),-1;if(!s(t.keyCode)||t.ctrlKey||t.metaKey||t.altKey)return-1;t.preventDefault(),this._addToSearchString(String.fromCharCode(t.keyCode)),this._runStringSearch()}},e.prototype._clearSearchString=function(){this._searchString=""},e.prototype._addToSearchString=function(t){this._searchString+=t.toLowerCase()},e.prototype._startSearchStringRefreshTimer=function(){var t=this;this._clearSearchStringRefreshTimer(),this._stringSearchTimer=setTimeout(function(){t._clearSearchString()},this._settings.stringSearchDelay)},e.prototype._clearSearchStringRefreshTimer=function(){clearTimeout(this._stringSearchTimer)},e.prototype._runStringSearch=function(){this._startSearchStringRefreshTimer(),this.moveFocusByString(this._searchString)},e.prototype.moveFocusByString=function(t){for(var n,o=0,c=this._members.length;o<c;o++)if(n=this._members[o],!!n.text&&n.text.indexOf(t)===0)return i(n.node)},e.prototype._findIndexOfNode=function(t){for(var n=0,o=this._members.length;n<o;n++)if(this._members[n].node===t)return n;return-1},e.prototype._getActiveElementIndex=function(){return this._findIndexOfNode(document.activeElement)},e.prototype.focusNodeAtIndex=function(t){var n=this._members[t];return n&&i(n.node),this},e.prototype.addMember=function(t,n){var o=t.node||t,c=t.text||o.getAttribute("data-focus-group-text")||o.textContent||"";this._checkNode(o);var a=c.replace(/[\W_]/g,"").toLowerCase(),h={node:o,text:a};return n!=null?this._members.splice(n,0,h):this._members.push(h),this},e.prototype.removeMember=function(t){var n=typeof t=="number"?t:this._findIndexOfNode(t);if(n!==-1)return this._members.splice(n,1),this},e.prototype.clearMembers=function(){return this._members=[],this},e.prototype.setMembers=function(t){this.clearMembers();for(var n=0,o=t.length;n<o;n++)this.addMember(t[n]);return this},e.prototype.getMembers=function(){return this._members},e.prototype._checkNode=function(t){if(!t.nodeType||t.nodeType!==window.Node.ELEMENT_NODE)throw new Error("focus-group: only DOM nodes allowed");return t};function r(t,n){for(var o in t)if(n[o]!==void 0&&t[o]!==n[o])return!1;return!0}function s(t){return t>=65&&t<=90}function i(t){!t||!t.focus||(t.focus(),t.tagName.toLowerCase()==="input"&&t.select())}return w=function(n){return new e(n)},w}var C=F();const N=x(C),R={wrap:!0,stringSearch:!0},D={options:{closeOnSelection:!0,closeOnBlur:!0},isOpen:!1,button:null,menu:null,focusGroup:null,blurTimer:void 0,moveFocusTimer:void 0,init(e){this.updateOptions(e),this.handleBlur=A.bind(this),this.handleSelection=G.bind(this),this.handleMenuKey=j.bind(this),this.focusGroup=N(R),this.button=null,this.menu=null,this.isOpen=!1},updateOptions(e){const r=this.options;this.options={...e,closeOnSelection:e?.closeOnSelection??!0,closeOnBlur:e?.closeOnBlur??!0},this.options.id&&B(this.options.id,this),r.id&&r.id!==this.options.id&&K(r.id)},focusItem(e){this.focusGroup.focusNodeAtIndex(e)},addItem(e){this.focusGroup.addMember(e)},clearItems(){this.focusGroup.clearMembers()},handleButtonNonArrowKey(e){this.focusGroup._handleUnboundKey(e)},destroy(){this.button=null,this.menu=null,this.focusGroup.deactivate(),clearTimeout(this.blurTimer),clearTimeout(this.moveFocusTimer)},update(){this.menu?.setState?.({isOpen:this.isOpen}),this.button?.setState?.({menuOpen:this.isOpen}),this.options.onMenuToggle?.({isOpen:this.isOpen})},openMenu(e){if(this.isOpen)return;const s=(e??{}).focusMenu??!0;this.isOpen=!0,this.update(),this.focusGroup.activate(),s&&(this.moveFocusTimer=setTimeout(()=>{this.focusItem(0)},0))},closeMenu(e){if(!this.isOpen)return;const r=e??{};this.isOpen=!1,this.update(),r.focusButton&&this.button?.ref.current?.focus()},toggleMenu(e,r){const s=e??{},i=r??{};this.isOpen?this.closeMenu(s):this.openMenu(i)},handleBlur(){},handleSelection(e,r){},handleMenuKey(e){}};function A(){this.blurTimer=setTimeout(()=>{if(!this.button)return;const e=this.button.ref.current;if(!e)return;const r=e.ownerDocument.activeElement;if(r===e)return;const s=this.menu?.ref.current;if(s===r){this.focusItem(0);return}s?.contains(r)||this.isOpen&&this.closeMenu({focusButton:!1})},0)}function G(e,r){this.options.closeOnSelection&&this.closeMenu({focusButton:!0}),this.options.onSelection?.(e,r)}function j(e){if(this.isOpen)switch(e.key){case"Escape":e.preventDefault(),this.closeMenu({focusButton:!0});break;case"Home":e.preventDefault(),this.focusGroup.moveFocusToFirst();break;case"End":e.preventDefault(),this.focusGroup.moveFocusToLast();break}}function W(e){const r=Object.create(D);return r.init(e),r}const g=u.createContext(null);function O(e,r,s){const i=s??{};for(const t in r)Object.prototype.hasOwnProperty.call(r,t)&&(i[t]||(e[t]=r[t]))}const P={children:!0,onMenuToggle:!0,onSelection:!0,closeOnSelection:!0,closeOnBlur:!0,tag:!0};function E(e){return{onMenuToggle:e.onMenuToggle,onSelection:e.onSelection,closeOnSelection:e.closeOnSelection,closeOnBlur:e.closeOnBlur,id:e.id}}const q=u.forwardRef(function({children:r,onMenuToggle:s,onSelection:i,closeOnSelection:t,closeOnBlur:n,tag:o="div",...c},a){const h=u.useMemo(()=>W(E({onMenuToggle:s,onSelection:i,closeOnSelection:t,closeOnBlur:n,id:c.id})),[]);u.useEffect(()=>{h.updateOptions(E({onMenuToggle:s,onSelection:i,closeOnSelection:t,closeOnBlur:n,id:c.id}))},[h,s,i,t,n,c.id,r]);const l={};return O(l,c,P),y.jsx(g.Provider,{value:h,children:u.createElement(o,l,r)})}),H=["button","fieldset","input","optgroup","option","select","textarea"],U={ambManager:!0,children:!0,disabled:!0,tag:!0};function X({ambManager:e,children:r,disabled:s=!1,tag:i="span",...t}){const n=u.useRef(null),[,o]=u.useState(e.isOpen);u.useEffect(()=>(e.button={ref:n,setState:f=>{o(f.menuOpen)}},()=>{e.destroy()}),[e]);const c=u.useCallback(f=>{if(!s)switch(f.key){case"ArrowDown":f.preventDefault(),e.isOpen?e.focusItem(0):e.openMenu();break;case"Enter":case" ":f.preventDefault(),e.toggleMenu();break;case"Escape":e.handleMenuKey(f);break;default:e.handleButtonNonArrowKey(f.nativeEvent)}},[e,s]),a=u.useCallback(f=>{s||e.toggleMenu({},{focusMenu:!1})},[e,s]),h=u.useCallback(f=>{n.current=f},[]),l={role:"button",tabIndex:s?-1:0,"aria-haspopup":!0,"aria-expanded":e.isOpen,"aria-disabled":s,onKeyDown:c,onClick:a,ref:h};return H.includes(i)&&(l.disabled=s),e.options.closeOnBlur&&(l.onBlur=f=>{e.handleBlur()}),O(l,t,U),u.createElement(i,l,r)}const Y=u.forwardRef(function(r,s){const i=u.useContext(g);if(!i)throw new Error("Button must be used within a Wrapper component");return y.jsx(X,{...r,ambManager:i})});var S,T;function $(){return T||(T=1,S=function(r,s,i){var t=0,n=0,o=!1,c=!1,a=!1;r.addEventListener("click",h,i),r.addEventListener("touchstart",l,i);function h(p){a||s(p)}function l(p){a=!0,!o&&(o=!0,r.addEventListener("touchmove",f,i),r.addEventListener("touchend",d,i),r.addEventListener("touchcancel",v,i),c=!1,t=p.touches[0].clientX,n=p.touches[0].clientY)}function f(p){c||Math.abs(p.touches[0].clientX-t)<=10&&Math.abs(p.touches[0].clientY-n)<=10||(c=!0)}function d(p){o=!1,m(),c||s(p)}function v(){o=!1,c=!1,t=0,n=0}function m(){r.removeEventListener("touchmove",f,i),r.removeEventListener("touchend",d,i),r.removeEventListener("touchcancel",v,i)}function I(){r.removeEventListener("click",h,i),r.removeEventListener("touchstart",l,i),m()}return{remove:I}}),S}var V=$();const z=x(V),J={ambManager:!0,children:!0,tag:!0};function Q({ambManager:e,children:r,tag:s="div",...i}){const t=u.useRef(null),n=u.useRef(null),o=u.useRef(null),[,c]=u.useState(e.isOpen),a=u.useCallback(()=>{const d=t.current;if(!d)return;const v=m=>{t.current?.contains(m.target)||e.button?.ref.current?.contains(m.target)||e.closeMenu()};n.current=z(d.ownerDocument.documentElement,v)},[e]);u.useEffect(()=>(e.menu={ref:t,setState:d=>{c(d.isOpen)}},()=>{n.current&&n.current.remove(),o.current&&clearTimeout(o.current),e.destroy()}),[e]),u.useEffect(()=>{e.options.closeOnBlur&&(e.isOpen&&!n.current?o.current=setTimeout(()=>{a(),o.current=null},0):!e.isOpen&&n.current&&(n.current.remove(),n.current=null),e.isOpen||e.clearItems())},[e,e.isOpen,a]);const h=u.useCallback(d=>{t.current=d},[]),l=typeof r=="function"?r({isOpen:e.isOpen}):e.isOpen?r:null;if(!l)return null;const f={onKeyDown:d=>{e.handleMenuKey(d)},role:"menu",tabIndex:-1,ref:h};return e.options.closeOnBlur&&(f.onBlur=d=>{e.handleBlur()}),O(f,i,J),u.createElement(s,f,l)}const Z=u.forwardRef(function(r,s){const i=u.useContext(g);if(!i)throw new Error("Menu must be used within a Wrapper component");return y.jsx(Q,{...r,ambManager:i})}),ee={ambManager:!0,children:!0,tag:!0,text:!0,value:!0};function te({ambManager:e,children:r,tag:s="div",text:i,value:t,...n}){const o=u.useRef(null);u.useEffect(()=>{o.current&&e.addItem({node:o.current,text:i})},[e,i]);const c=u.useCallback(f=>{const d=t!==void 0?t:r;e.handleSelection(d,f)},[e,t,r]),a=u.useCallback(f=>{f.key!=="Enter"&&f.key!==" "||s==="a"&&"href"in n&&n.href!==void 0||(f.preventDefault(),c(f))},[s,n,c]),h=u.useCallback(f=>{o.current=f},[]),l={onClick:c,onKeyDown:a,role:"menuitem",tabIndex:-1,ref:h};return O(l,n,ee),u.createElement(s,l,r)}const ne=u.forwardRef(function(r,s){const i=u.useContext(g);if(!i)throw new Error("MenuItem must be used within a Wrapper component");return y.jsx(te,{...r,ambManager:i})});exports.Button=Y;exports.Menu=Z;exports.MenuItem=ne;exports.Wrapper=q;exports.closeMenu=L;exports.openMenu=M;
|