@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 +212 -45
- package/dist/Dropdown.d.ts +96 -17
- package/dist/Dropdown.js +589 -552
- package/dist/Dropdown.js.map +1 -1
- package/dist/Menubar.d.ts +7 -2
- package/dist/helpers.d.ts +3 -2
- package/package.json +7 -6
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
|
|
37
|
-
[collection of CSS custom properties](
|
|
38
|
-
|
|
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 (
|
|
148
|
-
|
|
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-
|
|
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
|
-
*
|
|
266
|
-
*
|
|
267
|
-
*
|
|
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
|
|
284
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
297
|
-
*
|
|
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
|
-
*
|
|
334
|
+
* The generated search input’s name. Only used when isSearchable is true.
|
|
307
335
|
*/
|
|
308
336
|
name?: string;
|
|
309
337
|
/**
|
|
310
|
-
* Called
|
|
311
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
385
|
+
placeholder?: string;
|
|
335
386
|
/**
|
|
336
|
-
*
|
|
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
|
-
|
|
339
|
-
style?: React.CSSProperties;
|
|
391
|
+
style?: StyleWithCustomProperties;
|
|
340
392
|
/**
|
|
341
|
-
*
|
|
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.
|
|
349
|
-
* change detection (skipping onSubmitItem when the
|
|
350
|
-
* re-submitted); the label is shown as the search
|
|
351
|
-
* isSearchable is true. A bare identifier
|
|
352
|
-
* matching child’s data-ukt-value in the body
|
|
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
|
|
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
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
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>`
|
|
1176
|
-
around them receive `role="presentation"` so the listbox or menu
|
|
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
|
|
package/dist/Dropdown.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
91
|
-
*
|
|
92
|
-
*
|
|
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?:
|
|
167
|
+
style?: StyleWithCustomProperties;
|
|
95
168
|
/**
|
|
96
|
-
*
|
|
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
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
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;
|