@stackline/react-multiselect-dropdown 19.1.1 → 19.1.2

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
@@ -15,7 +15,7 @@
15
15
  <img src="https://alexandro.net/images/public/2026/06/dropdownlist.gif" alt="@stackline/react-multiselect-dropdown live dropdown preview" width="420">
16
16
  </p>
17
17
 
18
- **Latest React 19 release:** `19.1.1` for React `19.x`
18
+ **Latest React 19 release:** `19.1.2` for React `19.x`
19
19
 
20
20
  ---
21
21
 
@@ -29,7 +29,7 @@
29
29
 
30
30
  The package is built around a controlled React API: pass `data`, bind `selectedItems`, receive updates through `onChange`, and customize behavior through a `settings` object. It also supports a Slots API for replacing component structure without losing ARIA/focus behavior, a headless `useMultiSelectDropdown` hook, a lower-level `useMultiSelectState` hook, custom React render functions for option rows and selected badges, lazy loading callbacks, imperative `ref` methods, and body-overlay positioning for dialogs or clipped containers.
31
31
 
32
- The current stable React 19 release is `19.1.1`. It adds guided structural slots, a headless hook, a state hook, a type-safe factory helper, and a strengthened combobox contract while keeping the styled `<MultiSelectDropdown />` component compatible with the existing visual contract.
32
+ The current stable React 19 release is `19.1.2`. It adds guided structural slots, a headless hook, a state hook, a type-safe factory helper, and a strengthened combobox contract while keeping the styled `<MultiSelectDropdown />` component compatible with the existing visual contract.
33
33
 
34
34
  ## Features
35
35
 
@@ -86,9 +86,9 @@ Each package family installs on its matching React family. Keep the package fami
86
86
 
87
87
  | Package family | React family | Peer range | Tested release window | Demo link |
88
88
  | :---: | :---: | :---: | :---: | :--- |
