react-aria-menubutton 7.0.3 → 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.
Files changed (43) hide show
  1. package/README.md +140 -122
  2. package/dist/index.d.ts +59 -0
  3. package/dist/react-aria-menubutton.cjs.js +1 -0
  4. package/dist/react-aria-menubutton.es.js +566 -0
  5. package/dist/react-aria-menubutton.umd.js +1 -0
  6. package/package.json +62 -57
  7. package/CHANGELOG.md +0 -176
  8. package/CODE_OF_CONDUCT.md +0 -22
  9. package/dist/Button.js +0 -140
  10. package/dist/ManagerContext.js +0 -7
  11. package/dist/Menu.js +0 -131
  12. package/dist/MenuItem.js +0 -96
  13. package/dist/Wrapper.js +0 -74
  14. package/dist/createManager.js +0 -163
  15. package/dist/externalStateControl.js +0 -32
  16. package/dist/index.js +0 -12
  17. package/dist/propTypes.js +0 -7
  18. package/dist/specialAssign.js +0 -11
  19. package/src/Button.js +0 -129
  20. package/src/ManagerContext.js +0 -5
  21. package/src/Menu.js +0 -118
  22. package/src/MenuItem.js +0 -84
  23. package/src/Wrapper.js +0 -62
  24. package/src/__tests__/Button.test.js +0 -169
  25. package/src/__tests__/Menu.test.js +0 -130
  26. package/src/__tests__/MenuItem.test.js +0 -106
  27. package/src/__tests__/__snapshots__/Button.test.js.snap +0 -41
  28. package/src/__tests__/__snapshots__/Menu.test.js.snap +0 -54
  29. package/src/__tests__/__snapshots__/MenuItem.test.js.snap +0 -37
  30. package/src/__tests__/createManager.test.js +0 -190
  31. package/src/__tests__/helpers/MockWrapper.js +0 -24
  32. package/src/__tests__/helpers/createMockKeyEvent.js +0 -7
  33. package/src/__tests__/helpers/createMockManager.js +0 -22
  34. package/src/__tests__/helpers/jest-setup.js +0 -5
  35. package/src/__tests__/helpers/raf.js +0 -3
  36. package/src/createManager.js +0 -173
  37. package/src/externalStateControl.js +0 -31
  38. package/src/index.js +0 -10
  39. package/src/propTypes.js +0 -8
  40. package/src/specialAssign.js +0 -9
  41. package/umd/ReactAriaMenuButton.js +0 -1
  42. package/webpack-demo.config.js +0 -14
  43. package/webpack-umd.config.js +0 -35
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # react-aria-menubutton [![Build Status](https://travis-ci.org/davidtheclark/react-aria-menubutton.svg?branch=master)](https://travis-ci.org/davidtheclark/react-aria-menubutton)
1
+ # react-aria-menubutton [![Build Status](https://github.com/davidtheclark/react-aria-menubutton/actions/workflows/build.yml/badge.svg)](https://github.com/davidtheclark/react-aria-menubutton/actions)
2
2
 
3
- A React component (set of components, really) that will help you build accessible menu buttons by providing keyboard interactions and ARIA attributes aligned with [the WAI-ARIA Menu Button Design Pattern](http://www.w3.org/TR/wai-aria-practices/#menubutton).
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
- The modular approach of this library means you're much better off building it into your code with a module bundling system like browserify or webpack.
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
- But if you need a UMD version (which will include `focus-group` and `teeny-tap` in the bundle, but of course not `React` or `ReactDOM`), you can get it via npmcdm at `https://unpkg.com/react-aria-menubutton@[version-of-choice]/umd/ReactAriaMenuButton.js`.
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
- Basically IE9+.
67
+ Modern browsers (ES2020+). For older browser support, you may need to transpile the library.
69
68
 
70
69
  ## Usage
71
70
 
72
- ```js
73
- const AriaMenuButton = require('react-aria-menubutton');
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
- // ... or with es2015
79
- import { Button, Wrapper, Menu, MenuItem } from 'react-aria-menubutton';
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
- ```js
89
- // Very simple ES2015 example
90
+ ### Basic Example (TypeScript)
90
91
 
91
- import React from 'react';
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
- class MyMenuButton extends React.Component {
97
- render() {
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
- return (
109
- <Wrapper
110
- className='MyMenuButton'
111
- onSelection={handleSelection}
112
- >
113
- <Button className='MyMenuButton-button'>
114
- click me
115
- </Button>
116
- <Menu className='MyMenuButton-menu'>
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
- function handleSelection(value, event) { .. }
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
- ```js
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
- return (
190
- <AriaMenuButton.Wrapper
191
- className='PeopleMenu'
192
- onSelection={handleSelection}
193
- style={{ marginTop: 20 }}
194
- >
195
- <AriaMenuButton.Button className='PeopleMenu-trigger'>
196
- <span className='PeopleMenu-triggerText'>
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
- function handleSelection(value, event) { .. }
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
- Lint with `npm run lint`.
392
+ ### Scripts
381
393
 
382
- Test with `npm run test-dev`. A browser should open; look at the console log for TAP output.
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
@@ -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;