@data-slot/dropdown-menu 0.2.163 → 0.2.165

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @data-slot/dropdown-menu
2
2
 
3
- Headless dropdown menu component with full keyboard navigation and ARIA support.
3
+ Headless dropdown menu for vanilla JavaScript. Supports action items, single-select radio items, and multi-select checkbox items with full keyboard navigation and ARIA support.
4
4
 
5
5
  ## Installation
6
6
 
@@ -10,22 +10,47 @@ npm install @data-slot/dropdown-menu
10
10
 
11
11
  ## Usage
12
12
 
13
- ### HTML Structure
13
+ ### Action Menu
14
14
 
15
15
  ```html
16
16
  <div data-slot="dropdown-menu">
17
- <button data-slot="dropdown-menu-trigger">Options</button>
17
+ <button data-slot="dropdown-menu-trigger">Actions</button>
18
18
  <div data-slot="dropdown-menu-content">
19
- <div data-slot="dropdown-menu-group">
20
- <div data-slot="dropdown-menu-label">Actions</div>
21
- <button data-slot="dropdown-menu-item">
22
- Edit
23
- <span data-slot="dropdown-menu-shortcut">Ctrl+E</span>
24
- </button>
25
- <button data-slot="dropdown-menu-item" data-variant="destructive">Delete</button>
26
- </div>
27
- <div data-slot="dropdown-menu-separator"></div>
28
- <button data-slot="dropdown-menu-item" data-disabled>Disabled</button>
19
+ <button data-slot="dropdown-menu-item" data-value="edit">Edit</button>
20
+ <button data-slot="dropdown-menu-item" data-value="copy">Copy</button>
21
+ <button data-slot="dropdown-menu-item" data-variant="destructive" data-value="delete">
22
+ Delete
23
+ </button>
24
+ </div>
25
+ </div>
26
+ ```
27
+
28
+ ### Single-Select Menu
29
+
30
+ ```html
31
+ <div data-slot="dropdown-menu" data-default-value="pro">
32
+ <button data-slot="dropdown-menu-trigger">Plan</button>
33
+ <div data-slot="dropdown-menu-content">
34
+ <button data-slot="dropdown-menu-radio-item" data-value="starter">Starter</button>
35
+ <button data-slot="dropdown-menu-radio-item" data-value="pro">Pro</button>
36
+ <button data-slot="dropdown-menu-radio-item" data-value="team">Team</button>
37
+ </div>
38
+ </div>
39
+ ```
40
+
41
+ ### Multi-Select Menu
42
+
43
+ ```html
44
+ <div
45
+ data-slot="dropdown-menu"
46
+ data-close-on-select="false"
47
+ data-default-values='["email","push"]'
48
+ >
49
+ <button data-slot="dropdown-menu-trigger">Channels</button>
50
+ <div data-slot="dropdown-menu-content">
51
+ <button data-slot="dropdown-menu-checkbox-item" data-value="email">Email</button>
52
+ <button data-slot="dropdown-menu-checkbox-item" data-value="sms">SMS</button>
53
+ <button data-slot="dropdown-menu-checkbox-item" data-value="push">Push</button>
29
54
  </div>
30
55
  </div>
31
56
  ```
@@ -35,24 +60,49 @@ npm install @data-slot/dropdown-menu
35
60
  ```js
36
61
  import { create, createDropdownMenu } from "@data-slot/dropdown-menu";
37
62
 
38
- // Auto-bind all dropdown menus in the document
39
63
  const controllers = create();
40
64
 
41
- // Or bind a specific element
42
65
  const root = document.querySelector('[data-slot="dropdown-menu"]');
43
66
  const controller = createDropdownMenu(root, {
44
- onOpenChange: (open) => console.log("Menu open:", open),
45
- onSelect: (value) => console.log("Selected:", value),
67
+ onOpenChange: (open) => console.log("open:", open),
68
+ onSelect: (value) => console.log("activation:", value),
69
+ onValueChange: (value) => console.log("radio value:", value),
70
+ onValuesChange: (values) => console.log("checkbox values:", values),
46
71
  });
47
72
 
48
- // Programmatic control
49
- controller.open();
50
- controller.close();
51
- controller.toggle();
73
+ controller.set({ value: "pro" });
74
+ controller.set({ values: ["email", "push"] });
75
+ controller.set({ open: true, highlightedValue: "push" });
52
76
  controller.destroy();
53
77
  ```
54
78
 
55
- `createDropdownMenu(root)` is idempotent per root. Calling it again for the same element returns the existing controller; destroy it first if you need to rebind with different options.
79
+ `createDropdownMenu(root)` is idempotent per root. Calling it again for the same element returns the existing controller.
80
+
81
+ ## Controller
82
+
83
+ ```ts
84
+ interface DropdownMenuController {
85
+ open(): void;
86
+ close(): void;
87
+ toggle(): void;
88
+ set(detail: DropdownMenuSetDetail): void;
89
+ readonly isOpen: boolean;
90
+ readonly value: string | null;
91
+ readonly values: string[];
92
+ readonly highlightedValue: string | null;
93
+ destroy(): void;
94
+ }
95
+ ```
96
+
97
+ `set()` applies fields in this order: `value`, `values`, `open`, `highlightedValue`.
98
+
99
+ - `set({ value })` commits radio selection.
100
+ - `set({ values })` commits checkbox selection.
101
+ - `set({ open })` opens or closes the menu.
102
+ - `set({ highlightedValue })` updates highlight only while the menu is open.
103
+ - Programmatic `set()` never emits `dropdown-menu:select`.
104
+ - No-op updates are silent.
105
+ - Unknown `value` / `values` targets are ignored, not thrown.
56
106
 
57
107
  ## Slots
58
108
 
@@ -60,16 +110,18 @@ controller.destroy();
60
110
  |------|-------------|
61
111
  | `dropdown-menu` | Root container |
62
112
  | `dropdown-menu-trigger` | Button that opens the menu |
63
- | `dropdown-menu-content` | The menu panel |
113
+ | `dropdown-menu-content` | Menu panel |
114
+ | `dropdown-menu-item` | Action item with no owned selection state |
115
+ | `dropdown-menu-radio-item` | Single-select menu item |
116
+ | `dropdown-menu-checkbox-item` | Multi-select menu item |
64
117
  | `dropdown-menu-group` | Groups related items |
65
- | `dropdown-menu-label` | Non-interactive label for groups |
66
- | `dropdown-menu-item` | Clickable menu item |
118
+ | `dropdown-menu-label` | Non-interactive label |
67
119
  | `dropdown-menu-separator` | Visual divider |
68
120
  | `dropdown-menu-shortcut` | Keyboard shortcut hint |
69
- | `dropdown-menu-positioner` | Optional authored positioning wrapper (reused instead of generated wrapper) |
70
- | `dropdown-menu-portal` | Optional authored portal wrapper that can contain `dropdown-menu-positioner` |
121
+ | `dropdown-menu-positioner` | Optional authored positioning wrapper |
122
+ | `dropdown-menu-portal` | Optional authored portal wrapper that contains `dropdown-menu-positioner` |
71
123
 
72
- ### Composed Portal Markup (Optional)
124
+ ### Composed Portal Markup
73
125
 
74
126
  ```html
75
127
  <div data-slot="dropdown-menu">
@@ -82,166 +134,223 @@ controller.destroy();
82
134
  </div>
83
135
  ```
84
136
 
85
- ## Data Attributes
137
+ ## State and Data Attributes
138
+
139
+ ### Root and Content
86
140
 
87
- | Attribute | Values | Description |
141
+ | Attribute | Target | Description |
88
142
  |-----------|--------|-------------|
89
- | `data-state` | `open`, `closed` | Current menu state (on root and content) |
90
- | `data-side` | `top`, `right`, `bottom`, `left` | Computed side after collision avoidance (may flip) |
91
- | `data-align` | `start`, `center`, `end` | Requested alignment (position may shift to fit viewport) |
92
- | `data-variant` | `default`, `destructive` | Item variant for styling |
93
- | `data-inset` | - | Adds left padding for alignment |
94
- | `data-disabled` | - | Disables the item |
95
- | `data-highlighted` | - | Focused/highlighted item |
96
- | `data-value` | string | Optional value for item selection |
143
+ | `data-state="open|closed"` | root, content | Current open state |
144
+ | `data-open` / `data-closed` | root, content | Presence aliases for state styling |
145
+ | `data-value="..."` | root | Current committed radio value only |
146
+ | `data-side` | content, positioner | Computed side after collision handling |
147
+ | `data-align` | content, positioner | Computed alignment after collision handling |
148
+
149
+ ### Items
150
+
151
+ | Attribute | Target | Description |
152
+ |-----------|--------|-------------|
153
+ | `data-highlighted` | item | Current highlighted item |
154
+ | `data-checked` | radio, checkbox items | Current committed checked state |
155
+ | `data-disabled` | item | Disabled item |
156
+ | `data-variant` | item | Styling hook such as `destructive` |
157
+ | `data-inset` | item | Styling hook for left padding |
158
+
159
+ ### Defaults and Options
160
+
161
+ | Attribute | Target | Description |
162
+ |-----------|--------|-------------|
163
+ | `data-default-open` | root | Initial open state |
164
+ | `data-default-value` | root | Initial radio value |
165
+ | `data-default-values='["a","b"]'` | root | Initial checkbox values as a JSON array string |
166
+ | `data-default-checked` | radio, checkbox item | Item-level default checked state |
167
+ | `data-close-on-click-outside` | root | Close on outside interaction |
168
+ | `data-close-on-escape` | root | Close on Escape |
169
+ | `data-close-on-select` | root | Close after accepted activation |
170
+ | `data-highlight-item-on-hover` | root | Highlight and focus items on hover |
171
+
172
+ Default precedence is:
173
+
174
+ 1. JavaScript options
175
+ 2. Root data attributes
176
+ 3. Item `data-default-checked`
177
+ 4. Empty state
178
+
179
+ For radio items, root defaults win over item defaults. For checkbox items, root `data-default-values` wins over item `data-default-checked`.
97
180
 
98
181
  ## Keyboard Navigation
99
182
 
100
183
  | Key | Action |
101
184
  |-----|--------|
102
- | `Enter` / `Space` | Open menu (on trigger) or activate item |
103
- | `ArrowDown` | Open menu (on trigger) or move to next item |
104
- | `ArrowUp` | Move to previous item |
105
- | `Home` | Move to first item |
106
- | `End` | Move to last item |
185
+ | `Enter` / `Space` | Open menu from trigger, or activate highlighted item |
186
+ | `ArrowDown` | Open menu from trigger, or move to next enabled item |
187
+ | `ArrowUp` | Move to previous enabled item |
188
+ | `Home` | Move to first enabled item |
189
+ | `End` | Move to last enabled item |
107
190
  | `Escape` | Close menu |
108
- | `A-Z` | Jump to item starting with letter (typeahead) |
191
+ | `Tab` | Close menu and continue tab order |
192
+ | `A-Z` | Typeahead by item text |
109
193
 
110
194
  ## Events
111
195
 
112
196
  ### Outbound Events
113
197
 
114
- | Event | Detail | Description |
115
- |-------|--------|-------------|
116
- | `dropdown-menu:change` | `{ open: boolean }` | Fired when menu opens or closes |
117
- | `dropdown-menu:select` | `{ value: string }` | Fired when an item is selected |
198
+ | Event | Detail | Notes |
199
+ |-------|--------|-------|
200
+ | `dropdown-menu:open-change` | `{ open, previousOpen, source, reason }` | Fires on real open-state changes |
201
+ | `dropdown-menu:change` | same detail | Deprecated alias for `open-change`; only `detail.open` is compatibility-guaranteed |
202
+ | `dropdown-menu:highlight-change` | `{ value, previousValue, item, previousItem, source }` | `value` and `item` become `null` when highlight clears |
203
+ | `dropdown-menu:select` | `{ value, item, itemType, source, checked? }` | Cancelable, user-only, fires before commit |
204
+ | `dropdown-menu:value-change` | `{ value, previousValue, item, previousItem, source }` | Radio commits only |
205
+ | `dropdown-menu:values-change` | `{ values, previousValues, changedValue, checked, item, source }` | Checkbox commits only |
206
+
207
+ `dropdown-menu:select` behavior:
208
+
209
+ - Fires only for user activation attempts. Disabled items and programmatic updates do not emit it.
210
+ - If `event.preventDefault()` is called, no selection state changes and no auto-close from selection occur.
211
+ - Programmatic updates never emit it.
212
+
213
+ `dropdown-menu:value-change`, `dropdown-menu:values-change`, `onValueChange`, and `onValuesChange` are silent on:
118
214
 
