@acusti/dropdown 1.0.0-rc.3 → 1.0.0-rc.5

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
@@ -33,9 +33,9 @@ The three primary design goals for the existence of this component:
33
33
  directly)
34
34
  5. To combine dropdowns into a menubar, wrap them in
35
35
  [`<Menubar>`](#menubar)
36
- 6. To style your dropdowns, use CSS; there are a
37
- [collection of CSS custom properties](https://github.com/acusti/uikit/blob/main/packages/dropdown/src/Dropdown.css)
38
- used internally to style them if that works best for you, or just
36
+ 6. To style your dropdowns, use CSS; there is a
37
+ [collection of CSS custom properties](#css-custom-properties) used
38
+ internally to style them if that works best for you, or just
39
39
  override the minimal default CSS as appropriate
40
40
  3. **Lightweight bundle size** with the bare minimum of dependencies (see
41
41
  minzipped size above)
@@ -53,6 +53,9 @@ npm install @acusti/dropdown
53
53
  yarn add @acusti/dropdown
54
54
  ```
55
55
 
56
+ Requires React 19.2 or later (`react` and `react-dom` are peer
57
+ dependencies).
58
+
56
59
  ## Quick Start
57
60
 
58
61
  ```tsx
@@ -144,8 +147,9 @@ Internally, the dropdown renders:
144
147
 
145
148
  Custom padding and overflow styling belongs on the content region, not the
146
149
  outer shell. Note that `.uktdropdown-content` already applies default
147
- padding (see the `--uktdd-body-pad-*` variables below), so your body
148
- element does **not** need its own padding:
150
+ padding (the `--uktdd-body-pad-*` custom properties, listed under
151
+ [CSS custom properties](#css-custom-properties)), so your body element does
152
+ **not** need its own padding:
149
153
 
150
154
  ```tsx
151
155
  // ✗ Don’t double-pad — the content region already has padding
@@ -155,7 +159,7 @@ element does **not** need its own padding:
155
159
  </Dropdown>
156
160
 
157
161
  // ✓ Override default padding via CSS variables if needed
158
- // .my-dropdown { --uktdd-body-pad-top: 16px; /* etc */ }
162
+ // .my-dropdown { --uktdd-body-pad-block-start: 16px; /* etc */ }
159
163
  ```
160
164
 
161
165
  For the most reliable anchor-positioning behavior:
@@ -262,11 +266,18 @@ type Props = {
262
266
  */
263
267
  allowEmpty?: boolean;
264
268
  /**
265
- * Can take a single React element or exactly two renderable children.
266
- * - Single child: The dropdown body (trigger will be auto-generated button)
267
- * - Two children: [trigger, body]
269
+ * Either a single React element (the dropdown body; the trigger is a
270
+ * generated button, or a generated search input when isSearchable) or
271
+ * exactly two renderable children: the trigger, then the body.
272
+ */
273
+ children:
274
+ | ReactElement
275
+ | [ReactNode, ReactNode]
276
+ | readonly [ReactNode, ReactNode];
277
+ /**
278
+ * Applied to the dropdown root element. For a nested (submenu) Dropdown,
279
+ * applied to its item element instead.
268
280
  */
269
- children: ReactNode | [ReactNode, ReactNode];
270
281
  className?: string;
271
282
  /**
272
283
  * Prevents the dropdown from opening via user interaction: pointer,
@@ -280,21 +291,38 @@ type Props = {
280
291
  */
281
292
  disabled?: boolean;
282
293
  /**
283
- * Whether the dropdown contains items that can be selected.
284
- * Defaults to true if children contain elements with data-ukt-item or data-ukt-value.
294
+ * Whether the body is a list of selectable items (the default) or
295
+ * arbitrary interactive content such as a form. hasItems={false} turns
296
+ * off item selection and item keyboard navigation, gives the popup
297
+ * role="dialog" (unless isSearchable), and keeps it open on clicks
298
+ * inside the body. Defaults to true and isn’t inferred from the children.
285
299
  */
286
300
  hasItems?: boolean;
287
301
  /**
288
- * Whether the dropdown should be open when first mounted.
302
+ * Renders the dropdown already open on mount. Uncontrolled: it sets only
303
+ * the initial state.
289
304
  */
290
305
  isOpenOnMount?: boolean;
291
306
  /**
292
- * Whether the dropdown should include a search input for filtering options.
307
+ * Renders the trigger as a text input (a combobox) that filters the items
308
+ * as the user types, and the popup as a listbox.
293
309
  */
294
310
  isSearchable?: boolean;
295
311
  /**
296
- * Whether the dropdown should remain open after selecting an item.
297
- * Useful for multi-select scenarios.
312
+ * For a nested (submenu) Dropdown, its item element. When unset, it is
313
+ * an <li> inside a <ul>, <ol>, or <menu> and a <div> anywhere else,
314
+ * decided by checking its container once it’s in the DOM. Setting this
315
+ * skips that check, so the item mounts once and server output matches
316
+ * the client; recommended for a body that isn’t a list, which otherwise
317
+ * pays for the check on every open. Only a submenu Dropdown (one nested
318
+ * in a dropdown where hasItems is true) reads it; anywhere else it is
319
+ * ignored, with a warning.
320
+ */
321
+ itemAs?: 'div' | 'li';
322
+ /**
323
+ * Whether the dropdown stays open after an item is submitted, e.g. for a
324
+ * multi-select. Defaults to !hasItems: a menu closes on submit, while a
325
+ * hasItems={false} dialog stays open.
298
326
  */
299
327
  keepOpenOnSubmit?: boolean;
300
328
  /**
@@ -303,19 +331,46 @@ type Props = {
303
331
  */
304
332
  label?: ReactNode;
305
333
  /**
306
- * Name attribute for the search input (requires isSearchable: true).
334
+ * The generated search input’s name. Only used when isSearchable is true.
307
335
  */
308
336
  name?: string;
309
337
  /**
310
- * Called when the active (highlighted) item changes. Receives the
311
- * same payload as onSubmitItem.
338
+ * Called whenever the highlighted item changes, with the same payload as
339
+ * onSubmitItem.
312
340
  */
313
341
  onActiveItem?: (payload: Item) => void;
342
+ /**
343
+ * Applied to the dropdown root element. Ignored on a nested (submenu)
344
+ * Dropdown, which has no root element of its own.
345
+ */
314
346
  onClick?: (event: React.MouseEvent<HTMLElement>) => unknown;
347
+ /**
348
+ * Called after the dropdown closes. For a nested (submenu) Dropdown,
349
+ * called when its submenu closes.
350
+ */
315
351
  onClose?: () => unknown;
352
+ /**
353
+ * Applied to the dropdown root element. Ignored on a nested (submenu)
354
+ * Dropdown, which has no root element of its own.
355
+ */
316
356
  onMouseDown?: (event: React.MouseEvent<HTMLElement>) => unknown;
357
+ /**
358
+ * Applied to the dropdown root element. Ignored on a nested (submenu)
359
+ * Dropdown, which has no root element of its own.
360
+ */
317
361
  onMouseUp?: (event: React.MouseEvent<HTMLElement>) => unknown;
362
+ /**
363
+ * Called after the dropdown opens (on mount, when isOpenOnMount). For a
364
+ * nested (submenu) Dropdown, called when its submenu opens.
365
+ */
318
366
  onOpen?: () => unknown;
367
+ /**
368
+ * Called when an item is submitted (click, Enter, or Space). Parent items
369
+ * (submenus) disclose rather than submit, so this fires for leaf items
370
+ * only. For a nested (submenu) Dropdown, fires for submissions within its
371
+ * subtree only.
372
+ */
373
+ onSubmitItem?: (payload: Item) => void;
319
374
  /**
320
375
  * Opens the dropdown when the pointer hovers the trigger, and closes it a
321
376
  * short moment after the pointer leaves the trigger and body entirely (the
@@ -325,63 +380,158 @@ type Props = {
325
380
  */
326
381
  openOnHover?: boolean;
327
382
  /**
328
- * Called when an item is selected. The payload includes:
329
- * - element: The DOM element that was clicked
330
- * - event: The click or keyboard event
331
- * - label: The visible text of the item
332
- * - value: The value attribute or text content
383
+ * The generated search input’s placeholder. Only used when isSearchable is true.
333
384
  */
334
- onSubmitItem?: (payload: Item) => void;
385
+ placeholder?: string;
335
386
  /**
336
- * Placeholder text for the search input (requires isSearchable: true).
387
+ * Applied to the dropdown root element (for a nested (submenu) Dropdown,
388
+ * its item element). Also accepts the component’s CSS custom
389
+ * properties (e.g. `--uktdd-body-min-width`).
337
390
  */
338
- placeholder?: string;
339
- style?: React.CSSProperties;
391
+ style?: StyleWithCustomProperties;
340
392
  /**
341
- * Tab index for the search input (requires isSearchable: true).
393
+ * The generated search input’s tabIndex. Only used when isSearchable is true.
342
394
  */
343
395
  tabIndex?: number;
344
396
  /**
345
397
  * The dropdown’s controlled value. Pass a bare identifier when an item’s
346
398
  * stored value and its displayed label are the same, or a { label, value }
347
399
  * pair when they differ (e.g. a human-readable label shown for a stored
348
- * id) — the same { label, value } shape onSubmitItem reports back. Used for
349
- * change detection (skipping onSubmitItem when the already-selected item is
350
- * re-submitted); the label is shown as the search input’s value when
351
- * isSearchable is true. A bare identifier resolves to its label from the
352
- * matching child’s data-ukt-value in the body, so children whose value
353
- * and label differ need no explicit label.
400
+ * id) — the same { label, value } shape onSubmitItem reports back. The
401
+ * value is used for change detection (skipping onSubmitItem when the
402
+ * already-selected item is re-submitted); the label is shown as the search
403
+ * input’s value when isSearchable is true. A bare identifier is resolved
404
+ * to its label from the matching child’s data-ukt-value in the body — so
405
+ * children whose value and label differ need no explicit label; a
406
+ * { label, value } pair states it.
354
407
  */
355
408
  value?: ItemValue | string;
356
409
  };
357
410
  ```
358
411
 
412
+ `Props` is also exported as `DropdownProps`, the name that pairs with
413
+ `MenubarProps`. `StyleWithCustomProperties` is `React.CSSProperties`
414
+ extended to accept the component’s `--uktdd-*` custom properties, and is
415
+ the type of both components’ `style` prop.
416
+
359
417
  ### Item Types
360
418
 
361
419
  Both types are exported alongside the component:
362
420
 
363
421
  ```ts
364
422
  /**
365
- * A { label, value } pair naming an item: value is the stored value (the
366
- * item’s data-ukt-value) and label is its displayed text. Accepted by the
423
+ * A { label, value } pair naming an item: `value` is the stored value (the
424
+ * item’s data-ukt-value) and `label` is its displayed text. Accepted by the
367
425
  * value prop when an item’s value and label differ; also the shape of an
368
426
  * Item’s path entries.
369
427
  */
370
428
  type ItemValue = { label: string; value: string };
371
429
 
372
430
  type Item = {
431
+ /**
432
+ * The item element. Null when the submitted value came from a text
433
+ * input rather than an item (a created value with allowCreate, or an
434
+ * empty value with allowEmpty).
435
+ */
373
436
  element: HTMLElement | null;
437
+ /** The mouse, keyboard, or input event that activated or submitted the item. */
374
438
  event: Event | React.SyntheticEvent<HTMLElement>;
439
+ /** The item’s visible text (or the input’s text when element is null). */
375
440
  label: string;
376
441
  /**
377
442
  * Ancestor parent items from the root level down to the item’s
378
443
  * immediate parent. Empty for top-level items.
379
444
  */
380
445
  path: Array<ItemValue>;
446
+ /** The item’s data-ukt-value, or its label if it has none. */
381
447
  value: string;
382
448
  };
383
449
  ```
384
450
 
451
+ ### CSS custom properties
452
+
453
+ Every `--uktdd-*` custom property the component reads, with its default.
454
+ Set them on the dropdown’s root (a class, or the `style` prop, which
455
+ accepts them) or on an ancestor to theme a whole subtree. Anything not
456
+ listed here — the `--uktdd-anchor` and `--uktdd-submenu-anchor` anchor
457
+ names, and the `data-ukt-generated-id` attribute — is internal.
458
+
459
+ Colors (see [Color Scheme](#color-scheme)):
460
+
461
+ | Property | Default |
462
+ | ----------------------------------------- | ---------------------------------------------------- |
463
+ | `--uktdd-color-scheme` | `inherit` |
464
+ | `--uktdd-body-bg-color` | `Canvas` |
465
+ | `--uktdd-body-color` | `CanvasText` |
466
+ | `--uktdd-body-bg-color-hover` | `Highlight` |
467
+ | `--uktdd-body-color-hover` | `HighlightText` |
468
+ | `--uktdd-body-bg-color-selected` | `color-mix(in oklab, currentColor 6%, transparent)` |
469
+ | `--uktdd-body-bg-color-path` | 12% of the body color mixed into its background |
470
+ | `--uktdd-body-color-path` | `currentColor` |
471
+ | `--uktdd-body-border-color` | `color-mix(in oklab, currentColor 15%, transparent)` |
472
+ | `--uktdd-body-box-shadow` | `0 8px 18px rgba(0, 0, 0, 0.25)` |
473
+ | `--uktdd-menubar-trigger-bg-color-active` | `color-mix(in oklab, currentColor 12%, transparent)` |
474
+
475
+ Typography, sizing, and spacing:
476
+
477
+ | Property | Default |
478
+ | ------------------------------- | ----------------------------------------- |
479
+ | `--uktdd-font-family` | the system UI font stack |
480
+ | `--uktdd-body-buffer` | `10px` (viewport inset for the max sizes) |
481
+ | `--uktdd-body-min-height` | `30px` |
482
+ | `--uktdd-body-max-height` | `calc(100dvh - var(--uktdd-body-buffer))` |
483
+ | `--uktdd-body-min-width` | `min(50px, 100%)` |
484
+ | `--uktdd-body-max-width` | `calc(100dvw - var(--uktdd-body-buffer))` |
485
+ | `--uktdd-body-pad-block-start` | `0.5em` |
486
+ | `--uktdd-body-pad-block-end` | `0.5em` |
487
+ | `--uktdd-body-pad-inline-start` | `0.6875em` |
488
+ | `--uktdd-body-pad-inline-end` | `0.6875em` |
489
+ | `--uktdd-body-gap` | `0px` (trigger ↔ body) |
490
+ | `--uktdd-submenu-gap` | `0px` (parent item ↔ submenu) |
491
+ | `--uktdd-label-gap` | `0.625em` (`props.label` text ↔ trigger) |
492
+
493
+ Placement (see
494
+ [Placement Customization](#placement-customization-with-css-variables) and
495
+ [Submenu placement and styling](#submenu-placement-and-styling)):
496
+
497
+ | Property | Default |
498
+ | --------------------------------------- | -------------------------------------------------------- |
499
+ | `--uktdd-body-position-area` | `block-end span-inline-end` |
500
+ | `--uktdd-body-position-try-fallback-1` | `--uktdd-top-start` |
501
+ | `--uktdd-body-position-try-fallback-2` | `--uktdd-bottom-end` |
502
+ | `--uktdd-body-fill-fallbacks` | `--uktdd-fill-bottom, --uktdd-fill-top` |
503
+ | `--uktdd-submenu-position-area` | `inline-end span-block-end` |
504
+ | `--uktdd-submenu-position-try-fallback` | `--uktdd-submenu-inline-start` |
505
+ | `--uktdd-submenu-fill-fallbacks` | `--uktdd-submenu-fill, --uktdd-submenu-fill flip-inline` |
506
+
507
+ The `@position-try` placements the component ships, for use in the fallback
508
+ slots above: `--uktdd-top-start`, `--uktdd-top-end`,
509
+ `--uktdd-bottom-start`, `--uktdd-bottom-end`,
510
+ `--uktdd-submenu-inline-start`, the fills `--uktdd-fill-bottom`,
511
+ `--uktdd-fill-top`, `--uktdd-fill`, `--uktdd-fill-cover`, and
512
+ `--uktdd-submenu-fill`, and the no-op `--uktdd-noop`.
513
+
514
+ ### Class names and data attributes
515
+
516
+ The DOM the component renders is styleable by these class names, which are
517
+ part of the public API along with the `data-ukt-*` attributes:
518
+
519
+ - `.uktdropdown` — the root, with the state classes `.is-open`,
520
+ `.is-searchable`, and `.is-disabled`
521
+ - `.uktdropdown-trigger` — the generated trigger (a custom trigger keeps
522
+ its own classes)
523
+ - `.uktdropdown-label` and `.uktdropdown-label-text` — the wrapper and text
524
+ rendered for `props.label`
525
+ - `.uktdropdown-body` — the anchored popover shell, with `.has-items`
526
+ unless `hasItems={false}`
527
+ - `.uktdropdown-content` — the padded, scrollable region inside the body
528
+ - `.uktmenubar` — the [`Menubar`](#menubar) root
529
+ - `data-ukt-item` and `data-ukt-value` mark items, `data-ukt-submenu` marks
530
+ a [submenu](#the-data-ukt-submenu-protocol), and `data-ukt-active` marks
531
+ the highlighted item at each open level (the styling hook for the
532
+ highlight, alongside `aria-expanded` on open parent items and
533
+ `aria-selected` on the selected option)
534
+
385
535
  ## Usage Examples
386
536
 
387
537
  ### Basic List Dropdown
@@ -870,6 +1020,18 @@ Like items, submenus are ultimately declared in the DOM. A nested
870
1020
  </li>
871
1021
  ```
872
1022
 
1023
+ The parent item is an `<li>` when the nested `Dropdown` sits inside a
1024
+ `<ul>`, `<ol>`, or `<menu>`, and a `<div>` anywhere else (a body built from
1025
+ `<div data-ukt-item>` items, say), so once mounted it is valid HTML in
1026
+ either kind of body. The container is checked when the item enters the DOM,
1027
+ and for a non-list container the item then remounts as a `<div>` before
1028
+ paint — on every open, since the body unmounts on close. For a non-list
1029
+ body, pass `itemAs="div"` to name the element up front and skip the check:
1030
+ the item mounts once, the submenu body’s own mount effects run once, and
1031
+ server output is valid too (without `itemAs`, a server-rendered
1032
+ `isOpenOnMount` dropdown still ships the `<li>`). `className` and `style`
1033
+ on the nested `Dropdown` land on that element either way.
1034
+
873
1035
  You can author that markup directly instead of nesting a `Dropdown`
874
1036
  component; the two forms behave identically because the component form
875
1037
  compiles to the attribute form. Rules for direct authoring:
@@ -893,14 +1055,19 @@ Most props keep their meaning, scoped to the submenu:
893
1055
  - `onSubmitItem`: fires for submissions within that subtree only
894
1056
  - `onActiveItem`, `onOpen`, `onClose`: scoped to the submenu
895
1057
  - `disabled`: disables the parent item
896
- - `className`/`style`: applied to the parent item element
1058
+ - `className`/`style`: applied to the item element
1059
+ - `itemAs`: the item element (`'li'` or `'div'`), skipping the container
1060
+ check described above and recommended for a non-list body; submenu-only,
1061
+ and ignored (with a warning) anywhere else, including a nested
1062
+ `hasItems={false}` `Dropdown`
897
1063
 
898
1064
  Props that only make sense at the top level (`allowCreate`, `allowEmpty`,
899
1065
  `isOpenOnMount`, `isSearchable`, `keepOpenOnSubmit`, `name`, `openOnHover`,
900
- `placeholder`, `tabIndex`, `value`) are ignored on a nested dropdown and
901
- warn (unconditionally, matching the children-count misuse error). A submenu
902
- already discloses on hover intent — see [Submenus](#submenus) — so
903
- `openOnHover` has nothing to add there.
1066
+ `placeholder`, `tabIndex`, `value`, and the root-element event props
1067
+ `onClick`, `onMouseDown`, `onMouseUp`) are ignored on a nested dropdown and
1068
+ warn once per mount with `console.warn`, as the too-many-children message
1069
+ does. A submenu already discloses on hover intent — see
1070
+ [Submenus](#submenus) — so `openOnHover` has nothing to add there.
904
1071
 
905
1072
  ### Submenu placement and styling
906
1073
 
@@ -1172,9 +1339,9 @@ so pass `props.label` when you want the popup named as well.)
1172
1339
 
1173
1340
  Item roles are filled in the same way when the body opens: items receive
1174
1341
  `role="option"` in a searchable (listbox) dropdown or `role="menuitem"` in
1175
- a menu (always `menuitem` inside a submenu), and the `<ul>`/`<ol>` wrappers
1176
- around them receive `role="presentation"` so the listbox or menu owns its
1177
- items directly rather than through an intervening list. A natively
1342
+ a menu (always `menuitem` inside a submenu), and the `<ul>`/`<ol>`/`<menu>`
1343
+ wrappers around them receive `role="presentation"` so the listbox or menu
1344
+ owns its items directly rather than through an intervening list. A natively
1178
1345
  interactive item (a button, link, or input) or one with a role you set
1179
1346
  yourself keeps its own role.
1180
1347
 
@@ -1,13 +1,21 @@
1
1
  import { type CSSProperties, type MouseEvent as ReactMouseEvent, type ReactElement, type ReactNode, type SyntheticEvent } from 'react';
2
2
  export type Item = {
3
+ /**
4
+ * The item element. Null when the submitted value came from a text
5
+ * input rather than an item (a created value with allowCreate, or an
6
+ * empty value with allowEmpty).
7
+ */
3
8
  element: MaybeHTMLElement;
9
+ /** The mouse, keyboard, or input event that activated or submitted the item. */
4
10
  event: Event | SyntheticEvent<HTMLElement>;
11
+ /** The item’s visible text (or the input’s text when element is null). */
5
12
  label: string;
6
13
  /**
7
14
  * Ancestor parent items from the root level down to the item’s
8
15
  * immediate parent. Empty for top-level items.
9
16
  */
10
17
  path: Array<ItemValue>;
18
+ /** The item’s data-ukt-value, or its label if it has none. */
11
19
  value: string;
12
20
  };
13
21
  /**
@@ -37,9 +45,15 @@ export type Props = {
37
45
  */
38
46
  allowEmpty?: boolean;
39
47
  /**
40
- * Can take a single React element or exactly two renderable children.
48
+ * Either a single React element (the dropdown body; the trigger is a
49
+ * generated button, or a generated search input when isSearchable) or
50
+ * exactly two renderable children: the trigger, then the body.
41
51
  */
42
52
  children: ChildrenTuple | ReactElement;
53
+ /**
54
+ * Applied to the dropdown root element. For a nested (submenu) Dropdown,
55
+ * applied to its item element instead.
56
+ */
43
57
  className?: string;
44
58
  /**
45
59
  * Prevents the dropdown from opening via user interaction: pointer,
@@ -52,9 +66,40 @@ export type Props = {
52
66
  * disabled doesn't override).
53
67
  */
54
68
  disabled?: boolean;
69
+ /**
70
+ * Whether the body is a list of selectable items (the default) or
71
+ * arbitrary interactive content such as a form. hasItems={false} turns
72
+ * off item selection and item keyboard navigation, gives the popup
73
+ * role="dialog" (unless isSearchable), and keeps it open on clicks
74
+ * inside the body. Defaults to true and isn’t inferred from the children.
75
+ */
55
76
  hasItems?: boolean;
77
+ /**
78
+ * Renders the dropdown already open on mount. Uncontrolled: it sets only
79
+ * the initial state.
80
+ */
56
81
  isOpenOnMount?: boolean;
82
+ /**
83
+ * Renders the trigger as a text input (a combobox) that filters the items
84
+ * as the user types, and the popup as a listbox.
85
+ */
57
86
  isSearchable?: boolean;
87
+ /**
88
+ * For a nested (submenu) Dropdown, its item element. When unset, it is
89
+ * an <li> inside a <ul>, <ol>, or <menu> and a <div> anywhere else,
90
+ * decided by checking its container once it’s in the DOM. Setting this
91
+ * skips that check, so the item mounts once and server output matches
92
+ * the client; recommended for a body that isn’t a list, which otherwise
93
+ * pays for the check on every open. Only a submenu Dropdown (one nested
94
+ * in a dropdown where hasItems is true) reads it; anywhere else it is
95
+ * ignored, with a warning.
96
+ */
97
+ itemAs?: 'div' | 'li';
98
+ /**
99
+ * Whether the dropdown stays open after an item is submitted, e.g. for a
100
+ * multi-select. Defaults to !hasItems: a menu closes on submit, while a
101
+ * hasItems={false} dialog stays open.
102
+ */
58
103
  keepOpenOnSubmit?: boolean;
59
104
  /**
60
105
  * Label content for the trigger button (when using single child syntax).
@@ -62,16 +107,45 @@ export type Props = {
62
107
  */
63
108
  label?: ReactNode;
64
109
  /**
65
- * Only usable in conjunction with {isSearchable: true}.
66
- * Used as search input’s name.
110
+ * The generated search input’s name. Only used when isSearchable is true.
67
111
  */
68
112
  name?: string;
113
+ /**
114
+ * Called whenever the highlighted item changes, with the same payload as
115
+ * onSubmitItem.
116
+ */
69
117
  onActiveItem?: (payload: Item) => void;
118
+ /**
119
+ * Applied to the dropdown root element. Ignored on a nested (submenu)
120
+ * Dropdown, which has no root element of its own.
121
+ */
70
122
  onClick?: (event: ReactMouseEvent<HTMLElement>) => unknown;
123
+ /**
124
+ * Called after the dropdown closes. For a nested (submenu) Dropdown,
125
+ * called when its submenu closes.
126
+ */
71
127
  onClose?: () => unknown;
128
+ /**
129
+ * Applied to the dropdown root element. Ignored on a nested (submenu)
130
+ * Dropdown, which has no root element of its own.
131
+ */
72
132
  onMouseDown?: (event: ReactMouseEvent<HTMLElement>) => unknown;
133
+ /**
134
+ * Applied to the dropdown root element. Ignored on a nested (submenu)
135
+ * Dropdown, which has no root element of its own.
136
+ */
73
137
  onMouseUp?: (event: ReactMouseEvent<HTMLElement>) => unknown;
138
+ /**
139
+ * Called after the dropdown opens (on mount, when isOpenOnMount). For a
140
+ * nested (submenu) Dropdown, called when its submenu opens.
141
+ */
74
142
  onOpen?: () => unknown;
143
+ /**
144
+ * Called when an item is submitted (click, Enter, or Space). Parent items
145
+ * (submenus) disclose rather than submit, so this fires for leaf items
146
+ * only. For a nested (submenu) Dropdown, fires for submissions within its
147
+ * subtree only.
148
+ */
75
149
  onSubmitItem?: (payload: Item) => void;
76
150
  /**
77
151
  * Opens the dropdown when the pointer hovers the trigger, and closes it a
@@ -82,19 +156,17 @@ export type Props = {
82
156
  */
83
157
  openOnHover?: boolean;
84
158
  /**
85
- * Only usable in conjunction with {isSearchable: true}.
86
- * Used as search input’s placeholder.
159
+ * The generated search input’s placeholder. Only used when isSearchable is true.
87
160
  */
88
161
  placeholder?: string;
89
162
  /**
90
- * Applied to the dropdown root element. Also accepts the component’s CSS
91
- * custom properties (e.g. `--uktdd-body-min-width`) for per-instance
92
- * placement and sizing, which plain `CSSProperties` rejects.
163
+ * Applied to the dropdown root element (for a nested (submenu) Dropdown,
164
+ * its item element). Also accepts the component’s CSS custom
165
+ * properties (e.g. `--uktdd-body-min-width`).
93
166
  */
94
- style?: CSSProperties & Record<`--${string}`, string | number | undefined>;
167
+ style?: StyleWithCustomProperties;
95
168
  /**
96
- * Only usable in conjunction with {isSearchable: true}.
97
- * Used as search input’s tabIndex.
169
+ * The generated search input’s tabIndex. Only used when isSearchable is true.
98
170
  */
99
171
  tabIndex?: number;
100
172
  /**
@@ -102,15 +174,22 @@ export type Props = {
102
174
  * stored value and its displayed label are the same, or a { label, value }
103
175
  * pair when they differ (e.g. a human-readable label shown for a stored
104
176
  * id) — the same { label, value } shape onSubmitItem reports back. The
105
- * value determines whether the value has changed, to avoid triggering
106
- * onSubmitItem when the already-selected item is re-submitted; the label is
107
- * used as the search input’s value when props.isSearchable === true. A bare
108
- * identifier is resolved to its label from the matching child’s
109
- * data-ukt-value in the body — so children whose value and label differ
110
- * need no explicit label; a { label, value } pair states it.
177
+ * value is used for change detection (skipping onSubmitItem when the
178
+ * already-selected item is re-submitted); the label is shown as the search
179
+ * input’s value when isSearchable is true. A bare identifier is resolved
180
+ * to its label from the matching child’s data-ukt-value in the body — so
181
+ * children whose value and label differ need no explicit label; a
182
+ * { label, value } pair states it.
111
183
  */
112
184
  value?: ItemValue | string;
113
185
  };
186
+ /** Alias of Props named to pair with MenubarProps. */
187
+ export type DropdownProps = Props;
188
+ /**
189
+ * A style object that also accepts the component’s CSS custom properties
190
+ * (e.g. `--uktdd-body-min-width`), which plain `CSSProperties` rejects.
191
+ */
192
+ export type StyleWithCustomProperties = CSSProperties & Record<`--${string}`, number | string | undefined>;
114
193
  type ChildrenTuple = [ReactNode, ReactNode] | readonly [ReactNode, ReactNode];
115
194
  type MaybeHTMLElement = HTMLElement | null;
116
195
  export default function Dropdown(props: Props): import("react").JSX.Element;