@acusti/dropdown 1.0.0-rc.3 → 1.0.0-rc.4
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 +209 -45
- package/dist/Dropdown.d.ts +96 -17
- package/dist/Dropdown.js +377 -342
- package/dist/Dropdown.js.map +1 -1
- package/dist/Menubar.d.ts +7 -2
- package/dist/helpers.d.ts +3 -2
- package/package.json +3 -2
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)
|
|
@@ -144,8 +144,9 @@ Internally, the dropdown renders:
|
|
|
144
144
|
|
|
145
145
|
Custom padding and overflow styling belongs on the content region, not the
|
|
146
146
|
outer shell. Note that `.uktdropdown-content` already applies default
|
|
147
|
-
padding (
|
|
148
|
-
|
|
147
|
+
padding (the `--uktdd-body-pad-*` custom properties, listed under
|
|
148
|
+
[CSS custom properties](#css-custom-properties)), so your body element does
|
|
149
|
+
**not** need its own padding:
|
|
149
150
|
|
|
150
151
|
```tsx
|
|
151
152
|
// ✗ Don’t double-pad — the content region already has padding
|
|
@@ -155,7 +156,7 @@ element does **not** need its own padding:
|
|
|
155
156
|
</Dropdown>
|
|
156
157
|
|
|
157
158
|
// ✓ Override default padding via CSS variables if needed
|
|
158
|
-
// .my-dropdown { --uktdd-body-pad-
|
|
159
|
+
// .my-dropdown { --uktdd-body-pad-block-start: 16px; /* etc */ }
|
|
159
160
|
```
|
|
160
161
|
|
|
161
162
|
For the most reliable anchor-positioning behavior:
|
|
@@ -262,11 +263,18 @@ type Props = {
|
|
|
262
263
|
*/
|
|
263
264
|
allowEmpty?: boolean;
|
|
264
265
|
/**
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
-
*
|
|
266
|
+
* Either a single React element (the dropdown body; the trigger is a
|
|
267
|
+
* generated button, or a generated search input when isSearchable) or
|
|
268
|
+
* exactly two renderable children: the trigger, then the body.
|
|
269
|
+
*/
|
|
270
|
+
children:
|
|
271
|
+
| ReactElement
|
|
272
|
+
| [ReactNode, ReactNode]
|
|
273
|
+
| readonly [ReactNode, ReactNode];
|
|
274
|
+
/**
|
|
275
|
+
* Applied to the dropdown root element. For a nested (submenu) Dropdown,
|
|
276
|
+
* applied to its item element instead.
|
|
268
277
|
*/
|
|
269
|
-
children: ReactNode | [ReactNode, ReactNode];
|
|
270
278
|
className?: string;
|
|
271
279
|
/**
|
|
272
280
|
* Prevents the dropdown from opening via user interaction: pointer,
|
|
@@ -280,21 +288,38 @@ type Props = {
|
|
|
280
288
|
*/
|
|
281
289
|
disabled?: boolean;
|
|
282
290
|
/**
|
|
283
|
-
* Whether the
|
|
284
|
-
*
|
|
291
|
+
* Whether the body is a list of selectable items (the default) or
|
|
292
|
+
* arbitrary interactive content such as a form. hasItems={false} turns
|
|
293
|
+
* off item selection and item keyboard navigation, gives the popup
|
|
294
|
+
* role="dialog" (unless isSearchable), and keeps it open on clicks
|
|
295
|
+
* inside the body. Defaults to true and isn’t inferred from the children.
|
|
285
296
|
*/
|
|
286
297
|
hasItems?: boolean;
|
|
287
298
|
/**
|
|
288
|
-
*
|
|
299
|
+
* Renders the dropdown already open on mount. Uncontrolled: it sets only
|
|
300
|
+
* the initial state.
|
|
289
301
|
*/
|
|
290
302
|
isOpenOnMount?: boolean;
|
|
291
303
|
/**
|
|
292
|
-
*
|
|
304
|
+
* Renders the trigger as a text input (a combobox) that filters the items
|
|
305
|
+
* as the user types, and the popup as a listbox.
|
|
293
306
|
*/
|
|
294
307
|
isSearchable?: boolean;
|
|
295
308
|
/**
|
|
296
|
-
*
|
|
297
|
-
*
|
|
309
|
+
* For a nested (submenu) Dropdown, its item element. When unset, it is
|
|
310
|
+
* an <li> inside a <ul>, <ol>, or <menu> and a <div> anywhere else,
|
|
311
|
+
* decided by checking its container once it’s in the DOM. Setting this
|
|
312
|
+
* skips that check, so the item mounts once and server output matches
|
|
313
|
+
* the client; recommended for a body that isn’t a list, which otherwise
|
|
314
|
+
* pays for the check on every open. Only a submenu Dropdown (one nested
|
|
315
|
+
* in a dropdown where hasItems is true) reads it; anywhere else it is
|
|
316
|
+
* ignored, with a warning.
|
|
317
|
+
*/
|
|
318
|
+
itemAs?: 'div' | 'li';
|
|
319
|
+
/**
|
|
320
|
+
* Whether the dropdown stays open after an item is submitted, e.g. for a
|
|
321
|
+
* multi-select. Defaults to !hasItems: a menu closes on submit, while a
|
|
322
|
+
* hasItems={false} dialog stays open.
|
|
298
323
|
*/
|
|
299
324
|
keepOpenOnSubmit?: boolean;
|
|
300
325
|
/**
|
|
@@ -303,19 +328,46 @@ type Props = {
|
|
|
303
328
|
*/
|
|
304
329
|
label?: ReactNode;
|
|
305
330
|
/**
|
|
306
|
-
*
|
|
331
|
+
* The generated search input’s name. Only used when isSearchable is true.
|
|
307
332
|
*/
|
|
308
333
|
name?: string;
|
|
309
334
|
/**
|
|
310
|
-
* Called
|
|
311
|
-
*
|
|
335
|
+
* Called whenever the highlighted item changes, with the same payload as
|
|
336
|
+
* onSubmitItem.
|
|
312
337
|
*/
|
|
313
338
|
onActiveItem?: (payload: Item) => void;
|
|
339
|
+
/**
|
|
340
|
+
* Applied to the dropdown root element. Ignored on a nested (submenu)
|
|
341
|
+
* Dropdown, which has no root element of its own.
|
|
342
|
+
*/
|
|
314
343
|
onClick?: (event: React.MouseEvent<HTMLElement>) => unknown;
|
|
344
|
+
/**
|
|
345
|
+
* Called after the dropdown closes. For a nested (submenu) Dropdown,
|
|
346
|
+
* called when its submenu closes.
|
|
347
|
+
*/
|
|
315
348
|
onClose?: () => unknown;
|
|
349
|
+
/**
|
|
350
|
+
* Applied to the dropdown root element. Ignored on a nested (submenu)
|
|
351
|
+
* Dropdown, which has no root element of its own.
|
|
352
|
+
*/
|
|
316
353
|
onMouseDown?: (event: React.MouseEvent<HTMLElement>) => unknown;
|
|
354
|
+
/**
|
|
355
|
+
* Applied to the dropdown root element. Ignored on a nested (submenu)
|
|
356
|
+
* Dropdown, which has no root element of its own.
|
|
357
|
+
*/
|
|
317
358
|
onMouseUp?: (event: React.MouseEvent<HTMLElement>) => unknown;
|
|
359
|
+
/**
|
|
360
|
+
* Called after the dropdown opens (on mount, when isOpenOnMount). For a
|
|
361
|
+
* nested (submenu) Dropdown, called when its submenu opens.
|
|
362
|
+
*/
|
|
318
363
|
onOpen?: () => unknown;
|
|
364
|
+
/**
|
|
365
|
+
* Called when an item is submitted (click, Enter, or Space). Parent items
|
|
366
|
+
* (submenus) disclose rather than submit, so this fires for leaf items
|
|
367
|
+
* only. For a nested (submenu) Dropdown, fires for submissions within its
|
|
368
|
+
* subtree only.
|
|
369
|
+
*/
|
|
370
|
+
onSubmitItem?: (payload: Item) => void;
|
|
319
371
|
/**
|
|
320
372
|
* Opens the dropdown when the pointer hovers the trigger, and closes it a
|
|
321
373
|
* short moment after the pointer leaves the trigger and body entirely (the
|
|
@@ -325,63 +377,158 @@ type Props = {
|
|
|
325
377
|
*/
|
|
326
378
|
openOnHover?: boolean;
|
|
327
379
|
/**
|
|
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
|
|
380
|
+
* The generated search input’s placeholder. Only used when isSearchable is true.
|
|
333
381
|
*/
|
|
334
|
-
|
|
382
|
+
placeholder?: string;
|
|
335
383
|
/**
|
|
336
|
-
*
|
|
384
|
+
* Applied to the dropdown root element (for a nested (submenu) Dropdown,
|
|
385
|
+
* its item element). Also accepts the component’s CSS custom
|
|
386
|
+
* properties (e.g. `--uktdd-body-min-width`).
|
|
337
387
|
*/
|
|
338
|
-
|
|
339
|
-
style?: React.CSSProperties;
|
|
388
|
+
style?: StyleWithCustomProperties;
|
|
340
389
|
/**
|
|
341
|
-
*
|
|
390
|
+
* The generated search input’s tabIndex. Only used when isSearchable is true.
|
|
342
391
|
*/
|
|
343
392
|
tabIndex?: number;
|
|
344
393
|
/**
|
|
345
394
|
* The dropdown’s controlled value. Pass a bare identifier when an item’s
|
|
346
395
|
* stored value and its displayed label are the same, or a { label, value }
|
|
347
396
|
* 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
|
|
397
|
+
* id) — the same { label, value } shape onSubmitItem reports back. The
|
|
398
|
+
* value is used for change detection (skipping onSubmitItem when the
|
|
399
|
+
* already-selected item is re-submitted); the label is shown as the search
|
|
400
|
+
* input’s value when isSearchable is true. A bare identifier is resolved
|
|
401
|
+
* to its label from the matching child’s data-ukt-value in the body — so
|
|
402
|
+
* children whose value and label differ need no explicit label; a
|
|
403
|
+
* { label, value } pair states it.
|
|
354
404
|
*/
|
|
355
405
|
value?: ItemValue | string;
|
|
356
406
|
};
|
|
357
407
|
```
|
|
358
408
|
|
|
409
|
+
`Props` is also exported as `DropdownProps`, the name that pairs with
|
|
410
|
+
`MenubarProps`. `StyleWithCustomProperties` is `React.CSSProperties`
|
|
411
|
+
extended to accept the component’s `--uktdd-*` custom properties, and is
|
|
412
|
+
the type of both components’ `style` prop.
|
|
413
|
+
|
|
359
414
|
### Item Types
|
|
360
415
|
|
|
361
416
|
Both types are exported alongside the component:
|
|
362
417
|
|
|
363
418
|
```ts
|
|
364
419
|
/**
|
|
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
|
|
420
|
+
* A { label, value } pair naming an item: `value` is the stored value (the
|
|
421
|
+
* item’s data-ukt-value) and `label` is its displayed text. Accepted by the
|
|
367
422
|
* value prop when an item’s value and label differ; also the shape of an
|
|
368
423
|
* Item’s path entries.
|
|
369
424
|
*/
|
|
370
425
|
type ItemValue = { label: string; value: string };
|
|
371
426
|
|
|
372
427
|
type Item = {
|
|
428
|
+
/**
|
|
429
|
+
* The item element. Null when the submitted value came from a text
|
|
430
|
+
* input rather than an item (a created value with allowCreate, or an
|
|
431
|
+
* empty value with allowEmpty).
|
|
432
|
+
*/
|
|
373
433
|
element: HTMLElement | null;
|
|
434
|
+
/** The mouse, keyboard, or input event that activated or submitted the item. */
|
|
374
435
|
event: Event | React.SyntheticEvent<HTMLElement>;
|
|
436
|
+
/** The item’s visible text (or the input’s text when element is null). */
|
|
375
437
|
label: string;
|
|
376
438
|
/**
|
|
377
439
|
* Ancestor parent items from the root level down to the item’s
|
|
378
440
|
* immediate parent. Empty for top-level items.
|
|
379
441
|
*/
|
|
380
442
|
path: Array<ItemValue>;
|
|
443
|
+
/** The item’s data-ukt-value, or its label if it has none. */
|
|
381
444
|
value: string;
|
|
382
445
|
};
|
|
383
446
|
```
|
|
384
447
|
|
|
448
|
+
### CSS custom properties
|
|
449
|
+
|
|
450
|
+
Every `--uktdd-*` custom property the component reads, with its default.
|
|
451
|
+
Set them on the dropdown’s root (a class, or the `style` prop, which
|
|
452
|
+
accepts them) or on an ancestor to theme a whole subtree. Anything not
|
|
453
|
+
listed here — the `--uktdd-anchor` and `--uktdd-submenu-anchor` anchor
|
|
454
|
+
names, and the `data-ukt-generated-id` attribute — is internal.
|
|
455
|
+
|
|
456
|
+
Colors (see [Color Scheme](#color-scheme)):
|
|
457
|
+
|
|
458
|
+
| Property | Default |
|
|
459
|
+
| ----------------------------------------- | ---------------------------------------------------- |
|
|
460
|
+
| `--uktdd-color-scheme` | `inherit` |
|
|
461
|
+
| `--uktdd-body-bg-color` | `Canvas` |
|
|
462
|
+
| `--uktdd-body-color` | `CanvasText` |
|
|
463
|
+
| `--uktdd-body-bg-color-hover` | `Highlight` |
|
|
464
|
+
| `--uktdd-body-color-hover` | `HighlightText` |
|
|
465
|
+
| `--uktdd-body-bg-color-selected` | `color-mix(in oklab, currentColor 6%, transparent)` |
|
|
466
|
+
| `--uktdd-body-bg-color-path` | 12% of the body color mixed into its background |
|
|
467
|
+
| `--uktdd-body-color-path` | `currentColor` |
|
|
468
|
+
| `--uktdd-body-border-color` | `color-mix(in oklab, currentColor 15%, transparent)` |
|
|
469
|
+
| `--uktdd-body-box-shadow` | `0 8px 18px rgba(0, 0, 0, 0.25)` |
|
|
470
|
+
| `--uktdd-menubar-trigger-bg-color-active` | `color-mix(in oklab, currentColor 12%, transparent)` |
|
|
471
|
+
|
|
472
|
+
Typography, sizing, and spacing:
|
|
473
|
+
|
|
474
|
+
| Property | Default |
|
|
475
|
+
| ------------------------------- | ----------------------------------------- |
|
|
476
|
+
| `--uktdd-font-family` | the system UI font stack |
|
|
477
|
+
| `--uktdd-body-buffer` | `10px` (viewport inset for the max sizes) |
|
|
478
|
+
| `--uktdd-body-min-height` | `30px` |
|
|
479
|
+
| `--uktdd-body-max-height` | `calc(100dvh - var(--uktdd-body-buffer))` |
|
|
480
|
+
| `--uktdd-body-min-width` | `min(50px, 100%)` |
|
|
481
|
+
| `--uktdd-body-max-width` | `calc(100dvw - var(--uktdd-body-buffer))` |
|
|
482
|
+
| `--uktdd-body-pad-block-start` | `0.5em` |
|
|
483
|
+
| `--uktdd-body-pad-block-end` | `0.5em` |
|
|
484
|
+
| `--uktdd-body-pad-inline-start` | `0.6875em` |
|
|
485
|
+
| `--uktdd-body-pad-inline-end` | `0.6875em` |
|
|
486
|
+
| `--uktdd-body-gap` | `0px` (trigger ↔ body) |
|
|
487
|
+
| `--uktdd-submenu-gap` | `0px` (parent item ↔ submenu) |
|
|
488
|
+
| `--uktdd-label-gap` | `0.625em` (`props.label` text ↔ trigger) |
|
|
489
|
+
|
|
490
|
+
Placement (see
|
|
491
|
+
[Placement Customization](#placement-customization-with-css-variables) and
|
|
492
|
+
[Submenu placement and styling](#submenu-placement-and-styling)):
|
|
493
|
+
|
|
494
|
+
| Property | Default |
|
|
495
|
+
| --------------------------------------- | -------------------------------------------------------- |
|
|
496
|
+
| `--uktdd-body-position-area` | `block-end span-inline-end` |
|
|
497
|
+
| `--uktdd-body-position-try-fallback-1` | `--uktdd-top-start` |
|
|
498
|
+
| `--uktdd-body-position-try-fallback-2` | `--uktdd-bottom-end` |
|
|
499
|
+
| `--uktdd-body-fill-fallbacks` | `--uktdd-fill-bottom, --uktdd-fill-top` |
|
|
500
|
+
| `--uktdd-submenu-position-area` | `inline-end span-block-end` |
|
|
501
|
+
| `--uktdd-submenu-position-try-fallback` | `--uktdd-submenu-inline-start` |
|
|
502
|
+
| `--uktdd-submenu-fill-fallbacks` | `--uktdd-submenu-fill, --uktdd-submenu-fill flip-inline` |
|
|
503
|
+
|
|
504
|
+
The `@position-try` placements the component ships, for use in the fallback
|
|
505
|
+
slots above: `--uktdd-top-start`, `--uktdd-top-end`,
|
|
506
|
+
`--uktdd-bottom-start`, `--uktdd-bottom-end`,
|
|
507
|
+
`--uktdd-submenu-inline-start`, the fills `--uktdd-fill-bottom`,
|
|
508
|
+
`--uktdd-fill-top`, `--uktdd-fill`, `--uktdd-fill-cover`, and
|
|
509
|
+
`--uktdd-submenu-fill`, and the no-op `--uktdd-noop`.
|
|
510
|
+
|
|
511
|
+
### Class names and data attributes
|
|
512
|
+
|
|
513
|
+
The DOM the component renders is styleable by these class names, which are
|
|
514
|
+
part of the public API along with the `data-ukt-*` attributes:
|
|
515
|
+
|
|
516
|
+
- `.uktdropdown` — the root, with the state classes `.is-open`,
|
|
517
|
+
`.is-searchable`, and `.is-disabled`
|
|
518
|
+
- `.uktdropdown-trigger` — the generated trigger (a custom trigger keeps
|
|
519
|
+
its own classes)
|
|
520
|
+
- `.uktdropdown-label` and `.uktdropdown-label-text` — the wrapper and text
|
|
521
|
+
rendered for `props.label`
|
|
522
|
+
- `.uktdropdown-body` — the anchored popover shell, with `.has-items`
|
|
523
|
+
unless `hasItems={false}`
|
|
524
|
+
- `.uktdropdown-content` — the padded, scrollable region inside the body
|
|
525
|
+
- `.uktmenubar` — the [`Menubar`](#menubar) root
|
|
526
|
+
- `data-ukt-item` and `data-ukt-value` mark items, `data-ukt-submenu` marks
|
|
527
|
+
a [submenu](#the-data-ukt-submenu-protocol), and `data-ukt-active` marks
|
|
528
|
+
the highlighted item at each open level (the styling hook for the
|
|
529
|
+
highlight, alongside `aria-expanded` on open parent items and
|
|
530
|
+
`aria-selected` on the selected option)
|
|
531
|
+
|
|
385
532
|
## Usage Examples
|
|
386
533
|
|
|
387
534
|
### Basic List Dropdown
|
|
@@ -870,6 +1017,18 @@ Like items, submenus are ultimately declared in the DOM. A nested
|
|
|
870
1017
|
</li>
|
|
871
1018
|
```
|
|
872
1019
|
|
|
1020
|
+
The parent item is an `<li>` when the nested `Dropdown` sits inside a
|
|
1021
|
+
`<ul>`, `<ol>`, or `<menu>`, and a `<div>` anywhere else (a body built from
|
|
1022
|
+
`<div data-ukt-item>` items, say), so once mounted it is valid HTML in
|
|
1023
|
+
either kind of body. The container is checked when the item enters the DOM,
|
|
1024
|
+
and for a non-list container the item then remounts as a `<div>` before
|
|
1025
|
+
paint — on every open, since the body unmounts on close. For a non-list
|
|
1026
|
+
body, pass `itemAs="div"` to name the element up front and skip the check:
|
|
1027
|
+
the item mounts once, the submenu body’s own mount effects run once, and
|
|
1028
|
+
server output is valid too (without `itemAs`, a server-rendered
|
|
1029
|
+
`isOpenOnMount` dropdown still ships the `<li>`). `className` and `style`
|
|
1030
|
+
on the nested `Dropdown` land on that element either way.
|
|
1031
|
+
|
|
873
1032
|
You can author that markup directly instead of nesting a `Dropdown`
|
|
874
1033
|
component; the two forms behave identically because the component form
|
|
875
1034
|
compiles to the attribute form. Rules for direct authoring:
|
|
@@ -893,14 +1052,19 @@ Most props keep their meaning, scoped to the submenu:
|
|
|
893
1052
|
- `onSubmitItem`: fires for submissions within that subtree only
|
|
894
1053
|
- `onActiveItem`, `onOpen`, `onClose`: scoped to the submenu
|
|
895
1054
|
- `disabled`: disables the parent item
|
|
896
|
-
- `className`/`style`: applied to the
|
|
1055
|
+
- `className`/`style`: applied to the item element
|
|
1056
|
+
- `itemAs`: the item element (`'li'` or `'div'`), skipping the container
|
|
1057
|
+
check described above and recommended for a non-list body; submenu-only,
|
|
1058
|
+
and ignored (with a warning) anywhere else, including a nested
|
|
1059
|
+
`hasItems={false}` `Dropdown`
|
|
897
1060
|
|
|
898
1061
|
Props that only make sense at the top level (`allowCreate`, `allowEmpty`,
|
|
899
1062
|
`isOpenOnMount`, `isSearchable`, `keepOpenOnSubmit`, `name`, `openOnHover`,
|
|
900
|
-
`placeholder`, `tabIndex`, `value
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
1063
|
+
`placeholder`, `tabIndex`, `value`, and the root-element event props
|
|
1064
|
+
`onClick`, `onMouseDown`, `onMouseUp`) are ignored on a nested dropdown and
|
|
1065
|
+
warn once per mount with `console.warn`, as the too-many-children message
|
|
1066
|
+
does. A submenu already discloses on hover intent — see
|
|
1067
|
+
[Submenus](#submenus) — so `openOnHover` has nothing to add there.
|
|
904
1068
|
|
|
905
1069
|
### Submenu placement and styling
|
|
906
1070
|
|
|
@@ -1172,9 +1336,9 @@ so pass `props.label` when you want the popup named as well.)
|
|
|
1172
1336
|
|
|
1173
1337
|
Item roles are filled in the same way when the body opens: items receive
|
|
1174
1338
|
`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
|
|
1339
|
+
a menu (always `menuitem` inside a submenu), and the `<ul>`/`<ol>`/`<menu>`
|
|
1340
|
+
wrappers around them receive `role="presentation"` so the listbox or menu
|
|
1341
|
+
owns its items directly rather than through an intervening list. A natively
|
|
1178
1342
|
interactive item (a button, link, or input) or one with a role you set
|
|
1179
1343
|
yourself keeps its own role.
|
|
1180
1344
|
|
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;
|