119
- ### Inbound Events
215
+ - initialization
216
+ - no-op commits
217
+ - ignored unknown targets
218
+
219
+ For `dropdown-menu:values-change`, `changedValue`, `checked`, and `item` are `null` when one programmatic update changes more than one checkbox at once.
220
+
221
+ ### Inbound Event
120
222
 
121
223
  | Event | Detail | Description |
122
224
  |-------|--------|-------------|
123
- | `dropdown-menu:set` | `{ open: boolean }` | Set open state programmatically |
225
+ | `dropdown-menu:set` | `DropdownMenuSetDetail` | Partial programmatic state update |
124
226
 
125
- ```javascript
126
- // Open the menu
227
+ ```js
127
228
  root.dispatchEvent(
128
- new CustomEvent("dropdown-menu:set", { detail: { open: true } })
229
+ new CustomEvent("dropdown-menu:set", {
230
+ detail: { value: "pro", source: "restore" },
231
+ })
129
232
  );
130
233
 
131
- // Close the menu
132
234
  root.dispatchEvent(
133
- new CustomEvent("dropdown-menu:set", { detail: { open: false } })
235
+ new CustomEvent("dropdown-menu:set", {
236
+ detail: { values: ["email", "push"], open: true },
237
+ })
134
238
  );
135
239
  ```
136
240
 
137
- #### Deprecated Shapes
241
+ ### Event Order
138
242
 
139
- The following shape is deprecated and will be removed in v1.0:
243
+ User radio selection:
140
244
 
141
- ```javascript
142
- // Deprecated: { value: boolean }
143
- root.dispatchEvent(
144
- new CustomEvent("dropdown-menu:set", { detail: { value: true } })
145
- );
146
- ```
245
+ 1. `dropdown-menu:select`
246
+ 2. `dropdown-menu:value-change`
247
+ 3. `dropdown-menu:open-change` if the menu closes
248
+
249
+ User checkbox selection:
250
+
251
+ 1. `dropdown-menu:select`
252
+ 2. `dropdown-menu:values-change`
253
+ 3. `dropdown-menu:open-change` if the menu closes
254
+
255
+ Programmatic selection:
147
256
 
148
- Use `{ open: boolean }` instead.
257
+ 1. `dropdown-menu:set` or `controller.set(...)`
258
+ 2. `dropdown-menu:value-change` or `dropdown-menu:values-change`
259
+ 3. No `dropdown-menu:select`
260
+
261
+ If closing the menu also clears an existing highlight, `dropdown-menu:highlight-change` is emitted before the close-side `dropdown-menu:open-change`.
149
262
 
150
263
  ## Options
151
264
 
152
265
  ```ts
153
266
  interface DropdownMenuOptions {
154
- /** Initial open state */
155
267
  defaultOpen?: boolean;
156
- /** Callback when open state changes */
268
+ defaultValue?: string | null;
269
+ defaultValues?: string[];
157
270
  onOpenChange?: (open: boolean) => void;
158
- /** Callback when an item is selected */
159
271
  onSelect?: (value: string) => void;
160
- /** Close when clicking outside (default: true) */
272
+ onValueChange?: (value: string | null) => void;
273
+ onValuesChange?: (values: string[]) => void;
161
274
  closeOnClickOutside?: boolean;
162
- /** Close when pressing Escape (default: true) */
163
275
  closeOnEscape?: boolean;
164
- /** Close when an item is selected (default: true) */
165
276
  closeOnSelect?: boolean;
166
- /** Highlight and focus items on pointer hover (default: true) */
167
277
  highlightItemOnHover?: boolean;
168
-
169
- // Positioning options (Radix-compatible)
170
- /** Preferred side of trigger: "top" | "right" | "bottom" | "left" (default: "bottom") */
171
278
  side?: "top" | "right" | "bottom" | "left";
172
- /** Alignment against trigger: "start" | "center" | "end" (default: "start") */
173
279
  align?: "start" | "center" | "end";
174
- /** Distance from trigger in px (default: 4) */
175
280
  sideOffset?: number;
176
- /** Offset from alignment edge in px (default: 0) */
177
281
  alignOffset?: number;
178
- /** Flip/shift to stay in viewport (default: true) */
179
282
  avoidCollisions?: boolean;
180
- /** Viewport edge padding in px (default: 8) */
181
283
  collisionPadding?: number;
284
+ lockScroll?: boolean;
182
285
  }
183
286
  ```
184
287
 
185
- ### Data Attribute Options
288
+ Notes:
186
289
 
187
- Options can also be set via data attributes. JS options take precedence.
290
+ - `closeOnSelect` defaults to `true`. Multi-select menus usually want `false`.
291
+ - `onSelect` tracks accepted user activation. It does not fire for programmatic state changes.
292
+ - `onValueChange` and `onValuesChange` follow the same silence rules as their DOM events.
188
293
 
189
- Placement attributes (`data-side`, `data-align`, `data-side-offset`, `data-align-offset`, `data-avoid-collisions`, `data-collision-padding`) resolve in this order:
294
+ ## Downstream Wrapper Contract
190
295
 
191
- 1. JavaScript option
192
- 2. `dropdown-menu-content`
193
- 3. `dropdown-menu-positioner`
194
- 4. `dropdown-menu` root (fallback)
195
-
196
- | Attribute | Type | Default | Description |
197
- |-----------|------|---------|-------------|
198
- | `data-default-open` | boolean | `false` | Initial open state |
199
- | `data-close-on-click-outside` | boolean | `true` | Close when clicking outside |
200
- | `data-close-on-escape` | boolean | `true` | Close when pressing Escape |
201
- | `data-close-on-select` | boolean | `true` | Close when an item is selected |
202
- | `data-highlight-item-on-hover` | boolean | `true` | Highlight and focus items on pointer hover |
203
- | `data-side` | string | `"bottom"` | Preferred side: top, right, bottom, left |
204
- | `data-align` | string | `"start"` | Alignment: start, center, end |
205
- | `data-side-offset` | number | `4` | Distance from trigger in px |
206
- | `data-align-offset` | number | `0` | Offset from alignment edge in px |
207
- | `data-avoid-collisions` | boolean | `true` | Flip/shift to stay in viewport |
208
- | `data-collision-padding` | number | `8` | Viewport edge padding in px |
209
-
210
- Boolean attributes: present or `"true"` = true, `"false"` = false, absent = default.
296
+ This package does not ship Astro components, but downstream wrappers should mirror this authoring model:
211
297
 
212
- ```html
213
- <!-- Menu positioned at top with larger offset -->
214
- <div data-slot="dropdown-menu" data-side="top" data-side-offset="8">
215
- ...
216
- </div>
298
+ - `DropdownMenuItem` renders `data-slot="dropdown-menu-item"`.
299
+ - `DropdownMenuRadioItem` renders `data-slot="dropdown-menu-radio-item"` and requires `value`.
300
+ - `DropdownMenuCheckboxItem` renders `data-slot="dropdown-menu-checkbox-item"` and requires `value`.
301
+ - Radio and checkbox wrappers may expose `defaultChecked`, but root defaults still take precedence over item defaults.
217
302
 
218
- <!-- Menu that stays open after selection -->
219
- <div data-slot="dropdown-menu" data-close-on-select="false">
220
- ...
221
- </div>
303
+ ## Deprecated APIs
304
+
305
+ The following compatibility APIs are deprecated and will be removed in the next major release:
306
+
307
+ ```js
308
+ // Deprecated open-state alias
309
+ root.addEventListener("dropdown-menu:change", (event) => {
310
+ console.log(event.detail.open);
311
+ });
312
+
313
+ // Deprecated programmatic shape
314
+ root.dispatchEvent(
315
+ new CustomEvent("dropdown-menu:set", { detail: { value: true } })
316
+ );
222
317
  ```
223
318
 
319
+ Use `dropdown-menu:open-change` and `dropdown-menu:set { open: boolean }` instead.
320
+
321
+ ## Migration Notes
322
+
323
+ If you currently use dropdown-menu as a picker:
324
+
325
+ - Replace picker-style `dropdown-menu-item` usage with `dropdown-menu-radio-item` or `dropdown-menu-checkbox-item`.
326
+ - Stop manually writing `data-selected` or `data-checked`.
327
+ - Listen to `dropdown-menu:value-change` or `dropdown-menu:values-change` for committed state.
328
+ - Push external restore/randomize/popstate/storage changes back in through `controller.set(...)` or `dropdown-menu:set`.
329
+ - Switch open-state listeners from `dropdown-menu:change` to `dropdown-menu:open-change`.
330
+
224
331
  ## Positioning
225
332
 
226
- The dropdown menu uses `position: fixed` and automatically positions itself relative to the trigger. It supports all standard placement options:
333
+ Placement attributes (`data-side`, `data-align`, `data-side-offset`, `data-align-offset`, `data-avoid-collisions`, `data-collision-padding`) resolve in this order:
334
+
335
+ 1. JavaScript option
336
+ 2. `dropdown-menu-content`
337
+ 3. `dropdown-menu-positioner`
338
+ 4. `dropdown-menu` root
339
+
340
+ The dropdown menu uses `position: fixed` by default and automatically positions itself relative to the trigger:
227
341
 
228
342
  ```js
229
343
  createDropdownMenu(root, {
230
- side: "bottom", // top, right, bottom, left
231
- align: "start", // start, center, end
232
- sideOffset: 4, // gap from trigger
233
- alignOffset: 0, // shift along alignment axis
344
+ side: "bottom",
345
+ align: "start",
346
+ sideOffset: 4,
347
+ alignOffset: 0,
234
348
  avoidCollisions: true,
235
349
  collisionPadding: 8,
236
350
  });
237
351
  ```
238
352
 
239
- When `avoidCollisions` is enabled (default), the menu will:
240
- - Flip to the opposite side if it would overflow the viewport
241
- - Shift/clamp to stay within the viewport with the specified padding
242
-
243
- The content element receives `data-side` (computed, may flip) and `data-align` (requested, position may shift) attributes, useful for animations.
244
- The positioned element (`dropdown-menu-positioner`, or `dropdown-menu-content` when no positioner is used) also receives `--transform-origin` for scale/zoom animation origins.
353
+ When `avoidCollisions` is enabled, the menu may flip sides or shift within the viewport. The positioned element also receives `--transform-origin` for animation origins.
245
354
 
246
355
  ## License
