@acusti/dropdown 1.2.1 → 1.3.0

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
@@ -244,6 +244,15 @@ These are _newly available_ Baseline features: they work in up-to-date
244
244
  browser versions but not in ones predating that support, which the
245
245
  component requires.
246
246
 
247
+ The [trigger chevron](#the-trigger-chevron) also uses `clip-path: shape()`
248
+ ([Baseline 2026](https://developer.mozilla.org/en-US/docs/Web/CSS/basic-shape/shape))
249
+ where it’s supported, for a stroke you can set, and falls back to an SVG
250
+ mask at the default stroke where it isn’t. Turning it off with
251
+ `--uktdd-trigger-icon: none` relies on container style queries
252
+ ([Baseline 2026](https://developer.mozilla.org/en-US/docs/Web/CSS/@container#container_style_queries));
253
+ without them (Firefox before 151), the chevron is hidden but keeps its
254
+ space.
255
+
247
256
  ## API Reference
248
257
 
249
258
  ### Props
@@ -371,8 +380,10 @@ type Props = {
371
380
  /**
372
381
  * Called when an item is submitted (click, Enter, or Space). Parent items
373
382
  * (submenus) disclose rather than submit, so this fires for leaf items
374
- * only. For a nested (submenu) Dropdown, fires for submissions within its
375
- * subtree only.
383
+ * only. A submit closes the dropdown 90ms later (unless the root dropdown
384
+ * sets keepOpenOnSubmit), and the dropdown ignores any other submit in
385
+ * that time, so this isn’t called twice for a double press. For a nested
386
+ * (submenu) Dropdown, fires for submissions within its subtree only.
376
387
  */
377
388
  onSubmitItem?: (payload: Item) => void;
378
389
  /**
@@ -494,6 +505,17 @@ Typography, sizing, and spacing:
494
505
  | `--uktdd-submenu-gap` | `0px` (parent item ↔ submenu) |
495
506
  | `--uktdd-label-gap` | `0.625em` (`props.label` text ↔ trigger) |
496
507
 
508
+ The generated trigger’s chevron (see
509
+ [The trigger chevron](#the-trigger-chevron)):
510
+
511
+ | Property | Default |
512
+ | ----------------------------- | -------------------------------- |
513
+ | `--uktdd-trigger-icon` | the built-in chevron (or `none`) |
514
+ | `--uktdd-trigger-icon-size` | `0.7em` (the chevron’s width) |
515
+ | `--uktdd-trigger-icon-stroke` | 2/17 of the size (`≈ 0.08em`) |
516
+ | `--uktdd-trigger-icon-gap` | `0.5em` (trigger text ↔ chevron) |
517
+ | `--uktdd-trigger-icon-rotate` | `0deg` |
518
+
497
519
  Placement (see
498
520
  [Placement Customization](#placement-customization-with-css-variables) and
499
521
  [Submenu placement and styling](#submenu-placement-and-styling)):
@@ -523,7 +545,7 @@ part of the public API along with the `data-ukt-*` attributes:
523
545
  - `.uktdropdown` — the root, with the state classes `.is-open`,
524
546
  `.is-searchable`, and `.is-disabled`
525
547
  - `.uktdropdown-trigger` — the generated trigger (a custom trigger keeps
526
- its own classes)
548
+ its own classes), whose `::after` is the [chevron](#the-trigger-chevron)
527
549
  - `.uktdropdown-label` and `.uktdropdown-label-text` — the wrapper and text
528
550
  rendered for `props.label`
529
551
  - `.uktdropdown-body` — the anchored popover shell, with `.has-items`
@@ -536,6 +558,112 @@ part of the public API along with the `data-ukt-*` attributes:
536
558
  highlight, alongside `aria-expanded` on open parent items and
537
559
  `aria-selected` on the selected option)
538
560
 
561
+ ### The trigger chevron
562
+
563
+ The generated button trigger ends with a chevron that marks it as something
564
+ that opens. It’s an `::after` pseudo-element, so it adds no markup and
565
+ leaves the trigger’s accessible name alone, and it’s painted in the
566
+ trigger’s `currentColor`, so it follows the trigger’s text color (including
567
+ its disabled color). A searchable dropdown’s trigger is a text input, which
568
+ can’t have one, and [`Menubar`](#menubar) triggers don’t get one. A custom
569
+ trigger gets one only if it carries the `uktdropdown-trigger` class, since
570
+ the rule matches the class rather than the generated element.
571
+
572
+ The trigger is `white-space: nowrap`, like a native `<select>`, so the
573
+ chevron stays on the line with the label; a label too long for a
574
+ fixed-width trigger overflows it rather than wrapping. To let a long label
575
+ wrap, see the flex recipe at the end of this section.
576
+
577
+ The chevron is drawn with `clip-path: shape()`, so its stroke is a custom
578
+ property: `--uktdd-trigger-icon-stroke` sets it, and by default it stays
579
+ 2/17 of `--uktdd-trigger-icon-size`, so resizing the chevron keeps its
580
+ weight in proportion. Browsers without `shape()` (it’s
581
+ [Baseline 2026](https://developer.mozilla.org/en-US/docs/Web/CSS/basic-shape/shape))
582
+ draw the same chevron as an SVG mask instead, at the default proportion,
583
+ ignoring `--uktdd-trigger-icon-stroke`. Its box is square and as wide as
584
+ the visible chevron, so `--uktdd-trigger-icon-size` is the chevron’s width
585
+ and `--uktdd-trigger-icon-gap` is the actual space between it and the text.
586
+ In a flex trigger, that gap adds to the trigger’s own `gap`; set it to `0`
587
+ there.
588
+
589
+ To use your own icon, set `--uktdd-trigger-icon` to an image: an SVG
590
+ `url()` or a gradient, used as a mask so it still takes the trigger’s color
591
+ (`--uktdd-trigger-icon-stroke` doesn’t apply to it). Crop its `viewBox`
592
+ tight to the icon horizontally and keep it square, or built-in side margins
593
+ will make it look smaller and further from the text than the size and gap
594
+ suggest:
595
+
596
+ ```css
597
+ .uktdropdown-trigger {
598
+ --uktdd-trigger-icon: url('/icons/my-chevron.svg');
599
+ }
600
+ ```
601
+
602
+ A relative `url()` in this property resolves against the page, not your
603
+ stylesheet: the property is substituted in the dropdown’s own stylesheet,
604
+ which is inline. Use a root-relative, absolute or `data:` URL, or, for a
605
+ file next to your stylesheet, set the mask on the `::after` directly:
606
+
607
+ ```css
608
+ .uktdropdown-trigger::after {
609
+ clip-path: none;
610
+ mask: url('./my-chevron.svg') center / contain no-repeat;
611
+ }
612
+ ```
613
+
614
+ Set `--uktdd-trigger-icon-size` to suit the icon: cropped to its ink, a
615
+ dense icon reads larger than an outlined one at the same size (the
616
+ [`TriggerChevron` story][] uses `1em` for an arrow in a circle and `0.8em`
617
+ for a pointing hand). The box stays centered on the label at any size, but
618
+ the icon’s strokes scale with it, so a much smaller icon needs a heavier
619
+ `stroke-width` in its SVG to keep its weight.
620
+
621
+ [`TriggerChevron` story]:
622
+ https://uikit.acusti.ca/?path=/docs/uikit-controls-Dropdown--docs#trigger-chevron
623
+
624
+ The default rules have the specificity of a bare element, so a class
625
+ selector overrides them regardless of stylesheet order.
626
+
627
+ The chevron is the trigger’s `currentColor`. Setting `color` on the
628
+ `::after` recolors it, but that color then also holds in forced colors
629
+ mode, where the trigger’s own text takes the forced color.
630
+
631
+ To remove the chevron, set `--uktdd-trigger-icon` to `none`, on a class or
632
+ inline through the dropdown’s `style` prop
633
+ (`style={{ '--uktdd-trigger-icon': 'none' }}`):
634
+
635
+ ```css
636
+ .my-dropdown {
637
+ --uktdd-trigger-icon: none;
638
+ }
639
+ ```
640
+
641
+ This relies on container style queries
642
+ ([Baseline 2026](https://developer.mozilla.org/en-US/docs/Web/CSS/@container#container_style_queries)).
643
+ Without them (Firefox before 151), the chevron is hidden but keeps its
644
+ space. `all: revert` on the trigger’s `::after` removes it in every
645
+ browser, and leaves the `::after` free for an indicator of your own.
646
+
647
+ In a full-width trigger, the chevron trails the centered label. To pin it
648
+ to the inline end with the label at the start:
649
+
650
+ ```css
651
+ .uktdropdown-trigger {
652
+ display: flex;
653
+ align-items: center;
654
+ gap: var(--uktdd-trigger-icon-gap);
655
+ }
656
+
657
+ .uktdropdown-trigger::after {
658
+ margin-inline-start: auto;
659
+ }
660
+ ```
661
+
662
+ The same rule lets a long label wrap while the chevron keeps its own
663
+ column: add `white-space: normal` and `text-align: start`, and use
664
+ `align-items: first baseline` to keep the chevron beside the label’s first
665
+ line.
666
+
539
667
  ## Usage Examples
540
668
 
541
669
  ### Basic List Dropdown
@@ -763,6 +891,9 @@ Enter in such an input, in the body or the trigger, counts the way a form
763
891
  would count it: not when a handler has prevented the key, as a combobox
764
892
  does to pick an option, and not when it commits an IME composition.
765
893
 
894
+ The dropdown closes 90ms after a submit and ignores any other submit in
895
+ that time. The form’s own `onSubmit` still runs for every submit.
896
+
766
897
  When a submit closes the dropdown while focus is in its body, focus returns
767
898
  to the trigger, as it does on Escape.
768
899
 
@@ -841,8 +972,9 @@ properties to the corresponding values:
841
972
  (The `…try-fallback-N` columns are `--uktdd-body-position-try-fallback-1`
842
973
  and `-2`.)
843
974
 
844
- The two Top rows should also flip the last-resort fill pair to match:
845
- `--uktdd-body-fill-fallbacks: --uktdd-fill-top, --uktdd-fill-bottom`.
975
+ The two Top rows should also flip the last-resort fill pair to match
976
+ (`--uktdd-body-fill-fallbacks: --uktdd-fill-top, --uktdd-fill-bottom`) and
977
+ turn the trigger chevron up (`--uktdd-trigger-icon-rotate: 180deg`).
846
978
 
847
979
  For example, to make a dropdown open upward and end-aligned (useful for a
848
980
  trigger pinned to the bottom of the viewport, near the inline-end edge):
@@ -853,13 +985,17 @@ trigger pinned to the bottom of the viewport, near the inline-end edge):
853
985
  --uktdd-body-position-try-fallback-1: --uktdd-bottom-end;
854
986
  --uktdd-body-position-try-fallback-2: --uktdd-top-start;
855
987
  --uktdd-body-fill-fallbacks: --uktdd-fill-top, --uktdd-fill-bottom;
988
+ --uktdd-trigger-icon-rotate: 180deg;
856
989
  }
857
990
  ```
858
991
 
859
- The last line flips the last-resort fill pair to match the recipe’s
992
+ The fill line flips the last-resort fill pair to match the recipe’s
860
993
  direction: when the body is too tall to fit anywhere at its natural size,
861
994
  it fills and scrolls on the upward side first (more on the fill pair
862
- below).
995
+ below). The last line turns the [trigger chevron](#the-trigger-chevron) to
996
+ point in the recipe’s preferred direction. It’s static: when the primary
997
+ placement doesn’t fit and a fallback opens the body on the other side, the
998
+ chevron still points the preferred way.
863
999
 
864
1000
  See the [`DirectionRecipes` story][] for a live demo of all four.
865
1001
 
@@ -147,8 +147,10 @@ export type Props = {
147
147
  /**
148
148
  * Called when an item is submitted (click, Enter, or Space). Parent items
149
149
  * (submenus) disclose rather than submit, so this fires for leaf items
150
- * only. For a nested (submenu) Dropdown, fires for submissions within its
151
- * subtree only.
150
+ * only. A submit closes the dropdown 90ms later (unless the root dropdown
151
+ * sets keepOpenOnSubmit), and the dropdown ignores any other submit in
152
+ * that time, so this isn’t called twice for a double press. For a nested
153
+ * (submenu) Dropdown, fires for submissions within its subtree only.
152
154
  */
153
155
  onSubmitItem?: (payload: Item) => void;
154
156
  /**