89
- | **17.x** | **React 17 only** | **`>=17.0.0 <18.0.0`** | **17.0.0 -> 17.0.2** | [React 17 family docs](https://alexandro.net/docs/react/multiselect/react-17/) |
90
- | **18.x** | **React 18 only** | **`>=18.0.0 <19.0.0`** | **18.0.0 -> 18.3.1** | [React 18 family docs](https://alexandro.net/docs/react/multiselect/react-18/) |
91
- | **19.x** | **React 19 only** | **`>=19.0.0 <20.0.0`** | **19.1.1 -> 19.2.4** | [React 19 family docs](https://alexandro.net/docs/react/multiselect/react-19/) |
89
+ | **17.x** | **React 17 only** | **`>=17.0.0 <18.0.0`** | **17.0.1 -> 17.0.2** | [React 17 family docs](https://alexandro.net/docs/react/multiselect/react-17/) |
90
+ | **18.x** | **React 18 only** | **`>=18.0.0 <19.0.0`** | **18.0.1 -> 18.3.1** | [React 18 family docs](https://alexandro.net/docs/react/multiselect/react-18/) |
91
+ | **19.x** | **React 19 only** | **`>=19.0.0 <20.0.0`** | **19.1.2 -> 19.2.4** | [React 19 family docs](https://alexandro.net/docs/react/multiselect/react-19/) |
92
92
 
93
93
  ## Installation
94
94
 
@@ -96,7 +96,7 @@ Each package family installs on its matching React family. Keep the package fami
96
96
  npm install @stackline/react-multiselect-dropdown@19.1.1 --save-exact
97
97
  ```
98
98
 
99
- Install `19.1.1` for React 19.x applications. The styled component includes its component styles and injects them at runtime. The headless hook does not inject CSS and lets your application own the markup and styling.
99
+ Install `19.1.2` for React 19.x applications. The styled component includes its component styles and injects them at runtime. The headless hook does not inject CSS and lets your application own the markup and styling.
100
100
 
101
101
  ## Setup
102
102
 
@@ -481,7 +481,7 @@ The styled component remains available for drop-in usage. The headless hooks are
481
481
 
482
482
  ## Combobox Contract
483
483
 
484
- Version `19.1.1` tightens the interaction details that usually matter most in production forms:
484
+ Version `19.1.2` tightens the interaction details that usually matter most in production forms:
485
485
 
486
486
  | Behavior | Contract |
487
487
  | :--- | :--- |
package/dist/index.cjs CHANGED
@@ -1518,6 +1518,11 @@ function InnerMultiSelectDropdown({
1518
1518
  return;
1519
1519
  }
1520
1520
  if (isSelected(item)) {
1521
+ if (settings.singleSelection) {
1522
+ closeDropdown(true);
1523
+ focusAfterSelectionChange("trigger");
1524
+ return;
1525
+ }
1521
1526
  removeItem(item, focusTarget);
1522
1527
  return;
1523
1528
  }
@@ -1928,7 +1933,7 @@ function InnerMultiSelectDropdown({
1928
1933
  }
1929
1934
  if (event.key === "Enter" || isSpaceKey(event.key)) {
1930
1935
  event.preventDefault();
1931
- const willClose = !isSelected(item) && (settings.singleSelection || settings.closeDropDownOnSelection);
1936
+ const willClose = settings.singleSelection || !isSelected(item) && settings.closeDropDownOnSelection;
1932
1937
  const currentOptionId = event.currentTarget.id;
1933
1938
  const moveToNextOption = isSpaceKey(event.key) && settings.keyboard.spaceOptionAction === "toggle-and-next";
1934
1939
  selectItem(item, willClose ? "trigger" : "none");
@@ -2120,7 +2125,7 @@ function InnerMultiSelectDropdown({
2120
2125
  if (disabled) {
2121
2126
  return;
2122
2127
  }
2123
- const willClose = !isSelected(item) && (settings.singleSelection || settings.closeDropDownOnSelection);
2128
+ const willClose = settings.singleSelection || !isSelected(item) && settings.closeDropDownOnSelection;
2124
2129
  selectItem(item, willClose ? "trigger" : "none");
2125
2130
  if (!willClose) {
2126
2131
  focusOptionAfterPointerSelection(optionId, optionIndex);
@@ -2610,6 +2615,10 @@ function useMultiSelectState({
2610
2615
  return;
2611
2616
  }
2612
2617
  if (isSelected(item)) {
2618
+ if (settings.singleSelection) {
2619
+ onSelectionShouldClose?.();
2620
+ return;
2621
+ }
2613
2622
  removeItem(item);
2614
2623
  return;
2615
2624
  }
@@ -2934,7 +2943,7 @@ function useMultiSelectDropdown({
2934
2943
  };
2935
2944
  const selectItem = (item, focusTarget = "search") => {
2936
2945
  const wasSelected = state.isSelected(item);
2937
- const willClose = !wasSelected && (settings.singleSelection || settings.closeDropDownOnSelection);
2946
+ const willClose = settings.singleSelection || !wasSelected && settings.closeDropDownOnSelection;
2938
2947
  state.selectItem(item);
2939
2948
  focusAfterSelectionChange(willClose ? "trigger" : focusTarget);
2940
2949
  };
@@ -3049,7 +3058,7 @@ function useMultiSelectDropdown({
3049
3058
  event.preventDefault();
3050
3059
  const enabledOptions2 = options.filter((currentOption) => !currentOption.disabled);
3051
3060
  const currentEnabledIndex2 = enabledOptions2.findIndex((currentOption) => currentOption.id === option.id);
3052
- const willClose = !state.isSelected(option.item) && (settings.singleSelection || settings.closeDropDownOnSelection);
3061
+ const willClose = settings.singleSelection || !state.isSelected(option.item) && settings.closeDropDownOnSelection;
3053
3062
  const moveToNextOption = isSpaceKey2(event.key) && settings.keyboard.spaceOptionAction === "toggle-and-next";
3054
3063
  selectItem(option.item, willClose ? "trigger" : "none");
3055
3064
  if (!willClose) {
@@ -3286,7 +3295,7 @@ function useMultiSelectDropdown({
3286
3295
  if (option.disabled) {
3287
3296
  return;
3288
3297
  }
3289
- const willClose = !state.isSelected(option.item) && (settings.singleSelection || settings.closeDropDownOnSelection);
3298
+ const willClose = settings.singleSelection || !state.isSelected(option.item) && settings.closeDropDownOnSelection;
3290
3299
  selectItem(option.item, willClose ? "trigger" : "none");
3291
3300
  if (!willClose) {
3292
3301
  setActiveOptionIndex(option.index);
package/dist/index.js CHANGED
@@ -1497,6 +1497,11 @@ function InnerMultiSelectDropdown({
1497
1497
  return;
1498
1498
  }
1499
1499
  if (isSelected(item)) {
1500
+ if (settings.singleSelection) {
1501
+ closeDropdown(true);
1502
+ focusAfterSelectionChange("trigger");
1503
+ return;
1504
+ }
1500
1505
  removeItem(item, focusTarget);
1501
1506
  return;
1502
1507
  }
@@ -1907,7 +1912,7 @@ function InnerMultiSelectDropdown({
1907
1912
  }
1908
1913
  if (event.key === "Enter" || isSpaceKey(event.key)) {
1909
1914
  event.preventDefault();
1910
- const willClose = !isSelected(item) && (settings.singleSelection || settings.closeDropDownOnSelection);
1915
+ const willClose = settings.singleSelection || !isSelected(item) && settings.closeDropDownOnSelection;
1911
1916
  const currentOptionId = event.currentTarget.id;
1912
1917
  const moveToNextOption = isSpaceKey(event.key) && settings.keyboard.spaceOptionAction === "toggle-and-next";
1913
1918
  selectItem(item, willClose ? "trigger" : "none");
@@ -2099,7 +2104,7 @@ function InnerMultiSelectDropdown({
2099
2104
  if (disabled) {
2100
2105
  return;
2101
2106
  }
2102
- const willClose = !isSelected(item) && (settings.singleSelection || settings.closeDropDownOnSelection);
2107
+ const willClose = settings.singleSelection || !isSelected(item) && settings.closeDropDownOnSelection;
2103
2108
  selectItem(item, willClose ? "trigger" : "none");
2104
2109
  if (!willClose) {
2105
2110
  focusOptionAfterPointerSelection(optionId, optionIndex);
@@ -2595,6 +2600,10 @@ function useMultiSelectState({
2595
2600
  return;
2596
2601
  }
2597
2602
  if (isSelected(item)) {
2603
+ if (settings.singleSelection) {
2604
+ onSelectionShouldClose?.();
2605
+ return;
2606
+ }
2598
2607
  removeItem(item);
2599
2608
  return;
2600
2609
  }
@@ -2919,7 +2928,7 @@ function useMultiSelectDropdown({
2919
2928
  };
2920
2929
  const selectItem = (item, focusTarget = "search") => {
2921
2930
  const wasSelected = state.isSelected(item);
2922
- const willClose = !wasSelected && (settings.singleSelection || settings.closeDropDownOnSelection);
2931
+ const willClose = settings.singleSelection || !wasSelected && settings.closeDropDownOnSelection;
2923
2932
  state.selectItem(item);
2924
2933
  focusAfterSelectionChange(willClose ? "trigger" : focusTarget);
2925
2934
  };
@@ -3034,7 +3043,7 @@ function useMultiSelectDropdown({
3034
3043
  event.preventDefault();
3035
3044
  const enabledOptions2 = options.filter((currentOption) => !currentOption.disabled);
3036
3045
  const currentEnabledIndex2 = enabledOptions2.findIndex((currentOption) => currentOption.id === option.id);
3037
- const willClose = !state.isSelected(option.item) && (settings.singleSelection || settings.closeDropDownOnSelection);
3046
+ const willClose = settings.singleSelection || !state.isSelected(option.item) && settings.closeDropDownOnSelection;
3038
3047
  const moveToNextOption = isSpaceKey2(event.key) && settings.keyboard.spaceOptionAction === "toggle-and-next";
3039
3048
  selectItem(option.item, willClose ? "trigger" : "none");
3040
3049
  if (!willClose) {
@@ -3271,7 +3280,7 @@ function useMultiSelectDropdown({
3271
3280
  if (option.disabled) {
3272
3281
  return;
3273
3282
  }
3274
- const willClose = !state.isSelected(option.item) && (settings.singleSelection || settings.closeDropDownOnSelection);
3283
+ const willClose = settings.singleSelection || !state.isSelected(option.item) && settings.closeDropDownOnSelection;
3275
3284
  selectItem(option.item, willClose ? "trigger" : "none");
3276
3285
  if (!willClose) {
3277
3286
  setActiveOptionIndex(option.index);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackline/react-multiselect-dropdown",
3
- "version": "19.1.1",
3
+ "version": "19.1.2",
4
4
  "description": "Maintained React 19 multiselect dropdown with accessibility-focused and keyboard/ARIA tested support, controlled state, slots, headless/state hooks, Stackline skins, body overlays, live docs, search, grouping, lazy loading, and custom renderers.",
5
5
  "keywords": [
6
6
  "react",