247
356
 
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require(`@data-slot/core`);const t=[`top`,`right`,`bottom`,`left`],n=[`start`,`center`,`end`],r=`@data-slot/dropdown-menu`;function i(i,a={}){let o=(0,e.reuseRootBinding)(i,r,`[@data-slot/dropdown-menu] createDropdownMenu() called more than once for the same root. Returning the existing controller. Destroy it before rebinding with new options.`);if(o)return o;let s=(0,e.getPart)(i,`dropdown-menu-trigger`),c=(0,e.getPart)(i,`dropdown-menu-content`),l=(0,e.getPart)(i,`dropdown-menu-positioner`),u=l&&c&&l.contains(c)?l:null,d=(0,e.getPart)(i,`dropdown-menu-portal`),f=d&&u&&d.contains(u)?d:null;if(!s||!c)throw Error(`DropdownMenu requires trigger and content slots`);let p=a.defaultOpen??(0,e.getDataBool)(i,`defaultOpen`)??!1,m=a.onOpenChange,h=a.onSelect,ee=a.closeOnClickOutside??(0,e.getDataBool)(i,`closeOnClickOutside`)??!0,g=a.closeOnEscape??(0,e.getDataBool)(i,`closeOnEscape`)??!0,_=a.closeOnSelect??(0,e.getDataBool)(i,`closeOnSelect`)??!0,v=(t,n)=>(0,e.getDataEnum)(c,t,n)??(u?(0,e.getDataEnum)(u,t,n):void 0)??(0,e.getDataEnum)(i,t,n),y=t=>(0,e.getDataNumber)(c,t)??(u?(0,e.getDataNumber)(u,t):void 0)??(0,e.getDataNumber)(i,t),te=t=>(0,e.getDataBool)(c,t)??(u?(0,e.getDataBool)(u,t):void 0)??(0,e.getDataBool)(i,t),b=a.side??v(`side`,t)??`bottom`,x=a.align??v(`align`,n)??`start`,S=a.sideOffset??y(`sideOffset`)??4,C=a.alignOffset??y(`alignOffset`)??0,ne=a.avoidCollisions??te(`avoidCollisions`)??!0,re=a.collisionPadding??y(`collisionPadding`)??8,w=a.lockScroll??(0,e.getDataBool)(i,`lockScroll`)??!0,T=a.highlightItemOnHover??(0,e.getDataBool)(i,`highlightItemOnHover`)??!0,E=!1,D=null,O=-1,k=``,A=null,j=!1,M=[],N=!1,P=(0,e.createPortalLifecycle)({content:c,root:i,wrapperSlot:u?void 0:`dropdown-menu-positioner`,container:u??void 0,mountTarget:u?f??u:void 0}),F=!1,I=[],L=[],R=new Map,z=e=>e.hasAttribute(`disabled`)||e.hasAttribute(`data-disabled`)||e.getAttribute(`aria-disabled`)===`true`,B=e=>e.pointerType!==`touch`,V=(0,e.ensureId)(s,`dropdown-menu-trigger`),H=(0,e.ensureId)(c,`dropdown-menu-content`);s.setAttribute(`aria-haspopup`,`menu`),s.setAttribute(`aria-controls`,H),c.setAttribute(`role`,`menu`),c.setAttribute(`aria-labelledby`,V),c.tabIndex=-1;let U=()=>{I=(0,e.getParts)(c,`dropdown-menu-item`);for(let e of I)e.setAttribute(`role`,`menuitem`),e.hasAttribute(`data-disabled`)||e.hasAttribute(`disabled`)?e.setAttribute(`aria-disabled`,`true`):e.removeAttribute(`aria-disabled`),e.tabIndex=-1;L=I.filter(e=>!z(e)),R=new Map(L.map((e,t)=>[e,t]))},W=()=>{let t=P.container,n=i.ownerDocument.defaultView??window,r=s.getBoundingClientRect(),a=(0,e.computeFloatingPosition)({anchorRect:r,contentRect:(0,e.measurePopupContentRect)(c),side:b,align:x,sideOffset:S,alignOffset:C,avoidCollisions:ne,collisionPadding:re}),o=(0,e.computeFloatingTransformOrigin)({side:a.side,align:a.align,anchorRect:r,popupX:a.x,popupY:a.y});w?(t.style.position=`fixed`,t.style.top=`0px`,t.style.left=`0px`,t.style.transform=`translate3d(${a.x}px, ${a.y}px, 0)`):(t.style.position=`absolute`,t.style.top=`0px`,t.style.left=`0px`,t.style.transform=`translate3d(${a.x+n.scrollX}px, ${a.y+n.scrollY}px, 0)`),t.style.setProperty(`--transform-origin`,o),t.style.willChange=`transform`,t.style.margin=`0`,c.setAttribute(`data-side`,a.side),c.setAttribute(`data-align`,a.align),t!==c&&(t.setAttribute(`data-side`,a.side),t.setAttribute(`data-align`,a.align))},G=(0,e.createPositionSync)({observedElements:[s,c],isActive:()=>E,ancestorScroll:w,onUpdate:W}),K=(t,n=!0)=>{for(let r=0;r<L.length;r++){let i=L[r];r===t?(i.setAttribute(`data-highlighted`,``),(0,e.ensureItemVisibleInContainer)(i,c),n&&i.focus()):i.removeAttribute(`data-highlighted`)}O=t},q=()=>{for(let e of I)e.removeAttribute(`data-highlighted`);O=-1},J=()=>{q(),(0,e.focusElement)(c)},Y=e=>{i.setAttribute(`data-state`,e),c.setAttribute(`data-state`,e),e===`open`?(i.setAttribute(`data-open`,``),c.setAttribute(`data-open`,``),i.removeAttribute(`data-closed`),c.removeAttribute(`data-closed`)):(i.setAttribute(`data-closed`,``),c.setAttribute(`data-closed`,``),i.removeAttribute(`data-open`),c.removeAttribute(`data-open`))},ie=()=>{requestAnimationFrame(()=>{D&&document.contains(D)?(0,e.focusElement)(D):s&&document.contains(s)&&(0,e.focusElement)(s),D=null})},X=(0,e.createPresenceLifecycle)({element:c,onExitComplete:()=>{F||(P.restore(),c.hidden=!0,ie())}}),Z=t=>{E!==t&&(t?(D=document.activeElement,E=!0,(0,e.setAria)(s,`expanded`,!0),P.mount(),c.hidden=!1,Y(`open`),X.enter(),w&&!N&&((0,e.lockScroll)(),N=!0),U(),j=!1,q(),G.start(),W(),G.update(),c.focus()):(E=!1,(0,e.setAria)(s,`expanded`,!1),Y(`closed`),q(),k=``,j=!1,N&&=((0,e.unlockScroll)(),!1),G.stop(),X.exit()),(0,e.emit)(i,`dropdown-menu:change`,{open:E}),m?.(E))},Q=t=>{if(z(t))return;let n=t.dataset.value||t.textContent?.trim()||``;(0,e.emit)(i,`dropdown-menu:select`,{value:n}),h?.(n),_&&Z(!1)},ae=e=>{let t=L.length;if(t!==0)switch(e.key){case`ArrowDown`:e.preventDefault(),j=!0,K(O===-1?0:(O+1)%t);break;case`ArrowUp`:e.preventDefault(),j=!0,K(O===-1?t-1:(O-1+t)%t);break;case`Home`:e.preventDefault(),j=!0,K(0);break;case`End`:e.preventDefault(),j=!0,K(t-1);break;case`Enter`:case` `:e.preventDefault(),O>=0&&Q(L[O]);break;case`Tab`:Z(!1);break;default:e.key.length===1&&!e.ctrlKey&&!e.metaKey&&!e.altKey&&(e.preventDefault(),oe(e.key.toLowerCase()))}},oe=e=>{A&&clearTimeout(A),A=setTimeout(()=>{k=``},500),k+=e;let t=L.findIndex(e=>(e.textContent?.trim().toLowerCase()||``).startsWith(k));if(t===-1&&k.length===1){let n=O+1;for(let r=0;r<L.length;r++){let i=(n+r)%L.length;if((L[i].textContent?.trim().toLowerCase()||``).startsWith(e)){t=i;break}}}t!==-1&&(j=!0,K(t))};(0,e.setAria)(s,`expanded`,!1),c.hidden=!0,Y(`closed`),M.push((0,e.on)(s,`click`,()=>Z(!E)),(0,e.on)(s,`keydown`,e=>{(e.key===`Enter`||e.key===` `||e.key===`ArrowDown`)&&!E&&(e.preventDefault(),Z(!0))})),M.push((0,e.on)(c,`keydown`,ae),(0,e.on)(c,`click`,e=>{let t=e.target.closest?.(`[data-slot="dropdown-menu-item"]`);t&&Q(t)}),(0,e.on)(c,`pointermove`,e=>{if(!T||!B(e))return;let t=e.target.closest?.(`[data-slot="dropdown-menu-item"]`);if(!(j&&(j=!1,t&&R.get(t)===O)))if(t&&!z(t)){let e=R.get(t);e!==void 0&&e!==O&&K(e,!0)}else J()}),(0,e.on)(c,`pointerleave`,e=>{!T||!B(e)||j||J()})),M.push((0,e.createDismissLayer)({root:i,isOpen:()=>E,onDismiss:()=>Z(!1),closeOnClickOutside:ee,closeOnEscape:g})),M.push((0,e.on)(i,`dropdown-menu:set`,e=>{let t=e.detail,n;t?.open===void 0?t?.value!==void 0&&(n=t.value):n=t.open,typeof n==`boolean`&&Z(n)}));let $={open:()=>Z(!0),close:()=>Z(!1),toggle:()=>Z(!E),get isOpen(){return E},destroy:()=>{F=!0,A&&clearTimeout(A),G.stop(),X.cleanup(),P.cleanup(),N&&=((0,e.unlockScroll)(),!1),M.forEach(e=>e()),M.length=0,(0,e.clearRootBinding)(i,r,$)}};return(0,e.setRootBinding)(i,r,$),p&&Z(!0),$}function a(t=document){let n=[];for(let a of(0,e.getRoots)(t,`dropdown-menu`))(0,e.hasRootBinding)(a,r)||n.push(i(a));return n}exports.create=a,exports.createDropdownMenu=i;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require(`@data-slot/core`);const t=[`top`,`right`,`bottom`,`left`],n=[`start`,`center`,`end`],r=`@data-slot/dropdown-menu`,i=`[data-slot="dropdown-menu-item"], [data-slot="dropdown-menu-radio-item"], [data-slot="dropdown-menu-checkbox-item"]`,a=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),o=(e,t,n)=>{n?e.setAttribute(t,``):e.removeAttribute(t)},s=(e,t)=>{if(e.length!==t.length)return!1;for(let n=0;n<e.length;n++)if(e[n]!==t[n])return!1;return!0},c=(e,t,n,r=!1)=>e.dispatchEvent(new CustomEvent(t,{bubbles:!0,cancelable:r,detail:n})),ee=e=>{switch(e){case`radio`:return`menuitemradio`;case`checkbox`:return`menuitemcheckbox`;default:return`menuitem`}},te=e=>{let t=e.getAttribute(`data-slot`);return t===`dropdown-menu-radio-item`?`radio`:t===`dropdown-menu-checkbox-item`?`checkbox`:`item`},ne=e=>{let t=e.dataset.value;if(t===void 0)return null;let n=t.trim();return n.length>0?n:null},re=e=>e.type===`item`?e.value?e.value:e.el.textContent?.trim()??``:e.value??``,ie=e=>{if(e===void 0)return[];let t=e.trim();if(t.length===0)return[];try{let e=JSON.parse(t);return Array.isArray(e)?e.filter(e=>typeof e==`string`).map(e=>e.trim()).filter(e=>e.length>0):[]}catch{return[]}};function l(l,u={}){let ae=(0,e.reuseRootBinding)(l,r,`[@data-slot/dropdown-menu] createDropdownMenu() called more than once for the same root. Returning the existing controller. Destroy it before rebinding with new options.`);if(ae)return ae;let d=(0,e.getPart)(l,`dropdown-menu-trigger`),f=(0,e.getPart)(l,`dropdown-menu-content`),p=(0,e.getPart)(l,`dropdown-menu-positioner`),m=p&&f&&p.contains(f)?p:null,h=(0,e.getPart)(l,`dropdown-menu-portal`),oe=h&&m&&h.contains(m)?h:null;if(!d||!f)throw Error(`DropdownMenu requires trigger and content slots`);let se=u.defaultOpen??(0,e.getDataBool)(l,`defaultOpen`)??!1,ce=u.onOpenChange,le=u.onSelect,ue=u.onValueChange,de=u.onValuesChange,g=u.closeOnClickOutside??(0,e.getDataBool)(l,`closeOnClickOutside`)??!0,fe=u.closeOnEscape??(0,e.getDataBool)(l,`closeOnEscape`)??!0,pe=u.closeOnSelect??(0,e.getDataBool)(l,`closeOnSelect`)??!0,me=(t,n)=>(0,e.getDataEnum)(f,t,n)??(m?(0,e.getDataEnum)(m,t,n):void 0)??(0,e.getDataEnum)(l,t,n),_=t=>(0,e.getDataNumber)(f,t)??(m?(0,e.getDataNumber)(m,t):void 0)??(0,e.getDataNumber)(l,t),he=t=>(0,e.getDataBool)(f,t)??(m?(0,e.getDataBool)(m,t):void 0)??(0,e.getDataBool)(l,t),ge=u.side??me(`side`,t)??`bottom`,_e=u.align??me(`align`,n)??`start`,ve=u.sideOffset??_(`sideOffset`)??4,ye=u.alignOffset??_(`alignOffset`)??0,be=u.avoidCollisions??he(`avoidCollisions`)??!0,xe=u.collisionPadding??_(`collisionPadding`)??8,v=u.lockScroll??(0,e.getDataBool)(l,`lockScroll`)??!0,Se=u.highlightItemOnHover??(0,e.getDataBool)(l,`highlightItemOnHover`)??!0,Ce=a(u,`defaultValue`),we=a(u,`defaultValues`),Te=l.hasAttribute(`data-default-value`),Ee=l.hasAttribute(`data-default-values`),De=Ce?u.defaultValue??null:Te?(0,e.getDataString)(l,`defaultValue`)??null:null,Oe=we?u.defaultValues??[]:Ee?ie((0,e.getDataString)(l,`defaultValues`)):[],y=!1,b=null,x=[],S=null,C=null,w=``,T=null,E=!1,D=!1,ke=!1,O=null,k=[],A=(0,e.createPortalLifecycle)({content:f,root:l,wrapperSlot:m?void 0:`dropdown-menu-positioner`,container:m??void 0,mountTarget:m?oe??m:void 0}),j=[],M=[],N=new Map,Ae=e=>e.hasAttribute(`disabled`)||e.hasAttribute(`data-disabled`)||e.getAttribute(`aria-disabled`)===`true`,je=e=>e.pointerType!==`touch`,Me=e=>e instanceof Element?e.closest(i):null,P=e=>e?j.find(t=>t.el===e)??null:null,F=()=>j.filter(e=>e.type===`radio`),I=()=>j.filter(e=>e.type===`checkbox`),Ne=(e,t)=>e.find(e=>e.type===`radio`&&e.value===t)??null,L=e=>e?e.type===`item`?re(e):e.value:null,Pe=e=>(e.type===`radio`||e.type===`checkbox`)&&e.value===null,R=e=>Ae(e.el)||Pe(e),z=e=>F().find(t=>t.value===e)??null,Fe=e=>M.find(t=>L(t)===e)??null,Ie=(e,t,n=I())=>{let r=new Set(e),i=new Set(t),a=null,o=null,s=null;for(let e of n){if(e.type!==`checkbox`)continue;let t=e.value;if(!t)continue;let n=r.has(t),c=i.has(t);if(n!==c){if(a!==null)return{changedValue:null,checked:null,item:null};a=t,o=c,s=e.el}}return{changedValue:a,checked:o,item:s}},B=(e,t)=>{let n=I().filter(e=>e.value!==null);if(n.length===0)return null;if(e.length===0)return[];let r=new Set(e.filter(e=>typeof e==`string`).map(e=>e.trim()).filter(e=>e.length>0));if(r.size===0)return t===`init`?[]:null;let i=[];for(let e of n)e.value&&r.has(e.value)&&i.push(e.value);return i.length===0?t===`init`?[]:null:i},V=()=>{for(let t of j){let n=R(t);if(t.el.setAttribute(`role`,ee(t.type)),t.el.tabIndex=-1,n?t.el.setAttribute(`aria-disabled`,`true`):t.el.removeAttribute(`aria-disabled`),t.type===`radio`){let n=t.value!==null&&b===t.value;o(t.el,`data-checked`,n),(0,e.setAria)(t.el,`checked`,t.value===null?null:n)}else if(t.type===`checkbox`){let n=t.value!==null&&x.includes(t.value);o(t.el,`data-checked`,n),(0,e.setAria)(t.el,`checked`,t.value===null?null:n)}else t.el.removeAttribute(`data-checked`),t.el.removeAttribute(`aria-checked`)}F().length>0&&b!==null?l.setAttribute(`data-value`,b):l.removeAttribute(`data-value`)},Le=()=>{for(let e of j)o(e.el,`data-highlighted`,e.el===S)},H=({source:e=`programmatic`,emitSelectionInvalidation:t=!1}={})=>{let n=j,r=b,a=[...x];j=Array.from(f.querySelectorAll(i)).map(e=>({el:e,type:te(e),value:ne(e)}));let o=r!==null&&z(r)?r:null,c=a.length>0?B(a,`init`)??[]:[];if(b=o,x=c,M=j.filter(e=>!R(e)),N=new Map(M.map((e,t)=>[e.el,t])),S&&!N.has(S)&&(S=null),V(),Le(),t&&(r!==b&&Be({value:b,previousValue:r,item:b===null?null:z(b)?.el??null,previousItem:r===null?null:Ne(n,r)?.el??null,source:e}),!s(a,x))){let t=Ie(a,x,n.filter(e=>e.type===`checkbox`));Ve({values:[...x],previousValues:a,changedValue:t.changedValue,checked:t.checked,item:t.item,source:e})}},Re=t=>{(0,e.emit)(l,`dropdown-menu:open-change`,t),(0,e.emit)(l,`dropdown-menu:change`,t),ce?.(t.open)},ze=t=>{(0,e.emit)(l,`dropdown-menu:highlight-change`,t)},Be=t=>{(0,e.emit)(l,`dropdown-menu:value-change`,t),ue?.(t.value)},Ve=t=>{(0,e.emit)(l,`dropdown-menu:values-change`,t),de?.([...t.values])},He=()=>{let t=A.container,n=l.ownerDocument.defaultView??window,r=d.getBoundingClientRect(),i=(0,e.computeFloatingPosition)({anchorRect:r,contentRect:(0,e.measurePopupContentRect)(f),side:ge,align:_e,sideOffset:ve,alignOffset:ye,avoidCollisions:be,collisionPadding:xe}),a=(0,e.computeFloatingTransformOrigin)({side:i.side,align:i.align,anchorRect:r,popupX:i.x,popupY:i.y});v?(t.style.position=`fixed`,t.style.top=`0px`,t.style.left=`0px`,t.style.transform=`translate3d(${i.x}px, ${i.y}px, 0)`):(t.style.position=`absolute`,t.style.top=`0px`,t.style.left=`0px`,t.style.transform=`translate3d(${i.x+n.scrollX}px, ${i.y+n.scrollY}px, 0)`),t.style.setProperty(`--transform-origin`,a),t.style.willChange=`transform`,t.style.margin=`0`,f.setAttribute(`data-side`,i.side),f.setAttribute(`data-align`,i.align),t!==f&&(t.setAttribute(`data-side`,i.side),t.setAttribute(`data-align`,i.align))},U=(0,e.createPositionSync)({observedElements:[d,f],isActive:()=>y,ancestorScroll:v,onUpdate:He}),Ue=()=>{requestAnimationFrame(()=>{C&&document.contains(C)?(0,e.focusElement)(C):document.contains(d)&&(0,e.focusElement)(d),C=null})},W=(0,e.createPresenceLifecycle)({element:f,onExitComplete:()=>{ke||(A.restore(),f.hidden=!0,Ue())}}),G=e=>{l.setAttribute(`data-state`,e),f.setAttribute(`data-state`,e),e===`open`?(l.setAttribute(`data-open`,``),f.setAttribute(`data-open`,``),l.removeAttribute(`data-closed`),f.removeAttribute(`data-closed`)):(l.setAttribute(`data-closed`,``),f.setAttribute(`data-closed`,``),l.removeAttribute(`data-open`),f.removeAttribute(`data-open`))},K=(t,{source:n,focus:r=!0,focusContentOnClear:i=!1})=>{if(t&&!N.has(t))return!1;let a=S;return a===t?(t&&r?((0,e.ensureItemVisibleInContainer)(t,f),(0,e.focusElement)(t)):!t&&i&&(0,e.focusElement)(f),!1):(S=t,Le(),t?((0,e.ensureItemVisibleInContainer)(t,f),r&&(0,e.focusElement)(t)):i&&(0,e.focusElement)(f),ze({value:L(P(t)),previousValue:L(P(a)),item:t,previousItem:a,source:n}),!0)},q=(e,t,n=!0)=>{if(H({source:t,emitSelectionInvalidation:n}),F().length===0)return!1;let r=e===null?null:z(e);if(e!==null&&!r||b===e)return!1;let i=b,a=i===null?null:z(i);return b=e,V(),n&&Be({value:b,previousValue:i,item:r?.el??null,previousItem:a?.el??null,source:t}),!0},We=(e,t,n=!0)=>{H({source:t,emitSelectionInvalidation:n});let r=B(e,n?`set`:`init`);if(r===null||s(x,r))return!1;let i=[...x],a=Ie(i,r);return x=r,V(),n&&Ve({values:[...x],previousValues:i,changedValue:a.changedValue,checked:a.checked,item:a.item,source:t}),!0},Ge=()=>{if(H(),Ce||Te)De===null?b=null:q(De,`programmatic`,!1);else for(let t of F())if(t.value!==null&&(0,e.getDataBool)(t.el,`defaultChecked`)){b=t.value;break}x=we||Ee?B(Oe,`init`)??[]:B(I().filter(t=>t.value!==null&&(0,e.getDataBool)(t.el,`defaultChecked`)).map(e=>e.value),`init`)??[],V()},J=(t,{source:n,reason:r})=>{if(y===t)return;O=null;let i=y;t?(C=document.activeElement,y=!0,(0,e.setAria)(d,`expanded`,!0),A.mount(),f.hidden=!1,G(`open`),W.enter(),v&&!D&&((0,e.lockScroll)(),D=!0),H({source:n===`restore`?`restore`:`programmatic`,emitSelectionInvalidation:n!==`init`}),E=!1,w=``,U.start(),He(),U.update(),(0,e.focusElement)(f)):(y=!1,(0,e.setAria)(d,`expanded`,!1),G(`closed`),S&&K(null,{source:n===`init`?`programmatic`:n,focus:!1,focusContentOnClear:!1}),w=``,E=!1,D&&=((0,e.unlockScroll)(),!1),U.stop(),W.exit()),Re({open:y,previousOpen:i,source:n,reason:r})},Y=(e,t)=>{let n={source:e,reason:t};O=n,queueMicrotask(()=>{O===n&&(O=null)})},X=(e,t)=>{if(R(e))return;let n=L(e);if(n===null)return;let r;if(e.type===`radio`?r=!0:e.type===`checkbox`&&e.value!==null&&(r=!x.includes(e.value)),c(l,`dropdown-menu:select`,{value:n,item:e.el,itemType:e.type,source:t,checked:r},!0)){if(le?.(n),e.type===`radio`)q(e.value,t,!0);else if(e.type===`checkbox`&&e.value!==null){let n=new Set(x);n.has(e.value)?n.delete(e.value):n.add(e.value),We([...n],t,!0)}pe&&J(!1,{source:t,reason:`select`})}},Ke=e=>{T&&clearTimeout(T),T=setTimeout(()=>{w=``},500),w+=e;let t=M.findIndex(e=>(e.el.textContent?.trim().toLowerCase()??``).startsWith(w));if(t===-1&&w.length===1){let n=S?(N.get(S)??-1)+1:0;for(let r=0;r<M.length;r++){let i=(n+r)%M.length;if((M[i]?.el.textContent?.trim().toLowerCase()??``).startsWith(e)){t=i;break}}}t!==-1&&(E=!0,K(M[t]?.el??null,{source:`keyboard`,focus:!0}))},Z=e=>{let t=e.source??`programmatic`;if(e.value!==void 0&&q(e.value,t,!0),e.values!==void 0&&We(e.values,t,!0),e.open!==void 0&&J(e.open,{source:t,reason:`programmatic`}),e.highlightedValue!==void 0){if(!y)return;if(e.highlightedValue===null)K(null,{source:t,focus:!1,focusContentOnClear:!0});else{let n=Fe(e.highlightedValue);n&&K(n.el,{source:t,focus:!0})}}},qe=(0,e.ensureId)(d,`dropdown-menu-trigger`),Je=(0,e.ensureId)(f,`dropdown-menu-content`);d.setAttribute(`aria-haspopup`,`menu`),d.setAttribute(`aria-controls`,Je),f.setAttribute(`role`,`menu`),f.setAttribute(`aria-labelledby`,qe),f.tabIndex=-1,(0,e.setAria)(d,`expanded`,!1),f.hidden=!0,G(`closed`),Ge(),k.push((0,e.on)(d,`click`,()=>{J(!y,{source:`pointer`,reason:`trigger`})}),(0,e.on)(d,`keydown`,e=>{(e.key===`Enter`||e.key===` `||e.key===`ArrowDown`)&&!y&&(e.preventDefault(),J(!0,{source:`keyboard`,reason:`trigger`}))})),k.push((0,e.on)(f,`keydown`,e=>{if(e.key===`Tab`){J(!1,{source:`keyboard`,reason:`tab`});return}let t=M.length;if(t!==0)switch(e.key){case`ArrowDown`:e.preventDefault(),E=!0,K(M[S?((N.get(S)??-1)+1)%t:0]?.el??null,{source:`keyboard`,focus:!0});break;case`ArrowUp`:e.preventDefault(),E=!0,K(M[S?(N.get(S)-1+t)%t:t-1]?.el??null,{source:`keyboard`,focus:!0});break;case`Home`:e.preventDefault(),E=!0,K(M[0]?.el??null,{source:`keyboard`,focus:!0});break;case`End`:e.preventDefault(),E=!0,K(M[t-1]?.el??null,{source:`keyboard`,focus:!0});break;case`Enter`:case` `:if(e.preventDefault(),S){let e=P(S);e&&X(e,`keyboard`)}break;default:e.key.length===1&&!e.ctrlKey&&!e.metaKey&&!e.altKey&&(e.preventDefault(),Ke(e.key.toLowerCase()))}}),(0,e.on)(f,`click`,e=>{let t=P(Me(e.target));t&&X(t,`pointer`)}),(0,e.on)(f,`pointermove`,e=>{if(!Se||!je(e))return;let t=Me(e.target);E&&(E=!1,t&&t===S)||(t&&N.has(t)?K(t,{source:`pointer`,focus:!0}):S&&K(null,{source:`pointer`,focus:!1,focusContentOnClear:!0}))}),(0,e.on)(f,`pointerleave`,e=>{!Se||!je(e)||E||!S||K(null,{source:`pointer`,focus:!1,focusContentOnClear:!0})}));let Q=l.ownerDocument??document;k.push((0,e.on)(Q,`pointerdown`,t=>{if(!y||!g||t.pointerType===`touch`)return;let n=t.target;(0,e.containsWithPortals)(l,n)||Y(`pointer`,`outside`)},{capture:!0}),(0,e.on)(Q,`click`,t=>{if(!y||!g)return;let n=t.target;(0,e.containsWithPortals)(l,n)||Y(`pointer`,`outside`)},{capture:!0}),(0,e.on)(Q,`keydown`,e=>{!y||!fe||e.key!==`Escape`||e.defaultPrevented||Y(`keyboard`,`escape`)},{capture:!0})),k.push((0,e.createDismissLayer)({root:l,isOpen:()=>y,onDismiss:()=>{let e=O;if(O=null,e?.reason===`escape`){J(!1,{source:e.source,reason:`escape`});return}J(!1,{source:e?.source??`pointer`,reason:e?.reason??`outside`})},closeOnClickOutside:g,closeOnEscape:fe})),k.push((0,e.on)(l,`dropdown-menu:set`,e=>{let t=e.detail;if(!t||typeof t!=`object`)return;let n={source:t.source===`restore`||t.source===`programmatic`?t.source:void 0};t.open!==void 0&&(n.open=t.open),t.values!==void 0&&(n.values=Array.isArray(t.values)?t.values.filter(e=>typeof e==`string`):void 0),t.highlightedValue!==void 0&&(n.highlightedValue=t.highlightedValue===null||typeof t.highlightedValue==`string`?t.highlightedValue:void 0),t.value!==void 0&&(typeof t.value==`boolean`&&t.open===void 0?n.open=t.value:(t.value===null||typeof t.value==`string`)&&(n.value=t.value)),Z(n)}));let $={open:()=>J(!0,{source:`programmatic`,reason:`programmatic`}),close:()=>J(!1,{source:`programmatic`,reason:`programmatic`}),toggle:()=>J(!y,{source:`programmatic`,reason:`programmatic`}),set:e=>{Z(e)},get isOpen(){return y},get value(){return b},get values(){return[...x]},get highlightedValue(){return L(P(S))},destroy:()=>{ke=!0,T&&clearTimeout(T),U.stop(),W.cleanup(),A.cleanup(),D&&=((0,e.unlockScroll)(),!1),k.forEach(e=>e()),k.length=0,(0,e.clearRootBinding)(l,r,$)}};return(0,e.setRootBinding)(l,r,$),se&&J(!0,{source:`init`,reason:`init`}),$}function u(t=document){let n=[];for(let i of(0,e.getRoots)(t,`dropdown-menu`))(0,e.hasRootBinding)(i,r)||n.push(l(i));return n}exports.create=u,exports.createDropdownMenu=l;
package/dist/index.d.cts CHANGED
@@ -3,13 +3,69 @@
3
3
  type Side = "top" | "right" | "bottom" | "left";
