@acusti/dropdown 1.0.0-alpha.4 → 1.0.0-alpha.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 +78 -27
- package/dist/Dropdown.d.ts +19 -5
- package/dist/Dropdown.js +523 -426
- package/dist/Dropdown.js.map +1 -1
- package/dist/helpers.d.ts +4 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -123,13 +123,17 @@ CSS-first sizing model:
|
|
|
123
123
|
- The dropdown body is an anchored shell
|
|
124
124
|
- The inner content region becomes scrollable only when the content exceeds
|
|
125
125
|
the available space
|
|
126
|
-
-
|
|
126
|
+
- The body opens in its authored direction whenever it fits there, falling
|
|
127
|
+
back through the other placements in order and only squeezing-to-scroll
|
|
128
|
+
when it fits nowhere at its natural size (see
|
|
129
|
+
[Changing the Default Direction](#changing-the-default-direction))
|
|
127
130
|
|
|
128
131
|
This means the dropdown tends to:
|
|
129
132
|
|
|
130
133
|
- stay content-sized when the contents are small
|
|
131
|
-
-
|
|
132
|
-
-
|
|
134
|
+
- open in the direction you asked for, as long as that side can hold it
|
|
135
|
+
- expand to the available viewport space, and become scrollable, only when
|
|
136
|
+
the contents exceed the room the chosen side has
|
|
133
137
|
|
|
134
138
|
Internally, the dropdown renders:
|
|
135
139
|
|
|
@@ -292,16 +296,33 @@ type Props = {
|
|
|
292
296
|
*/
|
|
293
297
|
tabIndex?: number;
|
|
294
298
|
/**
|
|
295
|
-
*
|
|
296
|
-
*
|
|
299
|
+
* The dropdown’s controlled value. Pass a bare identifier when an item’s
|
|
300
|
+
* stored value and its displayed label are the same, or a { label, value }
|
|
301
|
+
* pair when they differ (e.g. a human-readable label shown for a stored
|
|
302
|
+
* id) — the same { label, value } shape onSubmitItem reports back. Used for
|
|
303
|
+
* change detection (skipping onSubmitItem when the already-selected item is
|
|
304
|
+
* re-submitted); the label is shown as the search input’s value when
|
|
305
|
+
* isSearchable is true. A bare identifier resolves to its label from the
|
|
306
|
+
* matching child’s data-ukt-value in the body, so children whose value
|
|
307
|
+
* and label differ need no explicit label.
|
|
297
308
|
*/
|
|
298
|
-
value?: string;
|
|
309
|
+
value?: ItemValue | string;
|
|
299
310
|
};
|
|
300
311
|
```
|
|
301
312
|
|
|
302
|
-
### Item
|
|
313
|
+
### Item Types
|
|
314
|
+
|
|
315
|
+
Both types are exported alongside the component:
|
|
303
316
|
|
|
304
317
|
```ts
|
|
318
|
+
/**
|
|
319
|
+
* A { label, value } pair naming an item: value is the stored value (the
|
|
320
|
+
* item’s data-ukt-value) and label is its displayed text. Accepted by the
|
|
321
|
+
* value prop when an item’s value and label differ; also the shape of an
|
|
322
|
+
* Item’s path entries.
|
|
323
|
+
*/
|
|
324
|
+
type ItemValue = { label: string; value: string };
|
|
325
|
+
|
|
305
326
|
type Item = {
|
|
306
327
|
element: HTMLElement | null;
|
|
307
328
|
event: Event | React.SyntheticEvent<HTMLElement>;
|
|
@@ -310,7 +331,7 @@ type Item = {
|
|
|
310
331
|
* Ancestor parent items from the root level down to the item’s
|
|
311
332
|
* immediate parent. Empty for top-level items.
|
|
312
333
|
*/
|
|
313
|
-
path: Array<
|
|
334
|
+
path: Array<ItemValue>;
|
|
314
335
|
value: string;
|
|
315
336
|
};
|
|
316
337
|
```
|
|
@@ -574,7 +595,18 @@ trigger pinned to the bottom of the viewport, near the inline-end edge):
|
|
|
574
595
|
}
|
|
575
596
|
```
|
|
576
597
|
|
|
577
|
-
See the `DirectionRecipes` story for a live demo of all four.
|
|
598
|
+
See the [`DirectionRecipes` story][] for a live demo of all four.
|
|
599
|
+
|
|
600
|
+
[`DirectionRecipes` story]:
|
|
601
|
+
https://uikit.acusti.ca/?path=/docs/uikit-controls-Dropdown--docs#direction-recipes
|
|
602
|
+
|
|
603
|
+
The primary placement wins whenever the body fits there at its natural
|
|
604
|
+
size, then the fallbacks are tried in order — the body opens in the
|
|
605
|
+
direction you asked for as long as that side can hold it, and never flips
|
|
606
|
+
to the opposite side just because the viewport happens to have more empty
|
|
607
|
+
space there. Only when the body fits in none of those placements does it
|
|
608
|
+
size itself to the block-axis space its preferred side has and let the
|
|
609
|
+
content scroll, so it never overflows the viewport or covers the trigger.
|
|
578
610
|
|
|
579
611
|
Use `--uktdd-body-gap` for the space between the trigger and the body. It
|
|
580
612
|
is applied as a symmetric `margin-block`, so the gap lands on whichever
|
|
@@ -618,8 +650,9 @@ tile. A `center` tile is only as wide as the trigger, so a body wider than
|
|
|
618
650
|
the trigger always overflows it — and `position-try` refuses to select a
|
|
619
651
|
fallback that overflows, so a `bottom center` → `top center` setup never
|
|
620
652
|
flips. `span-all` centers over the trigger identically but spans the full
|
|
621
|
-
width, so it flips cleanly and clamps to the viewport near an edge
|
|
622
|
-
|
|
653
|
+
width, so it flips cleanly and clamps to the viewport near an edge. The
|
|
654
|
+
menu opens below by default and flips above only when it doesn’t fit below;
|
|
655
|
+
list the flipped fallback so that flip is available:
|
|
623
656
|
|
|
624
657
|
```css
|
|
625
658
|
.centered-menu {
|
|
@@ -668,7 +701,7 @@ Submenu semantics:
|
|
|
668
701
|
|
|
669
702
|
- Parent items disclose; they never submit. `onSubmitItem` fires only for
|
|
670
703
|
leaf items, and the payload’s `path` reports the leaf’s ancestor parent
|
|
671
|
-
items (see the [`Item` type](#item-
|
|
704
|
+
items (see the [`Item` type](#item-types) above), so leaves with the same
|
|
672
705
|
value in different submenus are distinguishable.
|
|
673
706
|
- Submenus nest to arbitrary depth — a submenu body can itself contain
|
|
674
707
|
nested dropdowns.
|
|
@@ -695,7 +728,7 @@ Like items, submenus are ultimately declared in the DOM. A nested
|
|
|
695
728
|
```html
|
|
696
729
|
<li data-ukt-item aria-haspopup="menu" aria-expanded="false">
|
|
697
730
|
Align
|
|
698
|
-
<ul data-ukt-submenu role="menu">
|
|
731
|
+
<ul data-ukt-submenu role="menu" popover="manual">
|
|
699
732
|
…
|
|
700
733
|
</ul>
|
|
701
734
|
</li>
|
|
@@ -707,6 +740,9 @@ compiles to the attribute form. Rules for direct authoring:
|
|
|
707
740
|
|
|
708
741
|
- the `data-ukt-submenu` element must be a descendant of its parent item
|
|
709
742
|
element
|
|
743
|
+
- the submenu discloses in the top layer as a popover; the component sets
|
|
744
|
+
`popover="manual"`, and the engine adds it if your markup omits it, so
|
|
745
|
+
you don’t manage `display` (visibility follows the popover open state)
|
|
710
746
|
- in a level whose items include a submenu, mark all of that level’s items
|
|
711
747
|
explicitly with `data-ukt-item` or `data-ukt-value`; purely flat levels —
|
|
712
748
|
including flat submenu bodies — fall back to the automatic item-detection
|
|
@@ -734,8 +770,12 @@ already discloses on hover intent — see [Submenus](#submenus) — so
|
|
|
734
770
|
|
|
735
771
|
Submenus reuse the dropdown’s anchor-positioning layout model: the expanded
|
|
736
772
|
parent item is the anchor, and the submenu opens at its inline-end,
|
|
737
|
-
top-aligned, falling back to the opposite side when out of bounds.
|
|
738
|
-
|
|
773
|
+
top-aligned, falling back to the opposite side when out of bounds. Like the
|
|
774
|
+
body, a submenu renders in the top layer (`popover="manual"`), so its
|
|
775
|
+
containing block is the viewport — no ancestor (including the body’s own
|
|
776
|
+
top-layer box) can capture it and clip or shift its placement, which keeps
|
|
777
|
+
it flush to the parent item’s edge across browsers. Placement custom
|
|
778
|
+
properties follow the established naming scheme:
|
|
739
779
|
|
|
740
780
|
- `--uktdd-submenu-position-area` (default: `inline-end span-block-end`)
|
|
741
781
|
- `--uktdd-submenu-position-try-fallbacks`
|
|
@@ -757,18 +797,12 @@ theming menu colors.
|
|
|
757
797
|
|
|
758
798
|
The active path is fully styleable: `data-ukt-active` marks one item per
|
|
759
799
|
open level (a parent item stays active while you navigate its submenu), and
|
|
760
|
-
`aria-expanded="true"` marks open parent items.
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
[aria-expanded='true'] > [data-ukt-submenu] {
|
|
769
|
-
display: block;
|
|
770
|
-
}
|
|
771
|
-
```
|
|
800
|
+
`aria-expanded="true"` marks open parent items. Submenu visibility is not a
|
|
801
|
+
`display` rule you own: each submenu is a `popover="manual"` element the
|
|
802
|
+
component shows and hides (via `showPopover`/`hidePopover`) as its parent
|
|
803
|
+
item expands and collapses, so it discloses in the top layer and the UA’s
|
|
804
|
+
`:popover-open` state controls whether it renders. Don’t set `display` on
|
|
805
|
+
`[data-ukt-submenu]` — it would fight that state.
|
|
772
806
|
|
|
773
807
|
Following macOS, the default styles distinguish the deepest active item —
|
|
774
808
|
the one keyboard input operates on — from its ancestors on the active path.
|
|
@@ -927,6 +961,23 @@ readers can associate the trigger with its popup. If your custom trigger
|
|
|
927
961
|
already specifies any of these ARIA props, your values win — the component
|
|
928
962
|
only fills in what you haven’t set.
|
|
929
963
|
|
|
964
|
+
Item roles are filled in the same way when the body opens: items receive
|
|
965
|
+
`role="option"` in a searchable (listbox) dropdown or `role="menuitem"` in
|
|
966
|
+
a menu (always `menuitem` inside a submenu), and the `<ul>`/`<ol>` wrappers
|
|
967
|
+
around them receive `role="presentation"` so the listbox or menu owns its
|
|
968
|
+
items directly rather than through an intervening list. A natively
|
|
969
|
+
interactive item (a button, link, or input) or one with a role you set
|
|
970
|
+
yourself keeps its own role.
|
|
971
|
+
|
|
972
|
+
Opening also reveals the current selection: the item matching `props.value`
|
|
973
|
+
becomes the active item (so keyboard navigation starts from the current
|
|
974
|
+
selection) and is scrolled into view. In a searchable (listbox) dropdown it
|
|
975
|
+
additionally receives `aria-selected="true"` (`aria-selected` isn’t valid
|
|
976
|
+
on a `menuitem`) — unless you author `aria-selected` on items yourself, in
|
|
977
|
+
which case the component leaves selection ARIA entirely to you, as with any
|
|
978
|
+
ARIA you set. The persistent tint on the selected item is themeable via the
|
|
979
|
+
`--uktdd-body-bg-color-selected` custom property.
|
|
980
|
+
|
|
930
981
|
Submenus and menubars extend the same pattern automatically:
|
|
931
982
|
|
|
932
983
|
- parent items receive `aria-haspopup="menu"` and `aria-expanded`, and
|
package/dist/Dropdown.d.ts
CHANGED
|
@@ -7,10 +7,16 @@ export type Item = {
|
|
|
7
7
|
* Ancestor parent items from the root level down to the item’s
|
|
8
8
|
* immediate parent. Empty for top-level items.
|
|
9
9
|
*/
|
|
10
|
-
path: Array<
|
|
10
|
+
path: Array<ItemValue>;
|
|
11
11
|
value: string;
|
|
12
12
|
};
|
|
13
|
-
|
|
13
|
+
/**
|
|
14
|
+
* A { label, value } pair naming an item: `value` is the stored value (the
|
|
15
|
+
* item’s data-ukt-value) and `label` is its displayed text. Accepted by the
|
|
16
|
+
* value prop when an item’s value and label differ; also the shape of an
|
|
17
|
+
* Item’s path entries.
|
|
18
|
+
*/
|
|
19
|
+
export type ItemValue = {
|
|
14
20
|
label: string;
|
|
15
21
|
value: string;
|
|
16
22
|
};
|
|
@@ -82,10 +88,18 @@ export type Props = {
|
|
|
82
88
|
*/
|
|
83
89
|
tabIndex?: number;
|
|
84
90
|
/**
|
|
85
|
-
*
|
|
86
|
-
*
|
|
91
|
+
* The dropdown’s controlled value. Pass a bare identifier when an item’s
|
|
92
|
+
* stored value and its displayed label are the same, or a { label, value }
|
|
93
|
+
* pair when they differ (e.g. a human-readable label shown for a stored
|
|
94
|
+
* id) — the same { label, value } shape onSubmitItem reports back. The
|
|
95
|
+
* value determines whether the value has changed, to avoid triggering
|
|
96
|
+
* onSubmitItem when the already-selected item is re-submitted; the label is
|
|
97
|
+
* used as the search input’s value when props.isSearchable === true. A bare
|
|
98
|
+
* identifier is resolved to its label from the matching child’s
|
|
99
|
+
* data-ukt-value in the body — so children whose value and label differ
|
|
100
|
+
* need no explicit label; a { label, value } pair states it.
|
|
87
101
|
*/
|
|
88
|
-
value?: string;
|
|
102
|
+
value?: ItemValue | string;
|
|
89
103
|
};
|
|
90
104
|
type ChildrenTuple = [ReactNode, ReactNode] | readonly [ReactNode, ReactNode];
|
|
91
105
|
type MaybeHTMLElement = HTMLElement | null;
|