@data-slot/navigation-menu 0.1.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 ADDED
@@ -0,0 +1,223 @@
1
+ # @data-slot/navigation-menu
2
+
3
+ Headless navigation menu (mega menu) component for vanilla JavaScript. Accessible, unstyled, tiny.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @data-slot/navigation-menu
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```html
14
+ <nav data-slot="navigation-menu">
15
+ <ul data-slot="navigation-menu-list">
16
+ <li data-slot="navigation-menu-item" data-value="products">
17
+ <button data-slot="navigation-menu-trigger">Products</button>
18
+ <div data-slot="navigation-menu-content">
19
+ <a href="/product-a">Product A</a>
20
+ <a href="/product-b">Product B</a>
21
+ </div>
22
+ </li>
23
+ <li data-slot="navigation-menu-item" data-value="company">
24
+ <button data-slot="navigation-menu-trigger">Company</button>
25
+ <div data-slot="navigation-menu-content">
26
+ <a href="/about">About</a>
27
+ <a href="/careers">Careers</a>
28
+ </div>
29
+ </li>
30
+ <div data-slot="navigation-menu-indicator"></div>
31
+ </ul>
32
+ <div data-slot="navigation-menu-viewport"></div>
33
+ </nav>
34
+
35
+ <script type="module">
36
+ import { create } from "@data-slot/navigation-menu";
37
+
38
+ const controllers = create();
39
+ </script>
40
+ ```
41
+
42
+ ## API
43
+
44
+ ### `create(scope?)`
45
+
46
+ Auto-discover and bind all navigation menu instances in a scope (defaults to `document`).
47
+
48
+ ```typescript
49
+ import { create } from "@data-slot/navigation-menu";
50
+
51
+ const controllers = create(); // Returns NavigationMenuController[]
52
+ ```
53
+
54
+ ### `createNavigationMenu(root, options?)`
55
+
56
+ Create a controller for a specific element.
57
+
58
+ ```typescript
59
+ import { createNavigationMenu } from "@data-slot/navigation-menu";
60
+
61
+ const menu = createNavigationMenu(element, {
62
+ delayOpen: 200,
63
+ delayClose: 150,
64
+ onValueChange: (value) => console.log(value),
65
+ });
66
+ ```
67
+
68
+ ### Options
69
+
70
+ | Option | Type | Default | Description |
71
+ |--------|------|---------|-------------|
72
+ | `delayOpen` | `number` | `200` | Delay before opening on hover (ms) |
73
+ | `delayClose` | `number` | `150` | Delay before closing on mouse leave (ms) |
74
+ | `onValueChange` | `(value: string \| null) => void` | `undefined` | Callback when active item changes |
75
+
76
+ ### Controller
77
+
78
+ | Method/Property | Description |
79
+ |-----------------|-------------|
80
+ | `open(value)` | Open a specific item |
81
+ | `close()` | Close the menu |
82
+ | `value` | Currently active item value (readonly `string \| null`) |
83
+ | `destroy()` | Cleanup all event listeners |
84
+
85
+ ## Markup Structure
86
+
87
+ ```html
88
+ <nav data-slot="navigation-menu">
89
+ <ul data-slot="navigation-menu-list">
90
+ <li data-slot="navigation-menu-item" data-value="unique-id">
91
+ <button data-slot="navigation-menu-trigger">Label</button>
92
+ <div data-slot="navigation-menu-content">
93
+ <!-- Links, content -->
94
+ </div>
95
+ </li>
96
+ <!-- Optional hover indicator -->
97
+ <div data-slot="navigation-menu-indicator"></div>
98
+ </ul>
99
+ <!-- Optional viewport for animated content switching -->
100
+ <div data-slot="navigation-menu-viewport"></div>
101
+ </nav>
102
+ ```
103
+
104
+ ### Optional Slots
105
+
106
+ - `navigation-menu-indicator` - Animated highlight that follows the hovered trigger
107
+ - `navigation-menu-viewport` - Container for content with size transitions
108
+
109
+ ## Styling
110
+
111
+ ### Basic Styling
112
+
113
+ ```css
114
+ /* Hidden by default */
115
+ [data-slot="navigation-menu-content"] {
116
+ display: none;
117
+ }
118
+
119
+ [data-slot="navigation-menu-content"][data-state="active"] {
120
+ display: block;
121
+ }
122
+
123
+ /* Viewport sizing */
124
+ [data-slot="navigation-menu-viewport"] {
125
+ width: var(--viewport-width);
126
+ height: var(--viewport-height);
127
+ transition: width 0.3s, height 0.3s;
128
+ }
129
+
130
+ /* Skip animation on initial open */
131
+ [data-slot="navigation-menu-viewport"][data-instant] {
132
+ transition: none;
133
+ }
134
+
135
+ /* Indicator positioning */
136
+ [data-slot="navigation-menu-indicator"] {
137
+ position: absolute;
138
+ left: var(--indicator-left);
139
+ width: var(--indicator-width);
140
+ transition: left 0.2s, width 0.2s;
141
+ }
142
+ ```
143
+
144
+ ### Motion Animations
145
+
146
+ Content panels receive `data-motion` attributes for enter/exit animations:
147
+
148
+ ```css
149
+ /* Entering from right */
150
+ [data-slot="navigation-menu-content"][data-motion="from-right"] {
151
+ animation: slideFromRight 0.2s;
152
+ }
153
+
154
+ /* Exiting to left */
155
+ [data-slot="navigation-menu-content"][data-motion="to-left"] {
156
+ animation: slideToLeft 0.2s;
157
+ }
158
+
159
+ @keyframes slideFromRight {
160
+ from { transform: translateX(100%); opacity: 0; }
161
+ to { transform: translateX(0); opacity: 1; }
162
+ }
163
+
164
+ @keyframes slideToLeft {
165
+ from { transform: translateX(0); opacity: 1; }
166
+ to { transform: translateX(-100%); opacity: 0; }
167
+ }
168
+ ```
169
+
170
+ ### CSS Variables
171
+
172
+ | Variable | Element | Description |
173
+ |----------|---------|-------------|
174
+ | `--viewport-width` | viewport | Width of active content |
175
+ | `--viewport-height` | viewport | Height of active content |
176
+ | `--indicator-left` | indicator | Left offset from list |
177
+ | `--indicator-width` | indicator | Width of hovered trigger |
178
+ | `--indicator-top` | indicator | Top offset from list |
179
+ | `--indicator-height` | indicator | Height of hovered trigger |
180
+ | `--motion-direction` | viewport | `1` (right) or `-1` (left) |
181
+
182
+ ## Keyboard Navigation
183
+
184
+ ### Within Trigger List
185
+
186
+ | Key | Action |
187
+ |-----|--------|
188
+ | `ArrowLeft` | Move focus to previous trigger |
189
+ | `ArrowRight` | Move focus to next trigger |
190
+ | `ArrowDown` | Move focus into content panel |
191
+ | `Home` | Move focus to first trigger |
192
+ | `End` | Move focus to last trigger |
193
+ | `Escape` | Close menu |
194
+
195
+ ### Within Content Panel
196
+
197
+ | Key | Action |
198
+ |-----|--------|
199
+ | `ArrowDown` / `ArrowRight` | Move to next focusable element |
200
+ | `ArrowUp` / `ArrowLeft` | Move to previous element (returns to trigger at start) |
201
+ | `Escape` | Close menu and return focus to trigger |
202
+
203
+ ## Behavior
204
+
205
+ - **Hover**: Opens after `delayOpen` ms, closes after `delayClose` ms
206
+ - **Click**: Locks menu open until clicking outside or same trigger
207
+ - **Focus**: Opens immediately on keyboard focus
208
+ - **Switching**: Instant transition between items (no delay)
209
+
210
+ ## Events
211
+
212
+ Listen for changes via custom events:
213
+
214
+ ```javascript
215
+ element.addEventListener("navigation-menu:change", (e) => {
216
+ console.log("Active item:", e.detail.value);
217
+ });
218
+ ```
219
+
220
+ ## License
221
+
222
+ MIT
223
+
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));const c=s(require(`@data-slot/core`));function l(e,t={}){let{delayOpen:n=200,delayClose:r=150,openOnFocus:i=!0,onValueChange:a}=t,o=e=>e.replace(/[^a-z0-9\-_:.]/gi,`-`),s=(e,t)=>{`inert`in e&&(e.inert=t)},l=(0,c.getPart)(e,`navigation-menu-list`),u=(0,c.getParts)(e,`navigation-menu-item`),d=(0,c.getPart)(e,`navigation-menu-viewport`),f=(0,c.getPart)(e,`navigation-menu-indicator`);if(!l||u.length===0)throw Error(`NavigationMenu requires navigation-menu-list and at least one navigation-menu-item`);let p=null,m=null,h=-1,g=null,_=null,v=null,y=!1,b=!1,x=!1,S=[],C=null,w=e=>{C?.disconnect(),C=null,!(!d||!e)&&(C=new ResizeObserver(()=>P(e)),C.observe(e))};S.push(()=>C?.disconnect());let T=new Map,E=0;u.forEach(e=>{let t=e.dataset.value;if(!t)return;let n=(0,c.getPart)(e,`navigation-menu-trigger`),r=(0,c.getPart)(e,`navigation-menu-content`);if(n&&r){T.set(t,{item:e,trigger:n,content:r,index:E++});let i=o(t),a=(0,c.ensureId)(n,`nav-menu-trigger-${i}`),s=(0,c.ensureId)(r,`nav-menu-content-${i}`);n.setAttribute(`aria-haspopup`,`true`),n.setAttribute(`aria-controls`,s),r.setAttribute(`role`,`region`),r.setAttribute(`aria-labelledby`,a)}});let D=Array.from(T.values()).map(e=>e.trigger),O=new Map;for(let[e,t]of T)O.set(t.trigger,e);let k=`a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])`,A=e=>Array.from(e.querySelectorAll(k)).filter(e=>!e.hidden&&!e.closest(`[hidden]`)),j=()=>{g&&(clearTimeout(g),g=null),_&&(clearTimeout(_),_=null)},M=null,N=()=>(M||(M=document.createElement(`div`),M.setAttribute(`data-slot`,`navigation-menu-bridge`),M.style.cssText=`position: absolute; left: 0; right: 0; top: 0; pointer-events: auto; z-index: -1;`,d&&d.insertBefore(M,d.firstChild),S.push((0,c.on)(M,`pointerenter`,()=>{j()}))),M),P=e=>{d&&requestAnimationFrame(()=>{let t=e.getBoundingClientRect(),n=getComputedStyle(e),r=parseFloat(n.marginTop)||0,i=getComputedStyle(d),a=parseFloat(i.marginTop)||0,o=parseFloat(i.marginBottom)||0;d.style.setProperty(`--viewport-width`,`${t.width}px`),d.style.setProperty(`--viewport-height`,`${t.height+a+o}px`);let s=r+a;if(s>0){let e=N();e.style.height=`${s}px`,e.style.transform=`translateY(-${s}px)`}else M&&(M.style.height=`0`)})},F=e=>h===-1||e>h?`right`:`left`,I=e=>{if(!f)return;if(v=e,!e){f.setAttribute(`data-state`,`hidden`);return}let t=l.getBoundingClientRect(),n=e.getBoundingClientRect();f.style.setProperty(`--indicator-left`,`${n.left-t.left}px`),f.style.setProperty(`--indicator-width`,`${n.width}px`),f.style.setProperty(`--indicator-top`,`${n.top-t.top}px`),f.style.setProperty(`--indicator-height`,`${n.height}px`),f.setAttribute(`data-state`,`visible`)},L=(t,i=!1)=>{if(t===p){j();return}if(t!==null&&t===m){j();return}j(),m=t===null?null:t;let o=()=>{let n=p,r=t?T.get(t):null,i=n!==null&&t!==null&&n!==t,o=i&&r?F(r.index):null,l=document.activeElement;if(t===null&&l&&e.contains(l)){let e=n?T.get(n)?.trigger:null;e&&e.focus()}if(T.forEach(({trigger:e,content:r,item:i},a)=>{let l=a===t,u=a===n;if((0,c.setAria)(e,`expanded`,l),e.setAttribute(`data-state`,l?`open`:`closed`),l||u&&t===null?e.tabIndex=0:e.tabIndex=-1,i.setAttribute(`data-state`,l?`open`:`closed`),!l)if(r.setAttribute(`data-state`,`inactive`),r.setAttribute(`aria-hidden`,`true`),s(r,!0),r.hidden=!0,u&&o){let e=o===`right`?`to-left`:`to-right`;r.setAttribute(`data-motion`,e)}else r.removeAttribute(`data-motion`)}),r){if(o){let e=o===`right`?`from-right`:`from-left`;r.content.setAttribute(`data-motion`,e)}else r.content.removeAttribute(`data-motion`);r.content.setAttribute(`data-state`,`active`),r.content.removeAttribute(`aria-hidden`),s(r.content,!1),r.content.hidden=!1,h=r.index,P(r.content),w(r.content),I(r.trigger)}else w(null);let u=t!==null;e.setAttribute(`data-state`,u?`open`:`closed`),o?e.setAttribute(`data-motion`,o===`right`?`from-right`:`from-left`):e.removeAttribute(`data-motion`),d&&(d.setAttribute(`data-state`,u?`open`:`closed`),u&&!i?d.setAttribute(`data-instant`,``):i&&d.removeAttribute(`data-instant`),o&&d.style.setProperty(`--motion-direction`,o===`right`?`1`:`-1`)),p=t,m=null,t===null&&I(null),(0,c.emit)(e,`navigation-menu:change`,{value:t}),a?.(t)};i?o():t!==null&&p===null?g=setTimeout(o,n):t!==null&&p!==null?o():_=setTimeout(o,r)};e.setAttribute(`data-state`,`closed`),d&&d.setAttribute(`data-state`,`closed`),f&&f.setAttribute(`data-state`,`hidden`),T.forEach(({trigger:e,content:t,item:n})=>{e.tagName===`BUTTON`&&!e.hasAttribute(`type`)&&(e.type=`button`),(0,c.setAria)(e,`expanded`,!1),e.setAttribute(`data-state`,`closed`),e.tabIndex=e===D[0]?0:-1,n.setAttribute(`data-state`,`closed`),t.setAttribute(`data-state`,`inactive`),t.setAttribute(`aria-hidden`,`true`),t.tabIndex=-1,s(t,!0),t.hidden=!0}),T.forEach(({item:e,trigger:t},n)=>{S.push((0,c.on)(t,`pointerenter`,()=>{y||I(t)})),S.push((0,c.on)(e,`pointerenter`,()=>{y||L(n)})),S.push((0,c.on)(e,`pointerleave`,()=>{m===n&&p===null&&(j(),m=null)})),S.push((0,c.on)(t,`focus`,()=>{b||(i&&L(n,!0),I(t))})),S.push((0,c.on)(t,`pointerdown`,()=>{b=!0})),S.push((0,c.on)(t,`click`,()=>{j(),p===n&&y?(y=!1,L(null,!0),I(null)):p===n&&!y?(y=!0,I(t)):(y=!0,L(n,!0),I(t)),b=!1}))}),S.push((0,c.on)(e,`pointerenter`,()=>{x=!0}),(0,c.on)(e,`pointerleave`,()=>{x=!1,y||(L(null),I(null))}),(0,c.on)(e,`pointerdown`,j)),d&&S.push((0,c.on)(d,`pointerenter`,()=>{j()}),(0,c.on)(d,`transitionend`,e=>{if(e.target!==d)return;let t=p?T.get(p):null;t&&P(t.content)})),T.forEach(({content:e})=>{S.push((0,c.on)(e,`pointerenter`,()=>{j()}))}),S.push((0,c.on)(l,`keydown`,e=>{let t=e.target,n=D.indexOf(t);if(n===-1)return;let r=O.get(t)??null,i=n;switch(e.key){case`ArrowLeft`:i=n-1,i<0&&(i=D.length-1);break;case`ArrowRight`:i=n+1,i>=D.length&&(i=0);break;case`ArrowDown`:e.preventDefault(),r&&(y=!0,L(r,!0),requestAnimationFrame(()=>{let e=T.get(r);if(!e)return;let t=A(e.content),n=t[0];n?n.focus():e.content.focus()}));return;case`Home`:i=0;break;case`End`:i=D.length-1;break;case`Escape`:y=!1,L(null,!0),I(null);return;default:return}e.preventDefault();let a=D[i];a&&(D.forEach(e=>e.tabIndex=e===a?0:-1),a.focus(),I(a))})),T.forEach(({content:e,trigger:t})=>{S.push((0,c.on)(e,`keydown`,n=>{let r=n.target,i=A(e),a=i.indexOf(r);if(a!==-1)switch(n.key){case`ArrowDown`:case`ArrowRight`:{n.preventDefault();let e=a+1;e<i.length&&i[e]?.focus();break}case`ArrowUp`:case`ArrowLeft`:n.preventDefault(),a===0?t.focus():i[a-1]?.focus();break;case`Escape`:n.preventDefault(),y=!1,L(null,!0),I(null),t.focus();break}}))});let R=()=>e.contains(document.activeElement)||x||y;S.push((0,c.on)(document,`pointerup`,()=>{b=!1},{capture:!0}),(0,c.on)(document,`pointercancel`,()=>{b=!1},{capture:!0})),S.push((0,c.on)(document,`focusin`,t=>{p!==null&&(e.contains(t.target)||(y=!1,L(null,!0),I(null)))})),S.push((0,c.on)(document,`pointerdown`,t=>{p!==null&&R()&&(e.contains(t.target)||(y=!1,L(null,!0),I(null)))})),S.push((0,c.on)(document,`keydown`,e=>{e.key!==`Escape`||p===null||R()&&(y=!1,L(null,!0),I(null))})),S.push((0,c.on)(window,`resize`,()=>{v&&requestAnimationFrame(()=>I(v))}),(0,c.on)(l,`scroll`,()=>{v&&requestAnimationFrame(()=>I(v))}));let z={get value(){return p},open:e=>L(e,!0),close:()=>L(null,!0),destroy:()=>{j(),S.forEach(e=>e()),S.length=0}};return z}const u=new WeakSet;function d(e=document){let t=[];for(let n of(0,c.getRoots)(e,`navigation-menu`)){if(u.has(n))continue;u.add(n),t.push(l(n))}return t}exports.create=d,exports.createNavigationMenu=l;
@@ -0,0 +1,47 @@
1
+ //#region src/index.d.ts
2
+ interface NavigationMenuOptions {
3
+ /** Delay before opening on hover (ms) */
4
+ delayOpen?: number;
5
+ /** Delay before closing on mouse leave (ms) */
6
+ delayClose?: number;
7
+ /** Whether focusing a trigger opens its content (default: true) */
8
+ openOnFocus?: boolean;
9
+ /** Callback when active item changes */
10
+ onValueChange?: (value: string | null) => void;
11
+ }
12
+ interface NavigationMenuController {
13
+ /** Currently active item value */
14
+ readonly value: string | null;
15
+ /** Open a specific item */
16
+ open(value: string): void;
17
+ /** Close the menu */
18
+ close(): void;
19
+ /** Cleanup all event listeners */
20
+ destroy(): void;
21
+ }
22
+ /**
23
+ * Create a navigation menu controller for a root element
24
+ *
25
+ * Expected markup:
26
+ * ```html
27
+ * <nav data-slot="navigation-menu">
28
+ * <ul data-slot="navigation-menu-list">
29
+ * <li data-slot="navigation-menu-item" data-value="products">
30
+ * <button data-slot="navigation-menu-trigger">Products</button>
31
+ * <div data-slot="navigation-menu-content">...</div>
32
+ * </li>
33
+ * <!-- Optional hover indicator -->
34
+ * <div data-slot="navigation-menu-indicator"></div>
35
+ * </ul>
36
+ * <div data-slot="navigation-menu-viewport"></div>
37
+ * </nav>
38
+ * ```
39
+ */
40
+ declare function createNavigationMenu(root: Element, options?: NavigationMenuOptions): NavigationMenuController;
41
+ /**
42
+ * Find and bind all navigation menu components in a scope
43
+ * Returns array of controllers for programmatic access
44
+ */
45
+ declare function create(scope?: ParentNode): NavigationMenuController[];
46
+ //#endregion
47
+ export { NavigationMenuController, NavigationMenuOptions, create, createNavigationMenu };
@@ -0,0 +1,47 @@
1
+ //#region src/index.d.ts
2
+ interface NavigationMenuOptions {
3
+ /** Delay before opening on hover (ms) */
4
+ delayOpen?: number;
5
+ /** Delay before closing on mouse leave (ms) */
6
+ delayClose?: number;
7
+ /** Whether focusing a trigger opens its content (default: true) */
8
+ openOnFocus?: boolean;
9
+ /** Callback when active item changes */
10
+ onValueChange?: (value: string | null) => void;
11
+ }
12
+ interface NavigationMenuController {
13
+ /** Currently active item value */
14
+ readonly value: string | null;
15
+ /** Open a specific item */
16
+ open(value: string): void;
17
+ /** Close the menu */
18
+ close(): void;
19
+ /** Cleanup all event listeners */
20
+ destroy(): void;
21
+ }
22
+ /**
23
+ * Create a navigation menu controller for a root element
24
+ *
25
+ * Expected markup:
26
+ * ```html
27
+ * <nav data-slot="navigation-menu">
28
+ * <ul data-slot="navigation-menu-list">
29
+ * <li data-slot="navigation-menu-item" data-value="products">
30
+ * <button data-slot="navigation-menu-trigger">Products</button>
31
+ * <div data-slot="navigation-menu-content">...</div>
32
+ * </li>
33
+ * <!-- Optional hover indicator -->
34
+ * <div data-slot="navigation-menu-indicator"></div>
35
+ * </ul>
36
+ * <div data-slot="navigation-menu-viewport"></div>
37
+ * </nav>
38
+ * ```
39
+ */
40
+ declare function createNavigationMenu(root: Element, options?: NavigationMenuOptions): NavigationMenuController;
41
+ /**
42
+ * Find and bind all navigation menu components in a scope
43
+ * Returns array of controllers for programmatic access
44
+ */
45
+ declare function create(scope?: ParentNode): NavigationMenuController[];
46
+ //#endregion
47
+ export { NavigationMenuController, NavigationMenuOptions, create, createNavigationMenu };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ import{emit as e,ensureId as t,getPart as n,getParts as r,getRoots as i,on as a,setAria as o}from"@data-slot/core";function s(i,s={}){let{delayOpen:c=200,delayClose:l=150,openOnFocus:u=!0,onValueChange:d}=s,f=e=>e.replace(/[^a-z0-9\-_:.]/gi,`-`),p=(e,t)=>{`inert`in e&&(e.inert=t)},m=n(i,`navigation-menu-list`),h=r(i,`navigation-menu-item`),g=n(i,`navigation-menu-viewport`),_=n(i,`navigation-menu-indicator`);if(!m||h.length===0)throw Error(`NavigationMenu requires navigation-menu-list and at least one navigation-menu-item`);let v=null,y=null,b=-1,x=null,S=null,C=null,w=!1,T=!1,E=!1,D=[],O=null,k=e=>{O?.disconnect(),O=null,!(!g||!e)&&(O=new ResizeObserver(()=>z(e)),O.observe(e))};D.push(()=>O?.disconnect());let A=new Map,j=0;h.forEach(e=>{let r=e.dataset.value;if(!r)return;let i=n(e,`navigation-menu-trigger`),a=n(e,`navigation-menu-content`);if(i&&a){A.set(r,{item:e,trigger:i,content:a,index:j++});let n=f(r),o=t(i,`nav-menu-trigger-${n}`),s=t(a,`nav-menu-content-${n}`);i.setAttribute(`aria-haspopup`,`true`),i.setAttribute(`aria-controls`,s),a.setAttribute(`role`,`region`),a.setAttribute(`aria-labelledby`,o)}});let M=Array.from(A.values()).map(e=>e.trigger),N=new Map;for(let[e,t]of A)N.set(t.trigger,e);let P=`a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])`,F=e=>Array.from(e.querySelectorAll(P)).filter(e=>!e.hidden&&!e.closest(`[hidden]`)),I=()=>{x&&(clearTimeout(x),x=null),S&&(clearTimeout(S),S=null)},L=null,R=()=>(L||(L=document.createElement(`div`),L.setAttribute(`data-slot`,`navigation-menu-bridge`),L.style.cssText=`position: absolute; left: 0; right: 0; top: 0; pointer-events: auto; z-index: -1;`,g&&g.insertBefore(L,g.firstChild),D.push(a(L,`pointerenter`,()=>{I()}))),L),z=e=>{g&&requestAnimationFrame(()=>{let t=e.getBoundingClientRect(),n=getComputedStyle(e),r=parseFloat(n.marginTop)||0,i=getComputedStyle(g),a=parseFloat(i.marginTop)||0,o=parseFloat(i.marginBottom)||0;g.style.setProperty(`--viewport-width`,`${t.width}px`),g.style.setProperty(`--viewport-height`,`${t.height+a+o}px`);let s=r+a;if(s>0){let e=R();e.style.height=`${s}px`,e.style.transform=`translateY(-${s}px)`}else L&&(L.style.height=`0`)})},B=e=>b===-1||e>b?`right`:`left`,V=e=>{if(!_)return;if(C=e,!e){_.setAttribute(`data-state`,`hidden`);return}let t=m.getBoundingClientRect(),n=e.getBoundingClientRect();_.style.setProperty(`--indicator-left`,`${n.left-t.left}px`),_.style.setProperty(`--indicator-width`,`${n.width}px`),_.style.setProperty(`--indicator-top`,`${n.top-t.top}px`),_.style.setProperty(`--indicator-height`,`${n.height}px`),_.setAttribute(`data-state`,`visible`)},H=(t,n=!1)=>{if(t===v){I();return}if(t!==null&&t===y){I();return}I(),y=t===null?null:t;let r=()=>{let n=v,r=t?A.get(t):null,a=n!==null&&t!==null&&n!==t,s=a&&r?B(r.index):null,c=document.activeElement;if(t===null&&c&&i.contains(c)){let e=n?A.get(n)?.trigger:null;e&&e.focus()}if(A.forEach(({trigger:e,content:r,item:i},a)=>{let c=a===t,l=a===n;if(o(e,`expanded`,c),e.setAttribute(`data-state`,c?`open`:`closed`),c||l&&t===null?e.tabIndex=0:e.tabIndex=-1,i.setAttribute(`data-state`,c?`open`:`closed`),!c)if(r.setAttribute(`data-state`,`inactive`),r.setAttribute(`aria-hidden`,`true`),p(r,!0),r.hidden=!0,l&&s){let e=s===`right`?`to-left`:`to-right`;r.setAttribute(`data-motion`,e)}else r.removeAttribute(`data-motion`)}),r){if(s){let e=s===`right`?`from-right`:`from-left`;r.content.setAttribute(`data-motion`,e)}else r.content.removeAttribute(`data-motion`);r.content.setAttribute(`data-state`,`active`),r.content.removeAttribute(`aria-hidden`),p(r.content,!1),r.content.hidden=!1,b=r.index,z(r.content),k(r.content),V(r.trigger)}else k(null);let l=t!==null;i.setAttribute(`data-state`,l?`open`:`closed`),s?i.setAttribute(`data-motion`,s===`right`?`from-right`:`from-left`):i.removeAttribute(`data-motion`),g&&(g.setAttribute(`data-state`,l?`open`:`closed`),l&&!a?g.setAttribute(`data-instant`,``):a&&g.removeAttribute(`data-instant`),s&&g.style.setProperty(`--motion-direction`,s===`right`?`1`:`-1`)),v=t,y=null,t===null&&V(null),e(i,`navigation-menu:change`,{value:t}),d?.(t)};n?r():t!==null&&v===null?x=setTimeout(r,c):t!==null&&v!==null?r():S=setTimeout(r,l)};i.setAttribute(`data-state`,`closed`),g&&g.setAttribute(`data-state`,`closed`),_&&_.setAttribute(`data-state`,`hidden`),A.forEach(({trigger:e,content:t,item:n})=>{e.tagName===`BUTTON`&&!e.hasAttribute(`type`)&&(e.type=`button`),o(e,`expanded`,!1),e.setAttribute(`data-state`,`closed`),e.tabIndex=e===M[0]?0:-1,n.setAttribute(`data-state`,`closed`),t.setAttribute(`data-state`,`inactive`),t.setAttribute(`aria-hidden`,`true`),t.tabIndex=-1,p(t,!0),t.hidden=!0}),A.forEach(({item:e,trigger:t},n)=>{D.push(a(t,`pointerenter`,()=>{w||V(t)})),D.push(a(e,`pointerenter`,()=>{w||H(n)})),D.push(a(e,`pointerleave`,()=>{y===n&&v===null&&(I(),y=null)})),D.push(a(t,`focus`,()=>{T||(u&&H(n,!0),V(t))})),D.push(a(t,`pointerdown`,()=>{T=!0})),D.push(a(t,`click`,()=>{I(),v===n&&w?(w=!1,H(null,!0),V(null)):v===n&&!w?(w=!0,V(t)):(w=!0,H(n,!0),V(t)),T=!1}))}),D.push(a(i,`pointerenter`,()=>{E=!0}),a(i,`pointerleave`,()=>{E=!1,w||(H(null),V(null))}),a(i,`pointerdown`,I)),g&&D.push(a(g,`pointerenter`,()=>{I()}),a(g,`transitionend`,e=>{if(e.target!==g)return;let t=v?A.get(v):null;t&&z(t.content)})),A.forEach(({content:e})=>{D.push(a(e,`pointerenter`,()=>{I()}))}),D.push(a(m,`keydown`,e=>{let t=e.target,n=M.indexOf(t);if(n===-1)return;let r=N.get(t)??null,i=n;switch(e.key){case`ArrowLeft`:i=n-1,i<0&&(i=M.length-1);break;case`ArrowRight`:i=n+1,i>=M.length&&(i=0);break;case`ArrowDown`:e.preventDefault(),r&&(w=!0,H(r,!0),requestAnimationFrame(()=>{let e=A.get(r);if(!e)return;let t=F(e.content),n=t[0];n?n.focus():e.content.focus()}));return;case`Home`:i=0;break;case`End`:i=M.length-1;break;case`Escape`:w=!1,H(null,!0),V(null);return;default:return}e.preventDefault();let a=M[i];a&&(M.forEach(e=>e.tabIndex=e===a?0:-1),a.focus(),V(a))})),A.forEach(({content:e,trigger:t})=>{D.push(a(e,`keydown`,n=>{let r=n.target,i=F(e),a=i.indexOf(r);if(a!==-1)switch(n.key){case`ArrowDown`:case`ArrowRight`:{n.preventDefault();let e=a+1;e<i.length&&i[e]?.focus();break}case`ArrowUp`:case`ArrowLeft`:n.preventDefault(),a===0?t.focus():i[a-1]?.focus();break;case`Escape`:n.preventDefault(),w=!1,H(null,!0),V(null),t.focus();break}}))});let U=()=>i.contains(document.activeElement)||E||w;D.push(a(document,`pointerup`,()=>{T=!1},{capture:!0}),a(document,`pointercancel`,()=>{T=!1},{capture:!0})),D.push(a(document,`focusin`,e=>{v!==null&&(i.contains(e.target)||(w=!1,H(null,!0),V(null)))})),D.push(a(document,`pointerdown`,e=>{v!==null&&U()&&(i.contains(e.target)||(w=!1,H(null,!0),V(null)))})),D.push(a(document,`keydown`,e=>{e.key!==`Escape`||v===null||U()&&(w=!1,H(null,!0),V(null))})),D.push(a(window,`resize`,()=>{C&&requestAnimationFrame(()=>V(C))}),a(m,`scroll`,()=>{C&&requestAnimationFrame(()=>V(C))}));let W={get value(){return v},open:e=>H(e,!0),close:()=>H(null,!0),destroy:()=>{I(),D.forEach(e=>e()),D.length=0}};return W}const c=new WeakSet;function l(e=document){let t=[];for(let n of i(e,`navigation-menu`)){if(c.has(n))continue;c.add(n),t.push(s(n))}return t}export{l as create,s as createNavigationMenu};
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@data-slot/navigation-menu",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "sideEffects": false,
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ }
20
+ },
21
+ "files": ["dist"],
22
+ "scripts": {
23
+ "build": "tsdown"
24
+ },
25
+ "dependencies": {
26
+ "@data-slot/core": "workspace:*"
27
+ },
28
+ "keywords": ["headless", "ui", "navigation-menu", "mega-menu", "vanilla", "data-slot"],
29
+ "license": "MIT"
30
+ }