4
4
  /** Alignment of the content relative to the trigger */
5
5
  type Align = "start" | "center" | "end";
6
+ type DropdownMenuItemType = "item" | "radio" | "checkbox";
7
+ type DropdownMenuUserSource = "pointer" | "keyboard";
8
+ type DropdownMenuSetSource = "programmatic" | "restore";
9
+ type DropdownMenuSelectionSource = DropdownMenuUserSource | DropdownMenuSetSource;
10
+ type DropdownMenuOpenChangeSource = DropdownMenuSelectionSource | "init";
11
+ type DropdownMenuOpenChangeReason = "trigger" | "select" | "outside" | "escape" | "tab" | "programmatic" | "init";
12
+ interface DropdownMenuOpenChangeDetail {
13
+ open: boolean;
14
+ previousOpen: boolean;
15
+ source: DropdownMenuOpenChangeSource;
16
+ reason: DropdownMenuOpenChangeReason;
17
+ }
18
+ interface DropdownMenuHighlightChangeDetail {
19
+ value: string | null;
20
+ previousValue: string | null;
21
+ item: HTMLElement | null;
22
+ previousItem: HTMLElement | null;
23
+ source: DropdownMenuSelectionSource;
24
+ }
25
+ interface DropdownMenuSelectDetail {
26
+ value: string;
27
+ item: HTMLElement;
28
+ itemType: DropdownMenuItemType;
29
+ source: DropdownMenuUserSource;
30
+ checked?: boolean;
31
+ }
32
+ interface DropdownMenuValueChangeDetail {
33
+ value: string | null;
34
+ previousValue: string | null;
35
+ item: HTMLElement | null;
36
+ previousItem: HTMLElement | null;
37
+ source: DropdownMenuSelectionSource;
38
+ }
39
+ interface DropdownMenuValuesChangeDetail {
40
+ values: string[];
41
+ previousValues: string[];
42
+ changedValue: string | null;
43
+ checked: boolean | null;
44
+ item: HTMLElement | null;
45
+ source: DropdownMenuSelectionSource;
46
+ }
47
+ interface DropdownMenuSetDetail {
48
+ open?: boolean;
49
+ value?: string | null;
50
+ values?: string[];
51
+ highlightedValue?: string | null;
52
+ source?: DropdownMenuSetSource;
53
+ }
6
54
  interface DropdownMenuOptions {
7
55
  /** Initial open state */
8
56
  defaultOpen?: boolean;
57
+ /** Initial radio selection state */
58
+ defaultValue?: string | null;
59
+ /** Initial checkbox selection state */
60
+ defaultValues?: string[];
9
61
  /** Callback when open state changes */
10
62
  onOpenChange?: (open: boolean) => void;
11
- /** Callback when an item is selected */
63
+ /** Callback when a user activation is accepted */
12
64
  onSelect?: (value: string) => void;
65
+ /** Callback when the committed radio value changes */
66
+ onValueChange?: (value: string | null) => void;
67
+ /** Callback when the committed checkbox values change */
68
+ onValuesChange?: (values: string[]) => void;
13
69
  /** Close when clicking outside */
14
70
  closeOnClickOutside?: boolean;
15
71
  /** Close when pressing Escape */
@@ -66,8 +122,16 @@ interface DropdownMenuController {
66
122
  close(): void;
67
123
  /** Toggle the dropdown menu */
68
124
  toggle(): void;
125
+ /** Set one or more dropdown menu state fields programmatically */
126
+ set(detail: DropdownMenuSetDetail): void;
69
127
  /** Current open state */
70
128
  readonly isOpen: boolean;
129
+ /** Current committed radio value */
130
+ readonly value: string | null;
131
+ /** Current committed checkbox values */
132
+ readonly values: string[];
133
+ /** Current highlighted value */
134
+ readonly highlightedValue: string | null;
71
135
  /** Cleanup all event listeners */
72
136
  destroy(): void;
73
137
  }
@@ -83,16 +147,25 @@ interface DropdownMenuController {
83
147
  * - `collisionPadding`: viewport edge padding in px (default: 8)
84
148
  *
85
149
  * ## Events
86
- * - **Outbound** `dropdown-menu:change` (on root): Fires when menu opens/closes.
87
- * `event.detail: { open: boolean }`
88
- * - **Outbound** `dropdown-menu:select` (on root): Fires when an item is selected.
89
- * `event.detail: { value: string }`
150
+ * - **Outbound** `dropdown-menu:open-change` (on root): Fires when menu opens/closes.
151
+ * `event.detail: DropdownMenuOpenChangeDetail`
152
+ * - **Outbound** `dropdown-menu:change` (on root): Deprecated alias for `dropdown-menu:open-change`.
153
+ * - **Outbound** `dropdown-menu:highlight-change` (on root): Fires when highlight changes.
154
+ * `event.detail: DropdownMenuHighlightChangeDetail`
155
+ * - **Outbound** `dropdown-menu:select` (on root): Cancelable user activation event fired before commit.
156
+ * `event.detail: DropdownMenuSelectDetail`
157
+ * - **Outbound** `dropdown-menu:value-change` (on root): Fires when radio selection changes.
158
+ * `event.detail: DropdownMenuValueChangeDetail`
159
+ * - **Outbound** `dropdown-menu:values-change` (on root): Fires when checkbox selection changes.
160
+ * `event.detail: DropdownMenuValuesChangeDetail`
161
+ * - **Inbound** `dropdown-menu:set` (on root): Set open/highlight/selection state programmatically.
162
+ * `event.detail: DropdownMenuSetDetail`
90
163
  */
91
164
  declare function createDropdownMenu(root: Element, options?: DropdownMenuOptions): DropdownMenuController;
92
165
  /**
93
- * Find and bind all dropdown menu components in a scope
94
- * Returns array of controllers for programmatic access
166
+ * Find and bind all dropdown menu components in a scope.
167
+ * Returns array of controllers for programmatic access.
95
168
  */
96
169
  declare function create(scope?: ParentNode): DropdownMenuController[];
97
170
  //#endregion
98
- export { Align, DropdownMenuController, DropdownMenuOptions, Side, create, createDropdownMenu };
171
+ export { Align, DropdownMenuController, DropdownMenuHighlightChangeDetail, DropdownMenuItemType, DropdownMenuOpenChangeDetail, DropdownMenuOpenChangeReason, DropdownMenuOpenChangeSource, DropdownMenuOptions, DropdownMenuSelectDetail, DropdownMenuSelectionSource, DropdownMenuSetDetail, DropdownMenuSetSource, DropdownMenuUserSource, DropdownMenuValueChangeDetail, DropdownMenuValuesChangeDetail, Side, create, createDropdownMenu };
package/dist/index.d.ts CHANGED
@@ -3,13 +3,69 @@
3
3
  type Side = "top" | "right" | "bottom" | "left";
4
4
  /** Alignment of the content relative to the trigger */
5
5
  type Align = "start" | "center" | "end";
6
+ type DropdownMenuItemType = "item" | "radio" | "checkbox";
7
+ type DropdownMenuUserSource = "pointer" | "keyboard";
8
+ type DropdownMenuSetSource = "programmatic" | "restore";
9
+ type DropdownMenuSelectionSource = DropdownMenuUserSource | DropdownMenuSetSource;
10
+ type DropdownMenuOpenChangeSource = DropdownMenuSelectionSource | "init";
11
+ type DropdownMenuOpenChangeReason = "trigger" | "select" | "outside" | "escape" | "tab" | "programmatic" | "init";
12
+ interface DropdownMenuOpenChangeDetail {
13
+ open: boolean;
14
+ previousOpen: boolean;
15
+ source: DropdownMenuOpenChangeSource;
16
+ reason: DropdownMenuOpenChangeReason;
17
+ }
18
+ interface DropdownMenuHighlightChangeDetail {
19
+ value: string | null;
20
+ previousValue: string | null;
21
+ item: HTMLElement | null;
22
+ previousItem: HTMLElement | null;
23
+ source: DropdownMenuSelectionSource;
24
+ }
25
+ interface DropdownMenuSelectDetail {
26
+ value: string;
27
+ item: HTMLElement;
28
+ itemType: DropdownMenuItemType;
29
+ source: DropdownMenuUserSource;
30
+ checked?: boolean;
31
+ }
32
+ interface DropdownMenuValueChangeDetail {
33
+ value: string | null;
34
+ previousValue: string | null;
35
+ item: HTMLElement | null;
36
+ previousItem: HTMLElement | null;
37
+ source: DropdownMenuSelectionSource;
38
+ }
39
+ interface DropdownMenuValuesChangeDetail {
40
+ values: string[];
41
+ previousValues: string[];
42
+ changedValue: string | null;
43
+ checked: boolean | null;
44
+ item: HTMLElement | null;
45
+ source: DropdownMenuSelectionSource;
46
+ }
47
+ interface DropdownMenuSetDetail {
48
+ open?: boolean;
49
+ value?: string | null;
50
+ values?: string[];
51
+ highlightedValue?: string | null;
52
+ source?: DropdownMenuSetSource;
53
+ }
6
54
  interface DropdownMenuOptions {
7
55
  /** Initial open state */
8
56
  defaultOpen?: boolean;
57
+ /** Initial radio selection state */
58
+ defaultValue?: string | null;
59
+ /** Initial checkbox selection state */
60
+ defaultValues?: string[];
9
61
  /** Callback when open state changes */
10
62
  onOpenChange?: (open: boolean) => void;
11
- /** Callback when an item is selected */
63
+ /** Callback when a user activation is accepted */
12
64
  onSelect?: (value: string) => void;
65
+ /** Callback when the committed radio value changes */
66
+ onValueChange?: (value: string | null) => void;
67
+ /** Callback when the committed checkbox values change */
68
+ onValuesChange?: (values: string[]) => void;
13
69
  /** Close when clicking outside */
14
70
  closeOnClickOutside?: boolean;
15
71
  /** Close when pressing Escape */
@@ -66,8 +122,16 @@ interface DropdownMenuController {
66
122
  close(): void;
67
123
  /** Toggle the dropdown menu */
68
124
  toggle(): void;
125
+ /** Set one or more dropdown menu state fields programmatically */
126
+ set(detail: DropdownMenuSetDetail): void;
69
127
  /** Current open state */
70
128
  readonly isOpen: boolean;
129
+ /** Current committed radio value */
130
+ readonly value: string | null;
131
+ /** Current committed checkbox values */
132
+ readonly values: string[];
133
+ /** Current highlighted value */
134
+ readonly highlightedValue: string | null;
71
135
  /** Cleanup all event listeners */
72
136
  destroy(): void;
73
137
  }
@@ -83,16 +147,25 @@ interface DropdownMenuController {
83
147
  * - `collisionPadding`: viewport edge padding in px (default: 8)
84
148
  *
85
149
  * ## Events
86
- * - **Outbound** `dropdown-menu:change` (on root): Fires when menu opens/closes.
87
- * `event.detail: { open: boolean }`
88
- * - **Outbound** `dropdown-menu:select` (on root): Fires when an item is selected.
89
- * `event.detail: { value: string }`
150
+ * - **Outbound** `dropdown-menu:open-change` (on root): Fires when menu opens/closes.
151
+ * `event.detail: DropdownMenuOpenChangeDetail`
152
+ * - **Outbound** `dropdown-menu:change` (on root): Deprecated alias for `dropdown-menu:open-change`.
153
+ * - **Outbound** `dropdown-menu:highlight-change` (on root): Fires when highlight changes.
154
+ * `event.detail: DropdownMenuHighlightChangeDetail`
155
+ * - **Outbound** `dropdown-menu:select` (on root): Cancelable user activation event fired before commit.
156
+ * `event.detail: DropdownMenuSelectDetail`
157
+ * - **Outbound** `dropdown-menu:value-change` (on root): Fires when radio selection changes.
158
+ * `event.detail: DropdownMenuValueChangeDetail`
159
+ * - **Outbound** `dropdown-menu:values-change` (on root): Fires when checkbox selection changes.
160
+ * `event.detail: DropdownMenuValuesChangeDetail`
161
+ * - **Inbound** `dropdown-menu:set` (on root): Set open/highlight/selection state programmatically.
162
+ * `event.detail: DropdownMenuSetDetail`
90
163
  */
91
164
  declare function createDropdownMenu(root: Element, options?: DropdownMenuOptions): DropdownMenuController;
92
165
  /**
93
- * Find and bind all dropdown menu components in a scope
94
- * Returns array of controllers for programmatic access
166
+ * Find and bind all dropdown menu components in a scope.
167
+ * Returns array of controllers for programmatic access.
95
168
  */
96
169
  declare function create(scope?: ParentNode): DropdownMenuController[];
97
170
  //#endregion
98
- export { Align, DropdownMenuController, DropdownMenuOptions, Side, create, createDropdownMenu };
171
+ export { Align, DropdownMenuController, DropdownMenuHighlightChangeDetail, DropdownMenuItemType, DropdownMenuOpenChangeDetail, DropdownMenuOpenChangeReason, DropdownMenuOpenChangeSource, DropdownMenuOptions, DropdownMenuSelectDetail, DropdownMenuSelectionSource, DropdownMenuSetDetail, DropdownMenuSetSource, DropdownMenuUserSource, DropdownMenuValueChangeDetail, DropdownMenuValuesChangeDetail, Side, create, createDropdownMenu };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{clearRootBinding as e,computeFloatingPosition as t,computeFloatingTransformOrigin as n,createDismissLayer as r,createPortalLifecycle as i,createPositionSync as a,createPresenceLifecycle as ee,emit as o,ensureId as s,ensureItemVisibleInContainer as te,focusElement as c,getDataBool as l,getDataEnum as u,getDataNumber as d,getPart as f,getParts as ne,getRoots as p,hasRootBinding as m,lockScroll as re,measurePopupContentRect as ie,on as h,reuseRootBinding as ae,setAria as g,setRootBinding as oe,unlockScroll as _}from"@data-slot/core";const se=[`top`,`right`,`bottom`,`left`],ce=[`start`,`center`,`end`],v=`@data-slot/dropdown-menu`;function y(p,m={}){let y=ae(p,v,`[@data-slot/dropdown-menu] createDropdownMenu() called more than once for the same root. Returning the existing controller. Destroy it before rebinding with new options.`);if(y)return y;let b=f(p,`dropdown-menu-trigger`),x=f(p,`dropdown-menu-content`),S=f(p,`dropdown-menu-positioner`),C=S&&x&&S.contains(x)?S:null,w=f(p,`dropdown-menu-portal`),le=w&&C&&w.contains(C)?w:null;if(!b||!x)throw Error(`DropdownMenu requires trigger and content slots`);let ue=m.defaultOpen??l(p,`defaultOpen`)??!1,de=m.onOpenChange,fe=m.onSelect,pe=m.closeOnClickOutside??l(p,`closeOnClickOutside`)??!0,me=m.closeOnEscape??l(p,`closeOnEscape`)??!0,he=m.closeOnSelect??l(p,`closeOnSelect`)??!0,T=(e,t)=>u(x,e,t)??(C?u(C,e,t):void 0)??u(p,e,t),E=e=>d(x,e)??(C?d(C,e):void 0)??d(p,e),ge=e=>l(x,e)??(C?l(C,e):void 0)??l(p,e),_e=m.side??T(`side`,se)??`bottom`,ve=m.align??T(`align`,ce)??`start`,ye=m.sideOffset??E(`sideOffset`)??4,be=m.alignOffset??E(`alignOffset`)??0,xe=m.avoidCollisions??ge(`avoidCollisions`)??!0,Se=m.collisionPadding??E(`collisionPadding`)??8,D=m.lockScroll??l(p,`lockScroll`)??!0,O=m.highlightItemOnHover??l(p,`highlightItemOnHover`)??!0,k=!1,A=null,j=-1,M=``,N=null,P=!1,F=[],I=!1,L=i({content:x,root:p,wrapperSlot:C?void 0:`dropdown-menu-positioner`,container:C??void 0,mountTarget:C?le??C:void 0}),R=!1,z=[],B=[],V=new Map,H=e=>e.hasAttribute(`disabled`)||e.hasAttribute(`data-disabled`)||e.getAttribute(`aria-disabled`)===`true`,U=e=>e.pointerType!==`touch`,Ce=s(b,`dropdown-menu-trigger`),we=s(x,`dropdown-menu-content`);b.setAttribute(`aria-haspopup`,`menu`),b.setAttribute(`aria-controls`,we),x.setAttribute(`role`,`menu`),x.setAttribute(`aria-labelledby`,Ce),x.tabIndex=-1;let Te=()=>{z=ne(x,`dropdown-menu-item`);for(let e of z)e.setAttribute(`role`,`menuitem`),e.hasAttribute(`data-disabled`)||e.hasAttribute(`disabled`)?e.setAttribute(`aria-disabled`,`true`):e.removeAttribute(`aria-disabled`),e.tabIndex=-1;B=z.filter(e=>!H(e)),V=new Map(B.map((e,t)=>[e,t]))},W=()=>{let e=L.container,r=p.ownerDocument.defaultView??window,i=b.getBoundingClientRect(),a=t({anchorRect:i,contentRect:ie(x),side:_e,align:ve,sideOffset:ye,alignOffset:be,avoidCollisions:xe,collisionPadding:Se}),ee=n({side:a.side,align:a.align,anchorRect:i,popupX:a.x,popupY:a.y});D?(e.style.position=`fixed`,e.style.top=`0px`,e.style.left=`0px`,e.style.transform=`translate3d(${a.x}px, ${a.y}px, 0)`):(e.style.position=`absolute`,e.style.top=`0px`,e.style.left=`0px`,e.style.transform=`translate3d(${a.x+r.scrollX}px, ${a.y+r.scrollY}px, 0)`),e.style.setProperty(`--transform-origin`,ee),e.style.willChange=`transform`,e.style.margin=`0`,x.setAttribute(`data-side`,a.side),x.setAttribute(`data-align`,a.align),e!==x&&(e.setAttribute(`data-side`,a.side),e.setAttribute(`data-align`,a.align))},G=a({observedElements:[b,x],isActive:()=>k,ancestorScroll:D,onUpdate:W}),K=(e,t=!0)=>{for(let n=0;n<B.length;n++){let r=B[n];n===e?(r.setAttribute(`data-highlighted`,``),te(r,x),t&&r.focus()):r.removeAttribute(`data-highlighted`)}j=e},q=()=>{for(let e of z)e.removeAttribute(`data-highlighted`);j=-1},J=()=>{q(),c(x)},Y=e=>{p.setAttribute(`data-state`,e),x.setAttribute(`data-state`,e),e===`open`?(p.setAttribute(`data-open`,``),x.setAttribute(`data-open`,``),p.removeAttribute(`data-closed`),x.removeAttribute(`data-closed`)):(p.setAttribute(`data-closed`,``),x.setAttribute(`data-closed`,``),p.removeAttribute(`data-open`),x.removeAttribute(`data-open`))},Ee=()=>{requestAnimationFrame(()=>{A&&document.contains(A)?c(A):b&&document.contains(b)&&c(b),A=null})},X=ee({element:x,onExitComplete:()=>{R||(L.restore(),x.hidden=!0,Ee())}}),Z=e=>{k!==e&&(e?(A=document.activeElement,k=!0,g(b,`expanded`,!0),L.mount(),x.hidden=!1,Y(`open`),X.enter(),D&&!I&&(re(),I=!0),Te(),P=!1,q(),G.start(),W(),G.update(),x.focus()):(k=!1,g(b,`expanded`,!1),Y(`closed`),q(),M=``,P=!1,I&&=(_(),!1),G.stop(),X.exit()),o(p,`dropdown-menu:change`,{open:k}),de?.(k))},Q=e=>{if(H(e))return;let t=e.dataset.value||e.textContent?.trim()||``;o(p,`dropdown-menu:select`,{value:t}),fe?.(t),he&&Z(!1)},De=e=>{let t=B.length;if(t!==0)switch(e.key){case`ArrowDown`:e.preventDefault(),P=!0,K(j===-1?0:(j+1)%t);break;case`ArrowUp`:e.preventDefault(),P=!0,K(j===-1?t-1:(j-1+t)%t);break;case`Home`:e.preventDefault(),P=!0,K(0);break;case`End`:e.preventDefault(),P=!0,K(t-1);break;case`Enter`:case` `:e.preventDefault(),j>=0&&Q(B[j]);break;case`Tab`:Z(!1);break;default:e.key.length===1&&!e.ctrlKey&&!e.metaKey&&!e.altKey&&(e.preventDefault(),Oe(e.key.toLowerCase()))}},Oe=e=>{N&&clearTimeout(N),N=setTimeout(()=>{M=``},500),M+=e;let t=B.findIndex(e=>(e.textContent?.trim().toLowerCase()||``).startsWith(M));if(t===-1&&M.length===1){let n=j+1;for(let r=0;r<B.length;r++){let i=(n+r)%B.length;if((B[i].textContent?.trim().toLowerCase()||``).startsWith(e)){t=i;break}}}t!==-1&&(P=!0,K(t))};g(b,`expanded`,!1),x.hidden=!0,Y(`closed`),F.push(h(b,`click`,()=>Z(!k)),h(b,`keydown`,e=>{(e.key===`Enter`||e.key===` `||e.key===`ArrowDown`)&&!k&&(e.preventDefault(),Z(!0))})),F.push(h(x,`keydown`,De),h(x,`click`,e=>{let t=e.target.closest?.(`[data-slot="dropdown-menu-item"]`);t&&Q(t)}),h(x,`pointermove`,e=>{if(!O||!U(e))return;let t=e.target.closest?.(`[data-slot="dropdown-menu-item"]`);if(!(P&&(P=!1,t&&V.get(t)===j)))if(t&&!H(t)){let e=V.get(t);e!==void 0&&e!==j&&K(e,!0)}else J()}),h(x,`pointerleave`,e=>{!O||!U(e)||P||J()})),F.push(r({root:p,isOpen:()=>k,onDismiss:()=>Z(!1),closeOnClickOutside:pe,closeOnEscape:me})),F.push(h(p,`dropdown-menu:set`,e=>{let t=e.detail,n;t?.open===void 0?t?.value!==void 0&&(n=t.value):n=t.open,typeof n==`boolean`&&Z(n)}));let $={open:()=>Z(!0),close:()=>Z(!1),toggle:()=>Z(!k),get isOpen(){return k},destroy:()=>{R=!0,N&&clearTimeout(N),G.stop(),X.cleanup(),L.cleanup(),I&&=(_(),!1),F.forEach(e=>e()),F.length=0,e(p,v,$)}};return oe(p,v,$),ue&&Z(!0),$}function b(e=document){let t=[];for(let n of p(e,`dropdown-menu`))m(n,v)||t.push(y(n));return t}export{b as create,y as createDropdownMenu};
1
+ import{clearRootBinding as e,computeFloatingPosition as t,computeFloatingTransformOrigin as n,containsWithPortals as r,createDismissLayer as i,createPortalLifecycle as a,createPositionSync as o,createPresenceLifecycle as ee,emit as s,ensureId as te,ensureItemVisibleInContainer as ne,focusElement as c,getDataBool as l,getDataEnum as u,getDataNumber as d,getDataString as re,getPart as f,getRoots as p,hasRootBinding as m,lockScroll as ie,measurePopupContentRect as ae,on as h,reuseRootBinding as oe,setAria as g,setRootBinding as se,unlockScroll as ce}from"@data-slot/core";const le=[`top`,`right`,`bottom`,`left`],ue=[`start`,`center`,`end`],_=`@data-slot/dropdown-menu`,de=`[data-slot="dropdown-menu-item"], [data-slot="dropdown-menu-radio-item"], [data-slot="dropdown-menu-checkbox-item"]`,fe=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),v=(e,t,n)=>{n?e.setAttribute(t,``):e.removeAttribute(t)},pe=(e,t)=>{if(e.length!==t.length)return!1;for(let n=0;n<e.length;n++)if(e[n]!==t[n])return!1;return!0},me=(e,t,n,r=!1)=>e.dispatchEvent(new CustomEvent(t,{bubbles:!0,cancelable:r,detail:n})),he=e=>{switch(e){case`radio`:return`menuitemradio`;case`checkbox`:return`menuitemcheckbox`;default:return`menuitem`}},ge=e=>{let t=e.getAttribute(`data-slot`);return t===`dropdown-menu-radio-item`?`radio`:t===`dropdown-menu-checkbox-item`?`checkbox`:`item`},_e=e=>{let t=e.dataset.value;if(t===void 0)return null;let n=t.trim();return n.length>0?n:null},ve=e=>e.type===`item`?e.value?e.value:e.el.textContent?.trim()??``:e.value??``,ye=e=>{if(e===void 0)return[];let t=e.trim();if(t.length===0)return[];try{let e=JSON.parse(t);return Array.isArray(e)?e.filter(e=>typeof e==`string`).map(e=>e.trim()).filter(e=>e.length>0):[]}catch{return[]}};function y(p,m={}){let y=oe(p,_,`[@data-slot/dropdown-menu] createDropdownMenu() called more than once for the same root. Returning the existing controller. Destroy it before rebinding with new options.`);if(y)return y;let b=f(p,`dropdown-menu-trigger`),x=f(p,`dropdown-menu-content`),be=f(p,`dropdown-menu-positioner`),S=be&&x&&be.contains(x)?be:null,xe=f(p,`dropdown-menu-portal`),Se=xe&&S&&xe.contains(S)?xe:null;if(!b||!x)throw Error(`DropdownMenu requires trigger and content slots`);let Ce=m.defaultOpen??l(p,`defaultOpen`)??!1,we=m.onOpenChange,Te=m.onSelect,Ee=m.onValueChange,De=m.onValuesChange,C=m.closeOnClickOutside??l(p,`closeOnClickOutside`)??!0,Oe=m.closeOnEscape??l(p,`closeOnEscape`)??!0,ke=m.closeOnSelect??l(p,`closeOnSelect`)??!0,Ae=(e,t)=>u(x,e,t)??(S?u(S,e,t):void 0)??u(p,e,t),w=e=>d(x,e)??(S?d(S,e):void 0)??d(p,e),je=e=>l(x,e)??(S?l(S,e):void 0)??l(p,e),Me=m.side??Ae(`side`,le)??`bottom`,Ne=m.align??Ae(`align`,ue)??`start`,Pe=m.sideOffset??w(`sideOffset`)??4,Fe=m.alignOffset??w(`alignOffset`)??0,Ie=m.avoidCollisions??je(`avoidCollisions`)??!0,Le=m.collisionPadding??w(`collisionPadding`)??8,T=m.lockScroll??l(p,`lockScroll`)??!0,Re=m.highlightItemOnHover??l(p,`highlightItemOnHover`)??!0,ze=fe(m,`defaultValue`),Be=fe(m,`defaultValues`),Ve=p.hasAttribute(`data-default-value`),He=p.hasAttribute(`data-default-values`),Ue=ze?m.defaultValue??null:Ve?re(p,`defaultValue`)??null:null,We=Be?m.defaultValues??[]:He?ye(re(p,`defaultValues`)):[],E=!1,D=null,O=[],k=null,A=null,j=``,M=null,N=!1,P=!1,Ge=!1,F=null,I=[],L=a({content:x,root:p,wrapperSlot:S?void 0:`dropdown-menu-positioner`,container:S??void 0,mountTarget:S?Se??S:void 0}),R=[],z=[],B=new Map,Ke=e=>e.hasAttribute(`disabled`)||e.hasAttribute(`data-disabled`)||e.getAttribute(`aria-disabled`)===`true`,qe=e=>e.pointerType!==`touch`,Je=e=>e instanceof Element?e.closest(de):null,V=e=>e?R.find(t=>t.el===e)??null:null,H=()=>R.filter(e=>e.type===`radio`),U=()=>R.filter(e=>e.type===`checkbox`),Ye=(e,t)=>e.find(e=>e.type===`radio`&&e.value===t)??null,W=e=>e?e.type===`item`?ve(e):e.value:null,Xe=e=>(e.type===`radio`||e.type===`checkbox`)&&e.value===null,G=e=>Ke(e.el)||Xe(e),K=e=>H().find(t=>t.value===e)??null,Ze=e=>z.find(t=>W(t)===e)??null,Qe=(e,t,n=U())=>{let r=new Set(e),i=new Set(t),a=null,o=null,ee=null;for(let e of n){if(e.type!==`checkbox`)continue;let t=e.value;if(!t)continue;let n=r.has(t),s=i.has(t);if(n!==s){if(a!==null)return{changedValue:null,checked:null,item:null};a=t,o=s,ee=e.el}}return{changedValue:a,checked:o,item:ee}},q=(e,t)=>{let n=U().filter(e=>e.value!==null);if(n.length===0)return null;if(e.length===0)return[];let r=new Set(e.filter(e=>typeof e==`string`).map(e=>e.trim()).filter(e=>e.length>0));if(r.size===0)return t===`init`?[]:null;let i=[];for(let e of n)e.value&&r.has(e.value)&&i.push(e.value);return i.length===0?t===`init`?[]:null:i},J=()=>{for(let e of R){let t=G(e);if(e.el.setAttribute(`role`,he(e.type)),e.el.tabIndex=-1,t?e.el.setAttribute(`aria-disabled`,`true`):e.el.removeAttribute(`aria-disabled`),e.type===`radio`){let t=e.value!==null&&D===e.value;v(e.el,`data-checked`,t),g(e.el,`checked`,e.value===null?null:t)}else if(e.type===`checkbox`){let t=e.value!==null&&O.includes(e.value);v(e.el,`data-checked`,t),g(e.el,`checked`,e.value===null?null:t)}else e.el.removeAttribute(`data-checked`),e.el.removeAttribute(`aria-checked`)}H().length>0&&D!==null?p.setAttribute(`data-value`,D):p.removeAttribute(`data-value`)},$e=()=>{for(let e of R)v(e.el,`data-highlighted`,e.el===k)},Y=({source:e=`programmatic`,emitSelectionInvalidation:t=!1}={})=>{let n=R,r=D,i=[...O];R=Array.from(x.querySelectorAll(de)).map(e=>({el:e,type:ge(e),value:_e(e)}));let a=r!==null&&K(r)?r:null,o=i.length>0?q(i,`init`)??[]:[];if(D=a,O=o,z=R.filter(e=>!G(e)),B=new Map(z.map((e,t)=>[e.el,t])),k&&!B.has(k)&&(k=null),J(),$e(),t&&(r!==D&&nt({value:D,previousValue:r,item:D===null?null:K(D)?.el??null,previousItem:r===null?null:Ye(n,r)?.el??null,source:e}),!pe(i,O))){let t=Qe(i,O,n.filter(e=>e.type===`checkbox`));rt({values:[...O],previousValues:i,changedValue:t.changedValue,checked:t.checked,item:t.item,source:e})}},et=e=>{s(p,`dropdown-menu:open-change`,e),s(p,`dropdown-menu:change`,e),we?.(e.open)},tt=e=>{s(p,`dropdown-menu:highlight-change`,e)},nt=e=>{s(p,`dropdown-menu:value-change`,e),Ee?.(e.value)},rt=e=>{s(p,`dropdown-menu:values-change`,e),De?.([...e.values])},it=()=>{let e=L.container,r=p.ownerDocument.defaultView??window,i=b.getBoundingClientRect(),a=t({anchorRect:i,contentRect:ae(x),side:Me,align:Ne,sideOffset:Pe,alignOffset:Fe,avoidCollisions:Ie,collisionPadding:Le}),o=n({side:a.side,align:a.align,anchorRect:i,popupX:a.x,popupY:a.y});T?(e.style.position=`fixed`,e.style.top=`0px`,e.style.left=`0px`,e.style.transform=`translate3d(${a.x}px, ${a.y}px, 0)`):(e.style.position=`absolute`,e.style.top=`0px`,e.style.left=`0px`,e.style.transform=`translate3d(${a.x+r.scrollX}px, ${a.y+r.scrollY}px, 0)`),e.style.setProperty(`--transform-origin`,o),e.style.willChange=`transform`,e.style.margin=`0`,x.setAttribute(`data-side`,a.side),x.setAttribute(`data-align`,a.align),e!==x&&(e.setAttribute(`data-side`,a.side),e.setAttribute(`data-align`,a.align))},X=o({observedElements:[b,x],isActive:()=>E,ancestorScroll:T,onUpdate:it}),at=()=>{requestAnimationFrame(()=>{A&&document.contains(A)?c(A):document.contains(b)&&c(b),A=null})},ot=ee({element:x,onExitComplete:()=>{Ge||(L.restore(),x.hidden=!0,at())}}),Z=e=>{p.setAttribute(`data-state`,e),x.setAttribute(`data-state`,e),e===`open`?(p.setAttribute(`data-open`,``),x.setAttribute(`data-open`,``),p.removeAttribute(`data-closed`),x.removeAttribute(`data-closed`)):(p.setAttribute(`data-closed`,``),x.setAttribute(`data-closed`,``),p.removeAttribute(`data-open`),x.removeAttribute(`data-open`))},Q=(e,{source:t,focus:n=!0,focusContentOnClear:r=!1})=>{if(e&&!B.has(e))return!1;let i=k;return i===e?(e&&n?(ne(e,x),c(e)):!e&&r&&c(x),!1):(k=e,$e(),e?(ne(e,x),n&&c(e)):r&&c(x),tt({value:W(V(e)),previousValue:W(V(i)),item:e,previousItem:i,source:t}),!0)},st=(e,t,n=!0)=>{if(Y({source:t,emitSelectionInvalidation:n}),H().length===0)return!1;let r=e===null?null:K(e);if(e!==null&&!r||D===e)return!1;let i=D,a=i===null?null:K(i);return D=e,J(),n&&nt({value:D,previousValue:i,item:r?.el??null,previousItem:a?.el??null,source:t}),!0},ct=(e,t,n=!0)=>{Y({source:t,emitSelectionInvalidation:n});let r=q(e,n?`set`:`init`);if(r===null||pe(O,r))return!1;let i=[...O],a=Qe(i,r);return O=r,J(),n&&rt({values:[...O],previousValues:i,changedValue:a.changedValue,checked:a.checked,item:a.item,source:t}),!0},lt=()=>{if(Y(),ze||Ve)Ue===null?D=null:st(Ue,`programmatic`,!1);else for(let e of H())if(e.value!==null&&l(e.el,`defaultChecked`)){D=e.value;break}O=Be||He?q(We,`init`)??[]:q(U().filter(e=>e.value!==null&&l(e.el,`defaultChecked`)).map(e=>e.value),`init`)??[],J()},$=(e,{source:t,reason:n})=>{if(E===e)return;F=null;let r=E;e?(A=document.activeElement,E=!0,g(b,`expanded`,!0),L.mount(),x.hidden=!1,Z(`open`),ot.enter(),T&&!P&&(ie(),P=!0),Y({source:t===`restore`?`restore`:`programmatic`,emitSelectionInvalidation:t!==`init`}),N=!1,j=``,X.start(),it(),X.update(),c(x)):(E=!1,g(b,`expanded`,!1),Z(`closed`),k&&Q(null,{source:t===`init`?`programmatic`:t,focus:!1,focusContentOnClear:!1}),j=``,N=!1,P&&=(ce(),!1),X.stop(),ot.exit()),et({open:E,previousOpen:r,source:t,reason:n})},ut=(e,t)=>{let n={source:e,reason:t};F=n,queueMicrotask(()=>{F===n&&(F=null)})},dt=(e,t)=>{if(G(e))return;let n=W(e);if(n===null)return;let r;if(e.type===`radio`?r=!0:e.type===`checkbox`&&e.value!==null&&(r=!O.includes(e.value)),me(p,`dropdown-menu:select`,{value:n,item:e.el,itemType:e.type,source:t,checked:r},!0)){if(Te?.(n),e.type===`radio`)st(e.value,t,!0);else if(e.type===`checkbox`&&e.value!==null){let n=new Set(O);n.has(e.value)?n.delete(e.value):n.add(e.value),ct([...n],t,!0)}ke&&$(!1,{source:t,reason:`select`})}},ft=e=>{M&&clearTimeout(M),M=setTimeout(()=>{j=``},500),j+=e;let t=z.findIndex(e=>(e.el.textContent?.trim().toLowerCase()??``).startsWith(j));if(t===-1&&j.length===1){let n=k?(B.get(k)??-1)+1:0;for(let r=0;r<z.length;r++){let i=(n+r)%z.length;if((z[i]?.el.textContent?.trim().toLowerCase()??``).startsWith(e)){t=i;break}}}t!==-1&&(N=!0,Q(z[t]?.el??null,{source:`keyboard`,focus:!0}))},pt=e=>{let t=e.source??`programmatic`;if(e.value!==void 0&&st(e.value,t,!0),e.values!==void 0&&ct(e.values,t,!0),e.open!==void 0&&$(e.open,{source:t,reason:`programmatic`}),e.highlightedValue!==void 0){if(!E)return;if(e.highlightedValue===null)Q(null,{source:t,focus:!1,focusContentOnClear:!0});else{let n=Ze(e.highlightedValue);n&&Q(n.el,{source:t,focus:!0})}}},mt=te(b,`dropdown-menu-trigger`),ht=te(x,`dropdown-menu-content`);b.setAttribute(`aria-haspopup`,`menu`),b.setAttribute(`aria-controls`,ht),x.setAttribute(`role`,`menu`),x.setAttribute(`aria-labelledby`,mt),x.tabIndex=-1,g(b,`expanded`,!1),x.hidden=!0,Z(`closed`),lt(),I.push(h(b,`click`,()=>{$(!E,{source:`pointer`,reason:`trigger`})}),h(b,`keydown`,e=>{(e.key===`Enter`||e.key===` `||e.key===`ArrowDown`)&&!E&&(e.preventDefault(),$(!0,{source:`keyboard`,reason:`trigger`}))})),I.push(h(x,`keydown`,e=>{if(e.key===`Tab`){$(!1,{source:`keyboard`,reason:`tab`});return}let t=z.length;if(t!==0)switch(e.key){case`ArrowDown`:e.preventDefault(),N=!0,Q(z[k?((B.get(k)??-1)+1)%t:0]?.el??null,{source:`keyboard`,focus:!0});break;case`ArrowUp`:e.preventDefault(),N=!0,Q(z[k?(B.get(k)-1+t)%t:t-1]?.el??null,{source:`keyboard`,focus:!0});break;case`Home`:e.preventDefault(),N=!0,Q(z[0]?.el??null,{source:`keyboard`,focus:!0});break;case`End`:e.preventDefault(),N=!0,Q(z[t-1]?.el??null,{source:`keyboard`,focus:!0});break;case`Enter`:case` `:if(e.preventDefault(),k){let e=V(k);e&&dt(e,`keyboard`)}break;default:e.key.length===1&&!e.ctrlKey&&!e.metaKey&&!e.altKey&&(e.preventDefault(),ft(e.key.toLowerCase()))}}),h(x,`click`,e=>{let t=V(Je(e.target));t&&dt(t,`pointer`)}),h(x,`pointermove`,e=>{if(!Re||!qe(e))return;let t=Je(e.target);N&&(N=!1,t&&t===k)||(t&&B.has(t)?Q(t,{source:`pointer`,focus:!0}):k&&Q(null,{source:`pointer`,focus:!1,focusContentOnClear:!0}))}),h(x,`pointerleave`,e=>{!Re||!qe(e)||N||!k||Q(null,{source:`pointer`,focus:!1,focusContentOnClear:!0})}));let gt=p.ownerDocument??document;I.push(h(gt,`pointerdown`,e=>{if(!E||!C||e.pointerType===`touch`)return;let t=e.target;r(p,t)||ut(`pointer`,`outside`)},{capture:!0}),h(gt,`click`,e=>{if(!E||!C)return;let t=e.target;r(p,t)||ut(`pointer`,`outside`)},{capture:!0}),h(gt,`keydown`,e=>{!E||!Oe||e.key!==`Escape`||e.defaultPrevented||ut(`keyboard`,`escape`)},{capture:!0})),I.push(i({root:p,isOpen:()=>E,onDismiss:()=>{let e=F;if(F=null,e?.reason===`escape`){$(!1,{source:e.source,reason:`escape`});return}$(!1,{source:e?.source??`pointer`,reason:e?.reason??`outside`})},closeOnClickOutside:C,closeOnEscape:Oe})),I.push(h(p,`dropdown-menu:set`,e=>{let t=e.detail;if(!t||typeof t!=`object`)return;let n={source:t.source===`restore`||t.source===`programmatic`?t.source:void 0};t.open!==void 0&&(n.open=t.open),t.values!==void 0&&(n.values=Array.isArray(t.values)?t.values.filter(e=>typeof e==`string`):void 0),t.highlightedValue!==void 0&&(n.highlightedValue=t.highlightedValue===null||typeof t.highlightedValue==`string`?t.highlightedValue:void 0),t.value!==void 0&&(typeof t.value==`boolean`&&t.open===void 0?n.open=t.value:(t.value===null||typeof t.value==`string`)&&(n.value=t.value)),pt(n)}));let _t={open:()=>$(!0,{source:`programmatic`,reason:`programmatic`}),close:()=>$(!1,{source:`programmatic`,reason:`programmatic`}),toggle:()=>$(!E,{source:`programmatic`,reason:`programmatic`}),set:e=>{pt(e)},get isOpen(){return E},get value(){return D},get values(){return[...O]},get highlightedValue(){return W(V(k))},destroy:()=>{Ge=!0,M&&clearTimeout(M),X.stop(),ot.cleanup(),L.cleanup(),P&&=(ce(),!1),I.forEach(e=>e()),I.length=0,e(p,_,_t)}};return se(p,_,_t),Ce&&$(!0,{source:`init`,reason:`init`}),_t}function b(e=document){let t=[];for(let n of p(e,`dropdown-menu`))m(n,_)||t.push(y(n));return t}export{b as create,y as createDropdownMenu};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@data-slot/dropdown-menu",
3
- "version": "0.2.163",
3
+ "version": "0.2.165",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "main": "./dist/index.cjs",
@@ -35,6 +35,6 @@
35
35
  ],
36
36
  "license": "MIT",
37
37
  "dependencies": {
38
- "@data-slot/core": "0.2.163"
38
+ "@data-slot/core": "0.2.165"
39
39
  }
40
40
  }