#1726 - add ability to type arbitrary values that aren't in items

This commit is contained in:
McGaelen 2024-03-25 15:58:19 -04:00
commit 9ac8e34241
4 changed files with 89 additions and 23 deletions

View file

@ -1505,6 +1505,18 @@
"constant": false,
"reactive": true
},
{
"name": "allowArbitraryValues",
"kind": "let",
"description": "Set to `true` to allow values that aren't included in `items`",
"type": "boolean",
"value": "false",
"isFunction": false,
"isFunctionDeclaration": false,
"isRequired": false,
"constant": false,
"reactive": false
},
{
"name": "direction",
"kind": "let",

View file

@ -34,6 +34,19 @@ items={[
{id: "2", text: "Fax"}
]} />
## Arbitrary Values
Set `allowArbitraryValues` to `true` to allow the user to type in whatever value they want, even if it's not present in `items`.
`selectedId` will be set to `undefined` when the value is arbitrary.
<ComboBox allowArbitraryValues titleText="Contact" placeholder="Select contact method"
selectedId="1"
items={[
{id: "0", text: "Slack"},
{id: "1", text: "Email"},
{id: "2", text: "Fax"}
]} />
## Reactive example
<FileSource src="/framed/ComboBox/ReactiveComboBox" />

View file

@ -120,6 +120,11 @@
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.19.9.tgz#b2da6219b603e3fa371a78f53f5361260d0c5585"
integrity sha512-oxoQgglOP7RH6iasDrhY+R/3cHrfwIDvRlT4CGChflq6twk8iENeVvMJjmvBb94Ik1Z+93iGO27err7w6l54GQ==
"@ibm/telemetry-js@^1.2.1":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@ibm/telemetry-js/-/telemetry-js-1.3.0.tgz#321f2ed4bbbc78d69dc1ee9cb6f83d2d2af9baef"
integrity sha512-9gIkyF2B9RizWN6rsdQN76DN6D+/Xbr4HGTwm6EUujfXvEVtWbf4jzxDFwKvZkeTC2tjHpkUNJQKdivbMKt8yg==
"@jridgewell/gen-mapping@^0.3.0":
version "0.3.3"
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098"
@ -399,8 +404,9 @@ bufferutil@^4.0.1:
node-gyp-build "~3.7.0"
carbon-components-svelte@../:
version "0.82.5"
version "0.84.1"
dependencies:
"@ibm/telemetry-js" "^1.2.1"
flatpickr "4.6.9"
carbon-icons-svelte@^12.4.1:

View file

@ -27,6 +27,12 @@
/** Specify the selected combobox value */
export let value = "";
/**
* Allow values that aren't in `items`.
* @type {boolean}
*/
export let allowArbitraryValues = false
/**
* Specify the direction of the combobox dropdown menu
* @type {"bottom" | "top"}
@ -107,7 +113,7 @@
*/
export let listRef = null;
import { createEventDispatcher, afterUpdate, tick } from "svelte";
import { createEventDispatcher, afterUpdate, tick, onMount } from "svelte";
import Checkmark from "../icons/Checkmark.svelte";
import WarningFilled from "../icons/WarningFilled.svelte";
import WarningAltFilled from "../icons/WarningAltFilled.svelte";
@ -165,6 +171,12 @@
if (options?.focus !== false) ref?.focus();
}
onMount(() => {
if (selectedItem) {
value = itemToString(selectedItem)
}
})
afterUpdate(() => {
if (open) {
ref.focus();
@ -172,6 +184,7 @@
} else {
highlightedIndex = -1;
filteredItems = [];
if (!allowArbitraryValues) {
if (!selectedItem) {
selectedId = undefined;
value = "";
@ -182,6 +195,7 @@
value = itemToString(selectedItem);
}
}
}
});
$: if (selectedId !== undefined) {
@ -202,6 +216,39 @@
selectedItem = undefined;
}
function searchForMatchingValue() {
// searching typed value in text list with lowercase
let matchedItem =
filteredItems.find(
(e) =>
e.text.toLowerCase() === value?.toLowerCase() && !e.disabled
);
if (!allowArbitraryValues) {
// typed value has matched or fallback to first enabled item
matchedItem = matchedItem ?? filteredItems.find((e) => !e.disabled);
if (matchedItem) setMatchedItem(matchedItem)
} else {
// When allowing arbitrary values, we still want to select a value if the user types in one that exists.
// But if it doesn't exist, we don't try to fallback to another value.
if (matchedItem) {
setMatchedItem(matchedItem)
} else {
open = false;
selectedItem = undefined;
selectedId = undefined;
}
}
}
/** @param item {ComboBoxItem}*/
function setMatchedItem(item) {
open = false;
selectedItem = item;
value = itemToString(selectedItem);
selectedId = selectedItem.id;
}
$: ariaLabel = $$props["aria-label"] || "Choose an item";
$: menuId = `menu-${id}`;
$: comboId = `combo-${id}`;
@ -305,19 +352,7 @@
selectedId = filteredItems[highlightedIndex].id;
}
} else {
// searching typed value in text list with lowercase
const matchedItem =
filteredItems.find(
(e) =>
e.text.toLowerCase() === value?.toLowerCase() && !e.disabled
) ?? filteredItems.find((e) => !e.disabled);
if (matchedItem) {
// typed value has matched or fallback to first enabled item
open = false;
selectedItem = matchedItem;
value = itemToString(selectedItem);
selectedId = selectedItem.id;
}
searchForMatchingValue()
}
highlightedIndex = -1;
} else if (key === 'Tab') {