@net-advantage/jo-ui-menu 1.3.0 → 1.3.2
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 +57 -57
- package/dist/DropdownMenu.d.ts +4 -0
- package/dist/index.cjs +6 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +471 -0
- package/dist/jo-ui-menu.css +2 -0
- package/dist/jo-ui-menu.model.d.ts +46 -0
- package/dist/jo-ui-menu.types.d.ts +63 -0
- package/dist/jo-ui-menu.view-model.d.ts +2 -0
- package/package.json +43 -43
package/README.md
CHANGED
|
@@ -1,57 +1,57 @@
|
|
|
1
|
-
# @net-advantage/jo-ui-menu
|
|
2
|
-
|
|
3
|
-
Menu button for JourneyOf. `DropdownMenu` and `MenuButton` are the same control: a button that opens a menu of actions.
|
|
4
|
-
|
|
5
|
-
The control follows a strict MVVM split. The model places the menu and resolves keyboard movement. The view-model owns open state, focus, and outside clicks. The view renders the view-model.
|
|
6
|
-
|
|
7
|
-
Colours, radii, and focus come from `--jo-color-*` and `--jo-radius-*` tokens.
|
|
8
|
-
|
|
9
|
-
## MVVM
|
|
10
|
-
|
|
11
|
-
```mermaid
|
|
12
|
-
flowchart LR
|
|
13
|
-
Props["DropdownMenuProps"] --> ViewModel["useMenuViewModel"]
|
|
14
|
-
Model["jo-ui-menu.model.ts"] --> ViewModel
|
|
15
|
-
ViewModel --> View["DropdownMenu.tsx"]
|
|
16
|
-
View -->|"keys, click, hover"| ViewModel
|
|
17
|
-
ViewModel -->|"onSelect"| Host["Host application"]
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
| Layer | File | Responsibility |
|
|
21
|
-
| --- | --- | --- |
|
|
22
|
-
| Contract | [jo-ui-menu.types.ts](src/jo-ui-menu.types.ts) | Props, items, and the menu view-model |
|
|
23
|
-
| Model | [jo-ui-menu.model.ts](src/jo-ui-menu.model.ts) | Class names, enabled indexes, typeahead, and viewport placement |
|
|
24
|
-
| View-model | [jo-ui-menu.view-model.ts](src/jo-ui-menu.view-model.ts) | `useMenuViewModel`: open state, keyboard, focus return, outside click |
|
|
25
|
-
| View | [DropdownMenu.tsx](src/DropdownMenu.tsx) | Render the menu button and the menu |
|
|
26
|
-
|
|
27
|
-
## Models
|
|
28
|
-
|
|
29
|
-
```mermaid
|
|
30
|
-
flowchart TD
|
|
31
|
-
Items["MenuItem[]"] --> Enabled["enabled indexes"]
|
|
32
|
-
Items --> Typeahead["typeahead match"]
|
|
33
|
-
Trigger["trigger box"] --> Place["placeMenu"]
|
|
34
|
-
Viewport["viewport"] --> Place
|
|
35
|
-
Enabled --> ViewModel["MenuViewModel"]
|
|
36
|
-
Typeahead --> ViewModel
|
|
37
|
-
Place --> ViewModel
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
- The button uses `aria-haspopup="menu"`, `aria-expanded`, and `aria-controls`. The popup is `role="menu"` and each action is `role="menuitem"`.
|
|
41
|
-
- Arrow keys, Home, and End move among enabled items. Typeahead selects by the typed prefix. Enter and Space activate the active item.
|
|
42
|
-
- Escape closes the menu and returns focus to the button. Tab closes the menu and leaves focus movement to the browser.
|
|
43
|
-
- A pointer press outside the control closes the menu. The menu flips above the button and shifts sideways so it stays in the viewport.
|
|
44
|
-
- A disabled item stays visible, exposes `aria-disabled`, and does not activate. `pressed` sets `aria-pressed`. `badge` renders inside the item.
|
|
45
|
-
- `MenuButton` is the same component as `DropdownMenu`.
|
|
46
|
-
|
|
47
|
-
## Structure
|
|
48
|
-
|
|
49
|
-
- src: runtime component, model, view-model, types, and companion tests.
|
|
50
|
-
- stories: Storybook stories for light and dark themes.
|
|
51
|
-
|
|
52
|
-
## Publish footprint
|
|
53
|
-
|
|
54
|
-
This package publishes only:
|
|
55
|
-
|
|
56
|
-
- dist
|
|
57
|
-
- README.md
|
|
1
|
+
# @net-advantage/jo-ui-menu
|
|
2
|
+
|
|
3
|
+
Menu button for JourneyOf. `DropdownMenu` and `MenuButton` are the same control: a button that opens a menu of actions.
|
|
4
|
+
|
|
5
|
+
The control follows a strict MVVM split. The model places the menu and resolves keyboard movement. The view-model owns open state, focus, and outside clicks. The view renders the view-model.
|
|
6
|
+
|
|
7
|
+
Colours, radii, and focus come from `--jo-color-*` and `--jo-radius-*` tokens.
|
|
8
|
+
|
|
9
|
+
## MVVM
|
|
10
|
+
|
|
11
|
+
```mermaid
|
|
12
|
+
flowchart LR
|
|
13
|
+
Props["DropdownMenuProps"] --> ViewModel["useMenuViewModel"]
|
|
14
|
+
Model["jo-ui-menu.model.ts"] --> ViewModel
|
|
15
|
+
ViewModel --> View["DropdownMenu.tsx"]
|
|
16
|
+
View -->|"keys, click, hover"| ViewModel
|
|
17
|
+
ViewModel -->|"onSelect"| Host["Host application"]
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
| Layer | File | Responsibility |
|
|
21
|
+
| --- | --- | --- |
|
|
22
|
+
| Contract | [jo-ui-menu.types.ts](src/jo-ui-menu.types.ts) | Props, items, and the menu view-model |
|
|
23
|
+
| Model | [jo-ui-menu.model.ts](src/jo-ui-menu.model.ts) | Class names, enabled indexes, typeahead, and viewport placement |
|
|
24
|
+
| View-model | [jo-ui-menu.view-model.ts](src/jo-ui-menu.view-model.ts) | `useMenuViewModel`: open state, keyboard, focus return, outside click |
|
|
25
|
+
| View | [DropdownMenu.tsx](src/DropdownMenu.tsx) | Render the menu button and the menu |
|
|
26
|
+
|
|
27
|
+
## Models
|
|
28
|
+
|
|
29
|
+
```mermaid
|
|
30
|
+
flowchart TD
|
|
31
|
+
Items["MenuItem[]"] --> Enabled["enabled indexes"]
|
|
32
|
+
Items --> Typeahead["typeahead match"]
|
|
33
|
+
Trigger["trigger box"] --> Place["placeMenu"]
|
|
34
|
+
Viewport["viewport"] --> Place
|
|
35
|
+
Enabled --> ViewModel["MenuViewModel"]
|
|
36
|
+
Typeahead --> ViewModel
|
|
37
|
+
Place --> ViewModel
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
- The button uses `aria-haspopup="menu"`, `aria-expanded`, and `aria-controls`. The popup is `role="menu"` and each action is `role="menuitem"`.
|
|
41
|
+
- Arrow keys, Home, and End move among enabled items. Typeahead selects by the typed prefix. Enter and Space activate the active item.
|
|
42
|
+
- Escape closes the menu and returns focus to the button. Tab closes the menu and leaves focus movement to the browser.
|
|
43
|
+
- A pointer press outside the control closes the menu. The menu flips above the button and shifts sideways so it stays in the viewport.
|
|
44
|
+
- A disabled item stays visible, exposes `aria-disabled`, and does not activate. `pressed` sets `aria-pressed`. `badge` renders inside the item.
|
|
45
|
+
- `MenuButton` is the same component as `DropdownMenu`.
|
|
46
|
+
|
|
47
|
+
## Structure
|
|
48
|
+
|
|
49
|
+
- src: runtime component, model, view-model, types, and companion tests.
|
|
50
|
+
- stories: Storybook stories for light and dark themes.
|
|
51
|
+
|
|
52
|
+
## Publish footprint
|
|
53
|
+
|
|
54
|
+
This package publishes only:
|
|
55
|
+
|
|
56
|
+
- dist
|
|
57
|
+
- README.md
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports);let t=require("react");function n(e){return[`jo-ui-menu`,e??``].filter(Boolean).join(` `)}function r(e){return[`jo-ui-menu__button`,e??``].filter(Boolean).join(` `)}function i(e,t){return[`jo-ui-menu__item`,e===!0?`jo-ui-menu__item--pressed`:``,t?`jo-ui-menu__item--disabled`:``].filter(Boolean).join(` `)}function a(e){return!e}function o(e){let t=[];return e.forEach((e,n)=>{e.disabled||t.push(n)}),t}function s(e,t,n){if(e.length===0)return-1;let r=e.indexOf(t);if(r<0)return n===1?e[0]:e[e.length-1];let i=r+n;return i<0?e[e.length-1]:i>=e.length?e[0]:e[i]}function c(e,t){return e.length===0?-1:t===`first`?e[0]:e[e.length-1]}function l(e,t,n,r){return n-t>500?r.toLowerCase():`${e}${r}`.toLowerCase()}function u(e,t,n){let r=e.map((e,t)=>({index:t,label:e.label.toLowerCase(),disabled:e.disabled})).filter(e=>!e.disabled);if(r.length===0||t.length===0)return-1;let i=r.findIndex(e=>e.index===n),a=+(t.length===1),o=i<0?0:(i+a)%r.length;for(let e=0;e<r.length;e+=1){let n=r[(o+e)%r.length];if(n.label.startsWith(t))return n.index}return-1}function d(e,t,n,r){return e.length===1&&!t&&!n&&!r}function f(e,t,n,r=8,i=4){let a=n.height-e.bottom-r,o=e.top-r,s=t.height+i>a&&o>a,c=Math.max(0,(s?o:a)-i),l=Math.min(t.height,c),u=s?Math.max(r,e.top-i-l):e.bottom+i,d=e.left;return d+t.width>n.width-r&&(d=n.width-r-t.width),d<r&&(d=r),{top:u,left:d,maxHeight:c}}function p(e,t){return!e||!t||typeof t.nodeType!=`number`?!1:!e.contains(t)}function m(e,t){return e!==null&&e.top===t.top&&e.left===t.left&&e.maxHeight===t.maxHeight}function h(e,t,n,r){let a=e.disabled??!1;return{id:e.id,index:t,label:e.label,disabled:a,badge:e.badge,pressed:e.pressed,icon:e.icon,className:i(e.pressed,a),tabIndex:n&&t===r?0:-1,ariaPressed:e.pressed}}function g(e){let{label:i,disabled:g=!1,className:_,buttonClassName:v,items:y=[],onSelect:b}=e,x=(0,t.useId)(),S=(0,t.useRef)(null),C=(0,t.useRef)(null),w=(0,t.useRef)(null),T=(0,t.useRef)({query:``,at:0}),[E,D]=(0,t.useState)(!1),[O,k]=(0,t.useState)(-1),[A,j]=(0,t.useState)(null),M=(0,t.useRef)(!1);M.current=E;let N=y.map(e=>({id:e.id,label:e.label,disabled:e.disabled??!1})),P=o(N),F=(0,t.useCallback)(e=>{a(g)&&(k(c(P,e)),D(!0))},[g,P]),I=(0,t.useCallback)(e=>{D(!1),e&&C.current?.focus()},[]),L=(0,t.useCallback)(e=>{let t=y[e];t&&a(t.disabled??!1)&&(b?.(t.id),D(!1),C.current?.focus())},[y,b]),R=(0,t.useCallback)(()=>{if(a(g)){if(M.current){D(!1);return}k(c(P,`first`)),D(!0)}},[g,P]),z=(0,t.useCallback)(e=>{if(e.key===`ArrowDown`){e.preventDefault(),F(`first`);return}e.key===`ArrowUp`&&(e.preventDefault(),F(`last`))},[F]),B=(0,t.useCallback)(e=>{if(e.key===`ArrowDown`||e.key===`ArrowUp`){e.preventDefault(),k(t=>s(P,t,e.key===`ArrowDown`?1:-1));return}if(e.key===`Home`||e.key===`End`){e.preventDefault(),k(c(P,e.key===`Home`?`first`:`last`));return}if(e.key===`Enter`||e.key===` `){e.preventDefault(),L(O);return}if(e.key===`Escape`){e.preventDefault(),e.stopPropagation?.(),I(!0);return}if(e.key===`Tab`){I(!1);return}if(!d(e.key,e.ctrlKey===!0,e.metaKey===!0,e.altKey===!0))return;let t=Date.now(),n=l(T.current.query,T.current.at,t,e.key);T.current={query:n,at:t};let r=u(N,n,O);r>=0&&(e.preventDefault(),k(r))},[O,I,P,N,L]),V=(0,t.useCallback)(e=>{let t=y.findIndex(t=>t.id===e);L(t)},[y,L]),H=(0,t.useCallback)(e=>{let t=y.findIndex(t=>t.id===e),n=y[t];n&&!n.disabled&&k(t)},[y]);return(0,t.useLayoutEffect)(()=>{if(!E)return;let e=w.current;if(!e)return;let t=e.querySelector(`[data-menu-index="${O}"]`);if(t){t.focus();return}e.focus()},[O,E]),(0,t.useLayoutEffect)(()=>{if(!E)return;let e=()=>{let e=C.current,t=w.current;if(!e||!t)return;let n=f(e.getBoundingClientRect(),{width:t.offsetWidth,height:t.offsetHeight},{width:window.innerWidth,height:window.innerHeight});j(e=>m(e,n)?e:n)};return e(),window.addEventListener(`resize`,e),()=>window.removeEventListener(`resize`,e)},[E,y]),(0,t.useEffect)(()=>{if(!E)return;let e=e=>{p(S.current,e.target)&&D(!1)};return document.addEventListener(`pointerdown`,e,!0),()=>document.removeEventListener(`pointerdown`,e,!0)},[E]),{label:i,disabled:g,open:E,menuId:x,rootClassName:n(_),buttonClassName:r(v),menuClassName:`jo-ui-menu__list`,menuStyle:{position:`fixed`,top:A?.top??0,left:A?.left??0,maxHeight:A?.maxHeight},items:y.map((e,t)=>h(e,t,E,O)),rootRef:S,buttonRef:C,menuRef:w,onButtonClick:R,onButtonKeyDown:z,onMenuKeyDown:B,onItemClick:V,onItemHover:H}}var _=e((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.fragment`);function r(e,n,r){var i=null;if(r!==void 0&&(i=``+r),n.key!==void 0&&(i=``+n.key),`key`in n)for(var a in r={},n)a!==`key`&&(r[a]=n[a]);else r=n;return n=r.ref,{$$typeof:t,type:e,key:i,ref:n===void 0?null:n,props:r}}e.Fragment=n,e.jsx=r,e.jsxs=r})),v=e((e=>{process.env.NODE_ENV!==`production`&&(function(){function t(e){if(e==null)return null;if(typeof e==`function`)return e.$$typeof===k?null:e.displayName||e.name||null;if(typeof e==`string`)return e;switch(e){case _:return`Fragment`;case y:return`Profiler`;case v:return`StrictMode`;case C:return`Suspense`;case w:return`SuspenseList`;case D:return`Activity`;case O:return`ViewTransition`}if(typeof e==`object`)switch(typeof e.tag==`number`&&console.error(`Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue.`),e.$$typeof){case g:return`Portal`;case x:return e.displayName||`Context`;case b:return(e._context.displayName||`Context`)+`.Consumer`;case S:var n=e.render;return e=e.displayName,e||=(e=n.displayName||n.name||``,e===``?`ForwardRef`:`ForwardRef(`+e+`)`),e;case T:return n=e.displayName||null,n===null?t(e.type)||`Memo`:n;case E:n=e._payload,e=e._init;try{return t(e(n))}catch{}}return null}function n(e){return``+e}function r(e){try{n(e);var t=!1}catch{t=!0}if(t){t=console;var r=t.error,i=typeof Symbol==`function`&&Symbol.toStringTag&&e[Symbol.toStringTag]||e.constructor.name||`Object`;return r.call(t,`The provided key is an unsupported type %s. This value must be coerced to a string before using it here.`,i),n(e)}}function i(e){if(e===_)return`<>`;if(typeof e==`object`&&e&&e.$$typeof===E)return`<...>`;try{var n=t(e);return n?`<`+n+`>`:`<...>`}catch{return`<...>`}}function a(){var e=A.A;return e===null?null:e.getOwner()}function o(){return Error(`react-stack-top-frame`)}function s(e){if(j.call(e,`key`)){var t=Object.getOwnPropertyDescriptor(e,`key`).get;if(t&&t.isReactWarning)return!1}return e.key!==void 0}function c(e,t){function n(){P||(P=!0,console.error("%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)",t))}n.isReactWarning=!0,Object.defineProperty(e,"key",{get:n,configurable:!0})}function l(){var e=t(this.type);return F[e]||(F[e]=!0,console.error(`Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release.`)),e=this.props.ref,e===void 0?null:e}function u(e,t,n,r,i,a){var o=n.ref;return e={$$typeof:h,type:e,key:t,props:n,_owner:r},(o===void 0?null:o)===null?Object.defineProperty(e,"ref",{enumerable:!1,value:null}):Object.defineProperty(e,"ref",{enumerable:!1,get:l}),e._store={},Object.defineProperty(e._store,"validated",{configurable:!1,enumerable:!1,writable:!0,value:0}),Object.defineProperty(e,"_debugInfo",{configurable:!1,enumerable:!1,writable:!0,value:null}),Object.defineProperty(e,"_debugStack",{configurable:!1,enumerable:!1,writable:!0,value:i}),Object.defineProperty(e,"_debugTask",{configurable:!1,enumerable:!1,writable:!0,value:a}),Object.freeze&&(Object.freeze(e.props),Object.freeze(e)),e}function d(e,n,i,o,l,d){var p=n.children;if(p!==void 0){if(o){if(M(p)){for(o=0;o<p.length;o++)f(p[o]);Object.freeze&&Object.freeze(p)}else console.error(`React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead.`)}else f(p)}if(j.call(n,`key`)){p=t(e);var m=Object.keys(n).filter(function(e){return e!==`key`});o=0<m.length?`{key: someKey, `+m.join(`: ..., `)+`: ...}`:`{key: someKey}`,R[p+o]||(m=0<m.length?`{`+m.join(`: ..., `)+`: ...}`:`{}`,console.error(`A props object containing a "key" prop is being spread into JSX:
|
|
2
|
+
let props = %s;
|
|
3
|
+
<%s {...props} />
|
|
4
|
+
React keys must be passed directly to JSX without using spread:
|
|
5
|
+
let props = %s;
|
|
6
|
+
<%s key={someKey} {...props} />`,o,p,m,p),R[p+o]=!0)}if(p=null,i!==void 0&&(r(i),p=``+i),s(n)&&(r(n.key),p=``+n.key),`key`in n)for(var h in i={},n)h!==`key`&&(i[h]=n[h]);else i=n;return p&&c(i,typeof e==`function`?e.displayName||e.name||`Unknown`:e),u(e,p,i,a(),l,d)}function f(e){p(e)?e._store&&(e._store.validated=1):typeof e==`object`&&e&&e.$$typeof===E&&(e._payload.status===`fulfilled`?p(e._payload.value)&&e._payload.value._store&&(e._payload.value._store.validated=1):e._store&&(e._store.validated=1))}function p(e){return typeof e==`object`&&!!e&&e.$$typeof===h}var m=require("react"),h=Symbol.for(`react.transitional.element`),g=Symbol.for(`react.portal`),_=Symbol.for(`react.fragment`),v=Symbol.for(`react.strict_mode`),y=Symbol.for(`react.profiler`),b=Symbol.for(`react.consumer`),x=Symbol.for(`react.context`),S=Symbol.for(`react.forward_ref`),C=Symbol.for(`react.suspense`),w=Symbol.for(`react.suspense_list`),T=Symbol.for(`react.memo`),E=Symbol.for(`react.lazy`),D=Symbol.for(`react.activity`),O=Symbol.for(`react.view_transition`),k=Symbol.for(`react.client.reference`),A=m.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,j=Object.prototype.hasOwnProperty,M=Array.isArray,N=console.createTask?console.createTask:function(){return null};m={react_stack_bottom_frame:function(e){return e()}};var P,F={},I=m.react_stack_bottom_frame.bind(m,o)(),L=N(i(o)),R={};e.Fragment=_,e.jsx=function(e,t,n){var r=1e4>A.recentlyCreatedOwnerStacks++;if(r){var a=Error.stackTraceLimit;Error.stackTraceLimit=10;var o=Error(`react-stack-top-frame`);Error.stackTraceLimit=a}else o=I;return d(e,t,n,!1,o,r?N(i(e)):L)},e.jsxs=function(e,t,n){var r=1e4>A.recentlyCreatedOwnerStacks++;if(r){var a=Error.stackTraceLimit;Error.stackTraceLimit=10;var o=Error(`react-stack-top-frame`);Error.stackTraceLimit=a}else o=I;return d(e,t,n,!0,o,r?N(i(e)):L)}})()})),y=e(((e,t)=>{t.exports=process.env.NODE_ENV===`production`?_():v()}))();function b(e){let t=g(e);return(0,y.jsxs)(`div`,{ref:t.rootRef,className:t.rootClassName,children:[(0,y.jsx)(`button`,{ref:t.buttonRef,type:`button`,className:t.buttonClassName,disabled:t.disabled,"aria-haspopup":`menu`,"aria-expanded":t.open,"aria-controls":t.menuId,onClick:t.onButtonClick,onKeyDown:t.onButtonKeyDown,children:t.label}),(0,y.jsx)(`div`,{ref:t.menuRef,id:t.menuId,role:`menu`,"aria-label":t.label,hidden:!t.open||void 0,tabIndex:-1,className:t.menuClassName,style:t.menuStyle,onKeyDown:t.onMenuKeyDown,children:t.items.map(e=>(0,y.jsxs)(`button`,{type:`button`,role:`menuitem`,"data-menu-index":e.index,className:e.className,tabIndex:e.tabIndex,"aria-disabled":e.disabled||void 0,"aria-pressed":e.ariaPressed,onClick:()=>t.onItemClick(e.id),onMouseEnter:()=>t.onItemHover(e.id),children:[e.icon?(0,y.jsx)(`span`,{className:`jo-ui-menu__icon`,"aria-hidden":`true`,children:e.icon}):null,(0,y.jsx)(`span`,{className:`jo-ui-menu__label`,children:e.label}),e.badge?(0,y.jsx)(`span`,{className:`jo-ui-menu__badge`,children:e.badge}):null]},e.id))})]})}var x=b;exports.DropdownMenu=b,exports.MenuButton=x,exports.menuButtonClassName=r;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
import "./jo-ui-menu.css";
|
|
2
|
+
import { useCallback as e, useEffect as t, useId as n, useLayoutEffect as r, useRef as i, useState as a } from "react";
|
|
3
|
+
//#region \0rolldown/runtime.js
|
|
4
|
+
var o = (e, t) => () => (t || (e((t = { exports: {} }).exports, t), e = null), t.exports), s = /* @__PURE__ */ ((e) => typeof require < "u" ? require : typeof Proxy < "u" ? new Proxy(e, { get: (e, t) => (typeof require < "u" ? require : e)[t] }) : e)(function(e) {
|
|
5
|
+
if (typeof require < "u") return require.apply(this, arguments);
|
|
6
|
+
throw Error("Calling `require` for \"" + e + "\" in an environment that doesn't expose the `require` function. See https://rolldown.rs/in-depth/bundling-cjs#require-external-modules for more details.");
|
|
7
|
+
});
|
|
8
|
+
function c(e) {
|
|
9
|
+
return ["jo-ui-menu", e ?? ""].filter(Boolean).join(" ");
|
|
10
|
+
}
|
|
11
|
+
function l(e) {
|
|
12
|
+
return ["jo-ui-menu__button", e ?? ""].filter(Boolean).join(" ");
|
|
13
|
+
}
|
|
14
|
+
function u(e, t) {
|
|
15
|
+
return [
|
|
16
|
+
"jo-ui-menu__item",
|
|
17
|
+
e === !0 ? "jo-ui-menu__item--pressed" : "",
|
|
18
|
+
t ? "jo-ui-menu__item--disabled" : ""
|
|
19
|
+
].filter(Boolean).join(" ");
|
|
20
|
+
}
|
|
21
|
+
function d(e) {
|
|
22
|
+
return !e;
|
|
23
|
+
}
|
|
24
|
+
function f(e) {
|
|
25
|
+
let t = [];
|
|
26
|
+
return e.forEach((e, n) => {
|
|
27
|
+
e.disabled || t.push(n);
|
|
28
|
+
}), t;
|
|
29
|
+
}
|
|
30
|
+
function p(e, t, n) {
|
|
31
|
+
if (e.length === 0) return -1;
|
|
32
|
+
let r = e.indexOf(t);
|
|
33
|
+
if (r < 0) return n === 1 ? e[0] : e[e.length - 1];
|
|
34
|
+
let i = r + n;
|
|
35
|
+
return i < 0 ? e[e.length - 1] : i >= e.length ? e[0] : e[i];
|
|
36
|
+
}
|
|
37
|
+
function m(e, t) {
|
|
38
|
+
return e.length === 0 ? -1 : t === "first" ? e[0] : e[e.length - 1];
|
|
39
|
+
}
|
|
40
|
+
function h(e, t, n, r) {
|
|
41
|
+
return n - t > 500 ? r.toLowerCase() : `${e}${r}`.toLowerCase();
|
|
42
|
+
}
|
|
43
|
+
function g(e, t, n) {
|
|
44
|
+
let r = e.map((e, t) => ({
|
|
45
|
+
index: t,
|
|
46
|
+
label: e.label.toLowerCase(),
|
|
47
|
+
disabled: e.disabled
|
|
48
|
+
})).filter((e) => !e.disabled);
|
|
49
|
+
if (r.length === 0 || t.length === 0) return -1;
|
|
50
|
+
let i = r.findIndex((e) => e.index === n), a = +(t.length === 1), o = i < 0 ? 0 : (i + a) % r.length;
|
|
51
|
+
for (let e = 0; e < r.length; e += 1) {
|
|
52
|
+
let n = r[(o + e) % r.length];
|
|
53
|
+
if (n.label.startsWith(t)) return n.index;
|
|
54
|
+
}
|
|
55
|
+
return -1;
|
|
56
|
+
}
|
|
57
|
+
function _(e, t, n, r) {
|
|
58
|
+
return e.length === 1 && !t && !n && !r;
|
|
59
|
+
}
|
|
60
|
+
function v(e, t, n, r = 8, i = 4) {
|
|
61
|
+
let a = n.height - e.bottom - r, o = e.top - r, s = t.height + i > a && o > a, c = Math.max(0, (s ? o : a) - i), l = Math.min(t.height, c), u = s ? Math.max(r, e.top - i - l) : e.bottom + i, d = e.left;
|
|
62
|
+
return d + t.width > n.width - r && (d = n.width - r - t.width), d < r && (d = r), {
|
|
63
|
+
top: u,
|
|
64
|
+
left: d,
|
|
65
|
+
maxHeight: c
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function y(e, t) {
|
|
69
|
+
return !e || !t || typeof t.nodeType != "number" ? !1 : !e.contains(t);
|
|
70
|
+
}
|
|
71
|
+
function b(e, t) {
|
|
72
|
+
return e !== null && e.top === t.top && e.left === t.left && e.maxHeight === t.maxHeight;
|
|
73
|
+
}
|
|
74
|
+
function x(e, t, n, r) {
|
|
75
|
+
let i = e.disabled ?? !1;
|
|
76
|
+
return {
|
|
77
|
+
id: e.id,
|
|
78
|
+
index: t,
|
|
79
|
+
label: e.label,
|
|
80
|
+
disabled: i,
|
|
81
|
+
badge: e.badge,
|
|
82
|
+
pressed: e.pressed,
|
|
83
|
+
icon: e.icon,
|
|
84
|
+
className: u(e.pressed, i),
|
|
85
|
+
tabIndex: n && t === r ? 0 : -1,
|
|
86
|
+
ariaPressed: e.pressed
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
//#endregion
|
|
90
|
+
//#region src/jo-ui-menu.view-model.ts
|
|
91
|
+
function S(o) {
|
|
92
|
+
let { label: s, disabled: u = !1, className: S, buttonClassName: C, items: w = [], onSelect: T } = o, E = n(), D = i(null), O = i(null), k = i(null), A = i({
|
|
93
|
+
query: "",
|
|
94
|
+
at: 0
|
|
95
|
+
}), [j, M] = a(!1), [N, P] = a(-1), [F, I] = a(null), L = i(!1);
|
|
96
|
+
L.current = j;
|
|
97
|
+
let R = w.map((e) => ({
|
|
98
|
+
id: e.id,
|
|
99
|
+
label: e.label,
|
|
100
|
+
disabled: e.disabled ?? !1
|
|
101
|
+
})), z = f(R), B = e((e) => {
|
|
102
|
+
d(u) && (P(m(z, e)), M(!0));
|
|
103
|
+
}, [u, z]), V = e((e) => {
|
|
104
|
+
M(!1), e && O.current?.focus();
|
|
105
|
+
}, []), H = e((e) => {
|
|
106
|
+
let t = w[e];
|
|
107
|
+
t && d(t.disabled ?? !1) && (T?.(t.id), M(!1), O.current?.focus());
|
|
108
|
+
}, [w, T]), U = e(() => {
|
|
109
|
+
if (d(u)) {
|
|
110
|
+
if (L.current) {
|
|
111
|
+
M(!1);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
P(m(z, "first")), M(!0);
|
|
115
|
+
}
|
|
116
|
+
}, [u, z]), W = e((e) => {
|
|
117
|
+
if (e.key === "ArrowDown") {
|
|
118
|
+
e.preventDefault(), B("first");
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
e.key === "ArrowUp" && (e.preventDefault(), B("last"));
|
|
122
|
+
}, [B]), G = e((e) => {
|
|
123
|
+
if (e.key === "ArrowDown" || e.key === "ArrowUp") {
|
|
124
|
+
e.preventDefault(), P((t) => p(z, t, e.key === "ArrowDown" ? 1 : -1));
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (e.key === "Home" || e.key === "End") {
|
|
128
|
+
e.preventDefault(), P(m(z, e.key === "Home" ? "first" : "last"));
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
132
|
+
e.preventDefault(), H(N);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (e.key === "Escape") {
|
|
136
|
+
e.preventDefault(), e.stopPropagation?.(), V(!0);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (e.key === "Tab") {
|
|
140
|
+
V(!1);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (!_(e.key, e.ctrlKey === !0, e.metaKey === !0, e.altKey === !0)) return;
|
|
144
|
+
let t = Date.now(), n = h(A.current.query, A.current.at, t, e.key);
|
|
145
|
+
A.current = {
|
|
146
|
+
query: n,
|
|
147
|
+
at: t
|
|
148
|
+
};
|
|
149
|
+
let r = g(R, n, N);
|
|
150
|
+
r >= 0 && (e.preventDefault(), P(r));
|
|
151
|
+
}, [
|
|
152
|
+
N,
|
|
153
|
+
V,
|
|
154
|
+
z,
|
|
155
|
+
R,
|
|
156
|
+
H
|
|
157
|
+
]), K = e((e) => {
|
|
158
|
+
let t = w.findIndex((t) => t.id === e);
|
|
159
|
+
H(t);
|
|
160
|
+
}, [w, H]), q = e((e) => {
|
|
161
|
+
let t = w.findIndex((t) => t.id === e), n = w[t];
|
|
162
|
+
n && !n.disabled && P(t);
|
|
163
|
+
}, [w]);
|
|
164
|
+
return r(() => {
|
|
165
|
+
if (!j) return;
|
|
166
|
+
let e = k.current;
|
|
167
|
+
if (!e) return;
|
|
168
|
+
let t = e.querySelector(`[data-menu-index="${N}"]`);
|
|
169
|
+
if (t) {
|
|
170
|
+
t.focus();
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
e.focus();
|
|
174
|
+
}, [N, j]), r(() => {
|
|
175
|
+
if (!j) return;
|
|
176
|
+
let e = () => {
|
|
177
|
+
let e = O.current, t = k.current;
|
|
178
|
+
if (!e || !t) return;
|
|
179
|
+
let n = v(e.getBoundingClientRect(), {
|
|
180
|
+
width: t.offsetWidth,
|
|
181
|
+
height: t.offsetHeight
|
|
182
|
+
}, {
|
|
183
|
+
width: window.innerWidth,
|
|
184
|
+
height: window.innerHeight
|
|
185
|
+
});
|
|
186
|
+
I((e) => b(e, n) ? e : n);
|
|
187
|
+
};
|
|
188
|
+
return e(), window.addEventListener("resize", e), () => window.removeEventListener("resize", e);
|
|
189
|
+
}, [j, w]), t(() => {
|
|
190
|
+
if (!j) return;
|
|
191
|
+
let e = (e) => {
|
|
192
|
+
y(D.current, e.target) && M(!1);
|
|
193
|
+
};
|
|
194
|
+
return document.addEventListener("pointerdown", e, !0), () => document.removeEventListener("pointerdown", e, !0);
|
|
195
|
+
}, [j]), {
|
|
196
|
+
label: s,
|
|
197
|
+
disabled: u,
|
|
198
|
+
open: j,
|
|
199
|
+
menuId: E,
|
|
200
|
+
rootClassName: c(S),
|
|
201
|
+
buttonClassName: l(C),
|
|
202
|
+
menuClassName: "jo-ui-menu__list",
|
|
203
|
+
menuStyle: {
|
|
204
|
+
position: "fixed",
|
|
205
|
+
top: F?.top ?? 0,
|
|
206
|
+
left: F?.left ?? 0,
|
|
207
|
+
maxHeight: F?.maxHeight
|
|
208
|
+
},
|
|
209
|
+
items: w.map((e, t) => x(e, t, j, N)),
|
|
210
|
+
rootRef: D,
|
|
211
|
+
buttonRef: O,
|
|
212
|
+
menuRef: k,
|
|
213
|
+
onButtonClick: U,
|
|
214
|
+
onButtonKeyDown: W,
|
|
215
|
+
onMenuKeyDown: G,
|
|
216
|
+
onItemClick: K,
|
|
217
|
+
onItemHover: q
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
//#endregion
|
|
221
|
+
//#region ../../node_modules/.pnpm/react@19.3.0/node_modules/react/cjs/react-jsx-runtime.production.js
|
|
222
|
+
var C = /* @__PURE__ */ o(((e) => {
|
|
223
|
+
var t = Symbol.for("react.transitional.element"), n = Symbol.for("react.fragment");
|
|
224
|
+
function r(e, n, r) {
|
|
225
|
+
var i = null;
|
|
226
|
+
if (r !== void 0 && (i = "" + r), n.key !== void 0 && (i = "" + n.key), "key" in n) for (var a in r = {}, n) a !== "key" && (r[a] = n[a]);
|
|
227
|
+
else r = n;
|
|
228
|
+
return n = r.ref, {
|
|
229
|
+
$$typeof: t,
|
|
230
|
+
type: e,
|
|
231
|
+
key: i,
|
|
232
|
+
ref: n === void 0 ? null : n,
|
|
233
|
+
props: r
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
e.Fragment = n, e.jsx = r, e.jsxs = r;
|
|
237
|
+
})), w = /* @__PURE__ */ o(((e) => {
|
|
238
|
+
process.env.NODE_ENV !== "production" && (function() {
|
|
239
|
+
function t(e) {
|
|
240
|
+
if (e == null) return null;
|
|
241
|
+
if (typeof e == "function") return e.$$typeof === A ? null : e.displayName || e.name || null;
|
|
242
|
+
if (typeof e == "string") return e;
|
|
243
|
+
switch (e) {
|
|
244
|
+
case v: return "Fragment";
|
|
245
|
+
case b: return "Profiler";
|
|
246
|
+
case y: return "StrictMode";
|
|
247
|
+
case w: return "Suspense";
|
|
248
|
+
case T: return "SuspenseList";
|
|
249
|
+
case O: return "Activity";
|
|
250
|
+
case k: return "ViewTransition";
|
|
251
|
+
}
|
|
252
|
+
if (typeof e == "object") switch (typeof e.tag == "number" && console.error("Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."), e.$$typeof) {
|
|
253
|
+
case _: return "Portal";
|
|
254
|
+
case S: return e.displayName || "Context";
|
|
255
|
+
case x: return (e._context.displayName || "Context") + ".Consumer";
|
|
256
|
+
case C:
|
|
257
|
+
var n = e.render;
|
|
258
|
+
return e = e.displayName, e ||= (e = n.displayName || n.name || "", e === "" ? "ForwardRef" : "ForwardRef(" + e + ")"), e;
|
|
259
|
+
case E: return n = e.displayName || null, n === null ? t(e.type) || "Memo" : n;
|
|
260
|
+
case D:
|
|
261
|
+
n = e._payload, e = e._init;
|
|
262
|
+
try {
|
|
263
|
+
return t(e(n));
|
|
264
|
+
} catch {}
|
|
265
|
+
}
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
function n(e) {
|
|
269
|
+
return "" + e;
|
|
270
|
+
}
|
|
271
|
+
function r(e) {
|
|
272
|
+
try {
|
|
273
|
+
n(e);
|
|
274
|
+
var t = !1;
|
|
275
|
+
} catch {
|
|
276
|
+
t = !0;
|
|
277
|
+
}
|
|
278
|
+
if (t) {
|
|
279
|
+
t = console;
|
|
280
|
+
var r = t.error, i = typeof Symbol == "function" && Symbol.toStringTag && e[Symbol.toStringTag] || e.constructor.name || "Object";
|
|
281
|
+
return r.call(t, "The provided key is an unsupported type %s. This value must be coerced to a string before using it here.", i), n(e);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
function i(e) {
|
|
285
|
+
if (e === v) return "<>";
|
|
286
|
+
if (typeof e == "object" && e && e.$$typeof === D) return "<...>";
|
|
287
|
+
try {
|
|
288
|
+
var n = t(e);
|
|
289
|
+
return n ? "<" + n + ">" : "<...>";
|
|
290
|
+
} catch {
|
|
291
|
+
return "<...>";
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
function a() {
|
|
295
|
+
var e = j.A;
|
|
296
|
+
return e === null ? null : e.getOwner();
|
|
297
|
+
}
|
|
298
|
+
function o() {
|
|
299
|
+
return Error("react-stack-top-frame");
|
|
300
|
+
}
|
|
301
|
+
function c(e) {
|
|
302
|
+
if (M.call(e, "key")) {
|
|
303
|
+
var t = Object.getOwnPropertyDescriptor(e, "key").get;
|
|
304
|
+
if (t && t.isReactWarning) return !1;
|
|
305
|
+
}
|
|
306
|
+
return e.key !== void 0;
|
|
307
|
+
}
|
|
308
|
+
function l(e, t) {
|
|
309
|
+
function n() {
|
|
310
|
+
F || (F = !0, console.error("%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)", t));
|
|
311
|
+
}
|
|
312
|
+
n.isReactWarning = !0, Object.defineProperty(e, "key", {
|
|
313
|
+
get: n,
|
|
314
|
+
configurable: !0
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
function u() {
|
|
318
|
+
var e = t(this.type);
|
|
319
|
+
return I[e] || (I[e] = !0, console.error("Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release.")), e = this.props.ref, e === void 0 ? null : e;
|
|
320
|
+
}
|
|
321
|
+
function d(e, t, n, r, i, a) {
|
|
322
|
+
var o = n.ref;
|
|
323
|
+
return e = {
|
|
324
|
+
$$typeof: g,
|
|
325
|
+
type: e,
|
|
326
|
+
key: t,
|
|
327
|
+
props: n,
|
|
328
|
+
_owner: r
|
|
329
|
+
}, (o === void 0 ? null : o) === null ? Object.defineProperty(e, "ref", {
|
|
330
|
+
enumerable: !1,
|
|
331
|
+
value: null
|
|
332
|
+
}) : Object.defineProperty(e, "ref", {
|
|
333
|
+
enumerable: !1,
|
|
334
|
+
get: u
|
|
335
|
+
}), e._store = {}, Object.defineProperty(e._store, "validated", {
|
|
336
|
+
configurable: !1,
|
|
337
|
+
enumerable: !1,
|
|
338
|
+
writable: !0,
|
|
339
|
+
value: 0
|
|
340
|
+
}), Object.defineProperty(e, "_debugInfo", {
|
|
341
|
+
configurable: !1,
|
|
342
|
+
enumerable: !1,
|
|
343
|
+
writable: !0,
|
|
344
|
+
value: null
|
|
345
|
+
}), Object.defineProperty(e, "_debugStack", {
|
|
346
|
+
configurable: !1,
|
|
347
|
+
enumerable: !1,
|
|
348
|
+
writable: !0,
|
|
349
|
+
value: i
|
|
350
|
+
}), Object.defineProperty(e, "_debugTask", {
|
|
351
|
+
configurable: !1,
|
|
352
|
+
enumerable: !1,
|
|
353
|
+
writable: !0,
|
|
354
|
+
value: a
|
|
355
|
+
}), Object.freeze && (Object.freeze(e.props), Object.freeze(e)), e;
|
|
356
|
+
}
|
|
357
|
+
function f(e, n, i, o, s, u) {
|
|
358
|
+
var f = n.children;
|
|
359
|
+
if (f !== void 0) {
|
|
360
|
+
if (o) {
|
|
361
|
+
if (N(f)) {
|
|
362
|
+
for (o = 0; o < f.length; o++) p(f[o]);
|
|
363
|
+
Object.freeze && Object.freeze(f);
|
|
364
|
+
} else console.error("React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead.");
|
|
365
|
+
} else p(f);
|
|
366
|
+
}
|
|
367
|
+
if (M.call(n, "key")) {
|
|
368
|
+
f = t(e);
|
|
369
|
+
var m = Object.keys(n).filter(function(e) {
|
|
370
|
+
return e !== "key";
|
|
371
|
+
});
|
|
372
|
+
o = 0 < m.length ? "{key: someKey, " + m.join(": ..., ") + ": ...}" : "{key: someKey}", z[f + o] || (m = 0 < m.length ? "{" + m.join(": ..., ") + ": ...}" : "{}", console.error("A props object containing a \"key\" prop is being spread into JSX:\n let props = %s;\n <%s {...props} />\nReact keys must be passed directly to JSX without using spread:\n let props = %s;\n <%s key={someKey} {...props} />", o, f, m, f), z[f + o] = !0);
|
|
373
|
+
}
|
|
374
|
+
if (f = null, i !== void 0 && (r(i), f = "" + i), c(n) && (r(n.key), f = "" + n.key), "key" in n) for (var h in i = {}, n) h !== "key" && (i[h] = n[h]);
|
|
375
|
+
else i = n;
|
|
376
|
+
return f && l(i, typeof e == "function" ? e.displayName || e.name || "Unknown" : e), d(e, f, i, a(), s, u);
|
|
377
|
+
}
|
|
378
|
+
function p(e) {
|
|
379
|
+
m(e) ? e._store && (e._store.validated = 1) : typeof e == "object" && e && e.$$typeof === D && (e._payload.status === "fulfilled" ? m(e._payload.value) && e._payload.value._store && (e._payload.value._store.validated = 1) : e._store && (e._store.validated = 1));
|
|
380
|
+
}
|
|
381
|
+
function m(e) {
|
|
382
|
+
return typeof e == "object" && !!e && e.$$typeof === g;
|
|
383
|
+
}
|
|
384
|
+
var h = s("react"), g = Symbol.for("react.transitional.element"), _ = Symbol.for("react.portal"), v = Symbol.for("react.fragment"), y = Symbol.for("react.strict_mode"), b = Symbol.for("react.profiler"), x = Symbol.for("react.consumer"), S = Symbol.for("react.context"), C = Symbol.for("react.forward_ref"), w = Symbol.for("react.suspense"), T = Symbol.for("react.suspense_list"), E = Symbol.for("react.memo"), D = Symbol.for("react.lazy"), O = Symbol.for("react.activity"), k = Symbol.for("react.view_transition"), A = Symbol.for("react.client.reference"), j = h.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, M = Object.prototype.hasOwnProperty, N = Array.isArray, P = console.createTask ? console.createTask : function() {
|
|
385
|
+
return null;
|
|
386
|
+
};
|
|
387
|
+
h = { react_stack_bottom_frame: function(e) {
|
|
388
|
+
return e();
|
|
389
|
+
} };
|
|
390
|
+
var F, I = {}, L = h.react_stack_bottom_frame.bind(h, o)(), R = P(i(o)), z = {};
|
|
391
|
+
e.Fragment = v, e.jsx = function(e, t, n) {
|
|
392
|
+
var r = 1e4 > j.recentlyCreatedOwnerStacks++;
|
|
393
|
+
if (r) {
|
|
394
|
+
var a = Error.stackTraceLimit;
|
|
395
|
+
Error.stackTraceLimit = 10;
|
|
396
|
+
var o = Error("react-stack-top-frame");
|
|
397
|
+
Error.stackTraceLimit = a;
|
|
398
|
+
} else o = L;
|
|
399
|
+
return f(e, t, n, !1, o, r ? P(i(e)) : R);
|
|
400
|
+
}, e.jsxs = function(e, t, n) {
|
|
401
|
+
var r = 1e4 > j.recentlyCreatedOwnerStacks++;
|
|
402
|
+
if (r) {
|
|
403
|
+
var a = Error.stackTraceLimit;
|
|
404
|
+
Error.stackTraceLimit = 10;
|
|
405
|
+
var o = Error("react-stack-top-frame");
|
|
406
|
+
Error.stackTraceLimit = a;
|
|
407
|
+
} else o = L;
|
|
408
|
+
return f(e, t, n, !0, o, r ? P(i(e)) : R);
|
|
409
|
+
};
|
|
410
|
+
})();
|
|
411
|
+
})), T = (/* @__PURE__ */ o(((e, t) => {
|
|
412
|
+
t.exports = process.env.NODE_ENV === "production" ? C() : w();
|
|
413
|
+
})))();
|
|
414
|
+
function E(e) {
|
|
415
|
+
let t = S(e);
|
|
416
|
+
return /* @__PURE__ */ (0, T.jsxs)("div", {
|
|
417
|
+
ref: t.rootRef,
|
|
418
|
+
className: t.rootClassName,
|
|
419
|
+
children: [/* @__PURE__ */ (0, T.jsx)("button", {
|
|
420
|
+
ref: t.buttonRef,
|
|
421
|
+
type: "button",
|
|
422
|
+
className: t.buttonClassName,
|
|
423
|
+
disabled: t.disabled,
|
|
424
|
+
"aria-haspopup": "menu",
|
|
425
|
+
"aria-expanded": t.open,
|
|
426
|
+
"aria-controls": t.menuId,
|
|
427
|
+
onClick: t.onButtonClick,
|
|
428
|
+
onKeyDown: t.onButtonKeyDown,
|
|
429
|
+
children: t.label
|
|
430
|
+
}), /* @__PURE__ */ (0, T.jsx)("div", {
|
|
431
|
+
ref: t.menuRef,
|
|
432
|
+
id: t.menuId,
|
|
433
|
+
role: "menu",
|
|
434
|
+
"aria-label": t.label,
|
|
435
|
+
hidden: !t.open || void 0,
|
|
436
|
+
tabIndex: -1,
|
|
437
|
+
className: t.menuClassName,
|
|
438
|
+
style: t.menuStyle,
|
|
439
|
+
onKeyDown: t.onMenuKeyDown,
|
|
440
|
+
children: t.items.map((e) => /* @__PURE__ */ (0, T.jsxs)("button", {
|
|
441
|
+
type: "button",
|
|
442
|
+
role: "menuitem",
|
|
443
|
+
"data-menu-index": e.index,
|
|
444
|
+
className: e.className,
|
|
445
|
+
tabIndex: e.tabIndex,
|
|
446
|
+
"aria-disabled": e.disabled || void 0,
|
|
447
|
+
"aria-pressed": e.ariaPressed,
|
|
448
|
+
onClick: () => t.onItemClick(e.id),
|
|
449
|
+
onMouseEnter: () => t.onItemHover(e.id),
|
|
450
|
+
children: [
|
|
451
|
+
e.icon ? /* @__PURE__ */ (0, T.jsx)("span", {
|
|
452
|
+
className: "jo-ui-menu__icon",
|
|
453
|
+
"aria-hidden": "true",
|
|
454
|
+
children: e.icon
|
|
455
|
+
}) : null,
|
|
456
|
+
/* @__PURE__ */ (0, T.jsx)("span", {
|
|
457
|
+
className: "jo-ui-menu__label",
|
|
458
|
+
children: e.label
|
|
459
|
+
}),
|
|
460
|
+
e.badge ? /* @__PURE__ */ (0, T.jsx)("span", {
|
|
461
|
+
className: "jo-ui-menu__badge",
|
|
462
|
+
children: e.badge
|
|
463
|
+
}) : null
|
|
464
|
+
]
|
|
465
|
+
}, e.id))
|
|
466
|
+
})]
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
var D = E;
|
|
470
|
+
//#endregion
|
|
471
|
+
export { E as DropdownMenu, D as MenuButton, l as menuButtonClassName };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
.jo-ui-menu{--jo-ui-menu-bg:var(--jo-color-surface);--jo-ui-menu-fg:var(--jo-color-text);--jo-ui-menu-muted:var(--jo-color-text-muted);--jo-ui-menu-border:var(--jo-color-border);--jo-ui-menu-accent:var(--jo-color-accent);--jo-ui-menu-accent-contrast:var(--jo-color-accent-contrast);--jo-ui-menu-surface-alt:var(--jo-color-surface-alt);--jo-ui-menu-danger:var(--jo-color-danger);display:inline-flex;position:relative}.jo-ui-menu__button{border:1px solid var(--jo-ui-menu-border);border-radius:var(--jo-radius-control,12px);background:var(--jo-ui-menu-bg);min-height:2.5rem;color:var(--jo-ui-menu-fg);font:inherit;cursor:pointer;justify-content:center;align-items:center;gap:.5rem;padding:.5rem .75rem;font-size:.875rem;font-weight:600;line-height:1;display:inline-flex}.jo-ui-menu__button:disabled{cursor:not-allowed;opacity:.65}.jo-ui-menu__button:focus-visible,.jo-ui-menu__item:focus-visible{outline:2px solid var(--jo-color-focus);outline-offset:2px}.jo-ui-menu__list{z-index:40;border:1px solid var(--jo-ui-menu-border);border-radius:var(--jo-radius-control,12px);background:var(--jo-ui-menu-bg);min-width:12rem;color:var(--jo-ui-menu-fg);flex-direction:column;gap:2px;padding:.375rem;display:flex;overflow:auto;box-shadow:0 12px 32px #0f172a29}.jo-ui-menu__list[hidden]{display:none}.jo-ui-menu__item{border-radius:var(--jo-radius-sm,6px);width:100%;min-height:2.5rem;color:var(--jo-ui-menu-fg);font:inherit;text-align:left;cursor:pointer;background:0 0;border:0;align-items:center;gap:.5rem;padding:0 .75rem;font-size:.875rem;display:flex}.jo-ui-menu__item:focus-visible,.jo-ui-menu__item:hover{background:var(--jo-ui-menu-surface-alt)}.jo-ui-menu__item--pressed,.jo-ui-menu__item[aria-pressed=true]{background:var(--jo-ui-menu-accent);color:var(--jo-ui-menu-accent-contrast)}.jo-ui-menu__item--disabled,.jo-ui-menu__item[aria-disabled=true]{cursor:not-allowed;opacity:.5}.jo-ui-menu__label{flex:auto;min-width:0}.jo-ui-menu__icon{flex:none;justify-content:center;align-items:center;display:inline-flex}.jo-ui-menu__icon,.jo-ui-menu__icon svg{width:1rem;height:1rem}.jo-ui-menu__badge{border-radius:var(--jo-radius-sm,6px);background:var(--jo-ui-menu-danger);min-width:1.25rem;height:1.25rem;color:var(--jo-color-on-scrim,#fff);flex:none;justify-content:center;align-items:center;padding:0 .25rem;font-size:.6875rem;font-weight:700;line-height:1;display:inline-flex}.visible{visibility:visible}.fixed{position:fixed}.relative{position:relative}.flex{display:flex}.hidden{display:none}.resize{resize:both}.border{border-width:1px}.outline{outline-style:solid}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter,backdrop-filter;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1)}
|
|
2
|
+
/*$vite$:1*/
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { MenuItemViewModel, MenuPosition } from './jo-ui-menu.types';
|
|
2
|
+
export declare const TYPEAHEAD_RESET_MS = 500;
|
|
3
|
+
export declare const MENU_VIEWPORT_MARGIN = 8;
|
|
4
|
+
export declare const MENU_GAP = 4;
|
|
5
|
+
export interface MenuBox {
|
|
6
|
+
readonly top: number;
|
|
7
|
+
readonly left: number;
|
|
8
|
+
readonly right: number;
|
|
9
|
+
readonly bottom: number;
|
|
10
|
+
readonly width: number;
|
|
11
|
+
readonly height: number;
|
|
12
|
+
}
|
|
13
|
+
export declare function menuRootClassName(className?: string): string;
|
|
14
|
+
export declare function menuButtonClassName(className?: string): string;
|
|
15
|
+
export declare function menuItemClassName(pressed: boolean | undefined, disabled: boolean): string;
|
|
16
|
+
export declare function canActivate(disabled: boolean): boolean;
|
|
17
|
+
export declare function enabledIndexes(items: readonly {
|
|
18
|
+
readonly disabled: boolean;
|
|
19
|
+
}[]): number[];
|
|
20
|
+
export declare function moveIndex(enabled: readonly number[], current: number, direction: 1 | -1): number;
|
|
21
|
+
export declare function endIndex(enabled: readonly number[], which: 'first' | 'last'): number;
|
|
22
|
+
export declare function typeaheadQuery(previous: string, previousAt: number, now: number, char: string): string;
|
|
23
|
+
export declare function typeaheadIndex(items: readonly {
|
|
24
|
+
readonly label: string;
|
|
25
|
+
readonly disabled: boolean;
|
|
26
|
+
}[], query: string, fromIndex: number): number;
|
|
27
|
+
export declare function isTypeaheadKey(key: string, ctrl: boolean, meta: boolean, alt: boolean): boolean;
|
|
28
|
+
export declare function placeMenu(trigger: MenuBox, menu: {
|
|
29
|
+
readonly width: number;
|
|
30
|
+
readonly height: number;
|
|
31
|
+
}, viewport: {
|
|
32
|
+
readonly width: number;
|
|
33
|
+
readonly height: number;
|
|
34
|
+
}, margin?: number, gap?: number): MenuPosition;
|
|
35
|
+
export declare function isOutsideMenu(root: {
|
|
36
|
+
contains: (node: Node) => boolean;
|
|
37
|
+
} | null, target: EventTarget | null): boolean;
|
|
38
|
+
export declare function sameMenuPosition(current: MenuPosition | null, next: MenuPosition): boolean;
|
|
39
|
+
export declare function toMenuItemViewModel(item: {
|
|
40
|
+
readonly id: string;
|
|
41
|
+
readonly label: string;
|
|
42
|
+
readonly disabled?: boolean;
|
|
43
|
+
readonly badge?: string;
|
|
44
|
+
readonly pressed?: boolean;
|
|
45
|
+
readonly icon?: MenuItemViewModel['icon'];
|
|
46
|
+
}, index: number, open: boolean, activeIndex: number): MenuItemViewModel;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { CSSProperties, ReactNode, RefObject } from 'react';
|
|
2
|
+
export interface MenuItem {
|
|
3
|
+
readonly id: string;
|
|
4
|
+
readonly label: string;
|
|
5
|
+
readonly disabled?: boolean;
|
|
6
|
+
readonly badge?: string;
|
|
7
|
+
readonly pressed?: boolean;
|
|
8
|
+
readonly icon?: ReactNode;
|
|
9
|
+
}
|
|
10
|
+
export interface DropdownMenuProps {
|
|
11
|
+
readonly id?: string;
|
|
12
|
+
readonly label: string;
|
|
13
|
+
readonly disabled?: boolean;
|
|
14
|
+
readonly className?: string;
|
|
15
|
+
readonly buttonClassName?: string;
|
|
16
|
+
readonly items?: readonly MenuItem[];
|
|
17
|
+
readonly onSelect?: (itemId: string) => void;
|
|
18
|
+
}
|
|
19
|
+
export type MenuButtonProps = DropdownMenuProps;
|
|
20
|
+
export interface MenuItemViewModel {
|
|
21
|
+
readonly id: string;
|
|
22
|
+
readonly index: number;
|
|
23
|
+
readonly label: string;
|
|
24
|
+
readonly disabled: boolean;
|
|
25
|
+
readonly badge?: string;
|
|
26
|
+
readonly pressed?: boolean;
|
|
27
|
+
readonly icon?: ReactNode;
|
|
28
|
+
readonly className: string;
|
|
29
|
+
readonly tabIndex: number;
|
|
30
|
+
readonly ariaPressed: boolean | undefined;
|
|
31
|
+
}
|
|
32
|
+
export interface MenuPosition {
|
|
33
|
+
readonly top: number;
|
|
34
|
+
readonly left: number;
|
|
35
|
+
readonly maxHeight: number;
|
|
36
|
+
}
|
|
37
|
+
export interface MenuKeyEvent {
|
|
38
|
+
readonly key: string;
|
|
39
|
+
readonly ctrlKey?: boolean;
|
|
40
|
+
readonly metaKey?: boolean;
|
|
41
|
+
readonly altKey?: boolean;
|
|
42
|
+
preventDefault: () => void;
|
|
43
|
+
stopPropagation?: () => void;
|
|
44
|
+
}
|
|
45
|
+
export interface MenuViewModel {
|
|
46
|
+
readonly label: string;
|
|
47
|
+
readonly disabled: boolean;
|
|
48
|
+
readonly open: boolean;
|
|
49
|
+
readonly menuId: string;
|
|
50
|
+
readonly rootClassName: string;
|
|
51
|
+
readonly buttonClassName: string;
|
|
52
|
+
readonly menuClassName: string;
|
|
53
|
+
readonly menuStyle: CSSProperties;
|
|
54
|
+
readonly items: readonly MenuItemViewModel[];
|
|
55
|
+
readonly rootRef: RefObject<HTMLDivElement | null>;
|
|
56
|
+
readonly buttonRef: RefObject<HTMLButtonElement | null>;
|
|
57
|
+
readonly menuRef: RefObject<HTMLDivElement | null>;
|
|
58
|
+
readonly onButtonClick: () => void;
|
|
59
|
+
readonly onButtonKeyDown: (event: MenuKeyEvent) => void;
|
|
60
|
+
readonly onMenuKeyDown: (event: MenuKeyEvent) => void;
|
|
61
|
+
readonly onItemClick: (itemId: string) => void;
|
|
62
|
+
readonly onItemHover: (itemId: string) => void;
|
|
63
|
+
}
|
package/package.json
CHANGED
|
@@ -1,43 +1,43 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@net-advantage/jo-ui-menu",
|
|
3
|
-
"version": "1.3.
|
|
4
|
-
"private": false,
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "./dist/index.cjs",
|
|
7
|
-
"module": "./dist/index.js",
|
|
8
|
-
"types": "./dist/index.d.ts",
|
|
9
|
-
"exports": {
|
|
10
|
-
".": {
|
|
11
|
-
"types": "./dist/index.d.ts",
|
|
12
|
-
"import": "./dist/index.js",
|
|
13
|
-
"require": "./dist/index.cjs"
|
|
14
|
-
},
|
|
15
|
-
"./style.css": "./dist/jo-ui-menu.css"
|
|
16
|
-
},
|
|
17
|
-
"files": [
|
|
18
|
-
"dist",
|
|
19
|
-
"README.md"
|
|
20
|
-
],
|
|
21
|
-
"sideEffects": [
|
|
22
|
-
"**/*.css"
|
|
23
|
-
],
|
|
24
|
-
"peerDependencies": {
|
|
25
|
-
"react": "^19.0.0",
|
|
26
|
-
"react-dom": "^19.0.0"
|
|
27
|
-
},
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
}
|
|
43
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@net-advantage/jo-ui-menu",
|
|
3
|
+
"version": "1.3.2",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
},
|
|
15
|
+
"./style.css": "./dist/jo-ui-menu.css"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"README.md"
|
|
20
|
+
],
|
|
21
|
+
"sideEffects": [
|
|
22
|
+
"**/*.css"
|
|
23
|
+
],
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"react": "^19.0.0",
|
|
26
|
+
"react-dom": "^19.0.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/react": "^19.2.18",
|
|
30
|
+
"@types/react-dom": "^19.2.7",
|
|
31
|
+
"typescript": "~6.0.2",
|
|
32
|
+
"vite": "^8.3.0",
|
|
33
|
+
"vitest": "5.0.2",
|
|
34
|
+
"@vitejs/plugin-react": "^6.1.1"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "pnpm run build:styles && pnpm run build:js && pnpm run build:types",
|
|
38
|
+
"build:styles": "pnpm exec tailwindcss -c ../../tailwind.controls.config.cjs -i ./src/jo-ui-menu.tailwind.css -o ./src/jo-ui-menu.css --minify",
|
|
39
|
+
"build:types": "tsc -p tsconfig.build.json",
|
|
40
|
+
"build:js": "vite build",
|
|
41
|
+
"test": "vitest run"
|
|
42
|
+
}
|
|
43
|
+
}
|