feat(ComboBox): selectedIndex -> selectedId (#1016)

* feat(breaking): selectedIndex -> selectedId in ComboBox

* docs: update ComboBox
This commit is contained in:
Koichi Kiyokawa 2022-01-18 23:37:55 +09:00 committed by GitHub
commit cde8a79fa8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 91 additions and 102 deletions

View file

@ -656,31 +656,31 @@ export interface ComboBoxItem {
### Props ### Props
| Prop name | Kind | Reactive | Type | Default value | Description | | Prop name | Kind | Reactive | Type | Default value | Description |
| :--------------- | :-------------------- | :------- | :---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | | :--------------- | :-------------------- | :------- | :---------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| listRef | <code>let</code> | Yes | <code>null &#124; HTMLDivElement</code> | <code>null</code> | Obtain a reference to the list HTML element | | listRef | <code>let</code> | Yes | <code>null &#124; HTMLDivElement</code> | <code>null</code> | Obtain a reference to the list HTML element |
| ref | <code>let</code> | Yes | <code>null &#124; HTMLInputElement</code> | <code>null</code> | Obtain a reference to the input HTML element | | ref | <code>let</code> | Yes | <code>null &#124; HTMLInputElement</code> | <code>null</code> | Obtain a reference to the input HTML element |
| open | <code>let</code> | Yes | <code>boolean</code> | <code>false</code> | Set to `true` to open the combobox menu dropdown | | open | <code>let</code> | Yes | <code>boolean</code> | <code>false</code> | Set to `true` to open the combobox menu dropdown |
| value | <code>let</code> | Yes | <code>string</code> | <code>""</code> | Specify the selected combobox value | | value | <code>let</code> | Yes | <code>string</code> | <code>""</code> | Specify the selected combobox value |
| selectedIndex | <code>let</code> | Yes | <code>number</code> | <code>-1</code> | Set the selected item by value index | | selectedId | <code>let</code> | Yes | <code>string</code> | -- | Set the selected item by value id |
| items | <code>let</code> | No | <code>ComboBoxItem[]</code> | <code>[]</code> | Set the combobox items | | items | <code>let</code> | No | <code>ComboBoxItem[]</code> | <code>[]</code> | Set the combobox items |
| itemToString | <code>let</code> | No | <code>(item: ComboBoxItem) => string</code> | <code>(item) => item.text &#124;&#124; item.id</code> | Override the display of a combobox item | | itemToString | <code>let</code> | No | <code>(item: ComboBoxItem) => string</code> | <code>(item) => item.text &#124;&#124; item.id</code> | Override the display of a combobox item |
| direction | <code>let</code> | No | <code>"bottom" &#124; "top"</code> | <code>"bottom"</code> | Specify the direction of the combobox dropdown menu | | direction | <code>let</code> | No | <code>"bottom" &#124; "top"</code> | <code>"bottom"</code> | Specify the direction of the combobox dropdown menu |
| size | <code>let</code> | No | <code>"sm" &#124; "xl"</code> | -- | Set the size of the combobox | | size | <code>let</code> | No | <code>"sm" &#124; "xl"</code> | -- | Set the size of the combobox |
| disabled | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to disable the combobox | | disabled | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to disable the combobox |
| titleText | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the title text of the combobox | | titleText | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the title text of the combobox |
| placeholder | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the placeholder text | | placeholder | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the placeholder text |
| helperText | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the helper text | | helperText | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the helper text |
| invalidText | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the invalid state text | | invalidText | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the invalid state text |
| invalid | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to indicate an invalid state | | invalid | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to indicate an invalid state |
| warn | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to indicate an warning state | | warn | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to indicate an warning state |
| warnText | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the warning state text | | warnText | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the warning state text |
| light | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to enable the light variant | | light | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to enable the light variant |
| shouldFilterItem | <code>let</code> | No | <code>(item: ComboBoxItem, value: string) => boolean</code> | <code>() => true</code> | Determine if an item should be filtered given the current combobox value | | shouldFilterItem | <code>let</code> | No | <code>(item: ComboBoxItem, value: string) => boolean</code> | <code>() => true</code> | Determine if an item should be filtered given the current combobox value |
| translateWithId | <code>let</code> | No | <code>(id: any) => string</code> | -- | Override the default translation ids | | translateWithId | <code>let</code> | No | <code>(id: any) => string</code> | -- | Override the default translation ids |
| id | <code>let</code> | No | <code>string</code> | <code>"ccs-" + Math.random().toString(36)</code> | Set an id for the list box component | | id | <code>let</code> | No | <code>string</code> | <code>"ccs-" + Math.random().toString(36)</code> | Set an id for the list box component |
| name | <code>let</code> | No | <code>string</code> | -- | Specify a name attribute for the input | | name | <code>let</code> | No | <code>string</code> | -- | Specify a name attribute for the input |
| clear | <code>function</code> | No | <code>(options?: { focus?: boolean; }) => void</code> | <code>() => { prevSelectedIndex = undefined; selectedIndex = -1; highlightedIndex = -1; highlightedId = undefined; selectedId = undefined; selectedItem = undefined; open = false; inputValue = ""; if (options?.focus !== false) ref?.focus(); }</code> | Clear the combo box programmatically | | clear | <code>function</code> | No | <code>(options?: { focus?: boolean; }) => void</code> | <code>() => { prevSelectedId = null; highlightedIndex = -1; highlightedId = undefined; selectedId = undefined; selectedItem = undefined; open = false; inputValue = ""; if (options?.focus !== false) ref?.focus(); }</code> | Clear the combo box programmatically |
### Slots ### Slots
@ -688,15 +688,15 @@ None.
### Events ### Events
| Event name | Type | Detail | | Event name | Type | Detail |
| :--------- | :--------- | :------------------------------------------------------------------------------------- | | :--------- | :--------- | :-------------------------------------------------------------- |
| select | dispatched | <code>{ selectedId: string; selectedIndex: number; selectedItem: ComboBoxItem }</code> | | select | dispatched | <code>{ selectedId: string; selectedItem: ComboBoxItem }</code> |
| keydown | forwarded | -- | | keydown | forwarded | -- |
| keyup | forwarded | -- | | keyup | forwarded | -- |
| focus | forwarded | -- | | focus | forwarded | -- |
| blur | forwarded | -- | | blur | forwarded | -- |
| clear | forwarded | -- | | clear | forwarded | -- |
| scroll | forwarded | -- | | scroll | forwarded | -- |
## `ComposedModal` ## `ComposedModal`

View file

@ -1395,11 +1395,10 @@
"reactive": false "reactive": false
}, },
{ {
"name": "selectedIndex", "name": "selectedId",
"kind": "let", "kind": "let",
"description": "Set the selected item by value index", "description": "Set the selected item by value id",
"type": "number", "type": "string",
"value": "-1",
"isFunction": false, "isFunction": false,
"isFunctionDeclaration": false, "isFunctionDeclaration": false,
"constant": false, "constant": false,
@ -1616,7 +1615,7 @@
"kind": "function", "kind": "function",
"description": "Clear the combo box programmatically", "description": "Clear the combo box programmatically",
"type": "(options?: { focus?: boolean; }) => void", "type": "(options?: { focus?: boolean; }) => void",
"value": "() => { prevSelectedIndex = undefined; selectedIndex = -1; highlightedIndex = -1; highlightedId = undefined; selectedId = undefined; selectedItem = undefined; open = false; inputValue = \"\"; if (options?.focus !== false) ref?.focus(); }", "value": "() => { prevSelectedId = null; highlightedIndex = -1; highlightedId = undefined; selectedId = undefined; selectedItem = undefined; open = false; inputValue = \"\"; if (options?.focus !== false) ref?.focus(); }",
"isFunction": true, "isFunction": true,
"isFunctionDeclaration": true, "isFunctionDeclaration": true,
"constant": false, "constant": false,
@ -1628,7 +1627,7 @@
{ {
"type": "dispatched", "type": "dispatched",
"name": "select", "name": "select",
"detail": "{ selectedId: string; selectedIndex: number; selectedItem: ComboBoxItem }" "detail": "{ selectedId: string; selectedItem: ComboBoxItem }"
}, },
{ "type": "forwarded", "name": "keydown", "element": "input" }, { "type": "forwarded", "name": "keydown", "element": "input" },
{ "type": "forwarded", "name": "keyup", "element": "input" }, { "type": "forwarded", "name": "keyup", "element": "input" },

View file

@ -12,10 +12,10 @@ items={[
{id: "2", text: "Fax"} {id: "2", text: "Fax"}
]} /> ]} />
### Selected index ### Selected id
<ComboBox titleText="Contact" placeholder="Select contact method" <ComboBox titleText="Contact" placeholder="Select contact method"
selectedIndex={1} selectedId="1"
items={[ items={[
{id: "0", text: "Slack"}, {id: "0", text: "Slack"},
{id: "1", text: "Email"}, {id: "1", text: "Email"},

View file

@ -7,7 +7,7 @@
<ComboBox <ComboBox
titleText="Contact" titleText="Contact"
placeholder="Select contact method" placeholder="Select contact method"
selectedIndex="{1}" selectedId="1"
bind:this="{ref}" bind:this="{ref}"
items="{[ items="{[
{ id: '0', text: 'Slack' }, { id: '0', text: 'Slack' },

View file

@ -7,17 +7,18 @@
{ id: "2", text: "Fax" }, { id: "2", text: "Fax" },
]; ];
let comboBox1_selectedIndex = -1; let comboBox1_selectedId = undefined;
let comboBox2_selectedIndex = -1; let comboBox2_selectedId = undefined;
const formatSelected = (i) => (items[i] ? items[i].text : "N/A"); const formatSelected = (id) =>
items.find((item) => item.id === id)?.text ?? "N/A";
$: primary = formatSelected(comboBox1_selectedIndex); $: primary = formatSelected(comboBox1_selectedId);
$: secondary = formatSelected(comboBox2_selectedIndex); $: secondary = formatSelected(comboBox2_selectedId);
</script> </script>
<ComboBox <ComboBox
bind:selectedIndex="{comboBox1_selectedIndex}" bind:selectedId="{comboBox1_selectedId}"
titleText="Primary contact" titleText="Primary contact"
placeholder="Select primary contact method" placeholder="Select primary contact method"
items="{items}" items="{items}"
@ -26,7 +27,7 @@
<div>Primary: {primary}</div> <div>Primary: {primary}</div>
<ComboBox <ComboBox
bind:selectedIndex="{comboBox2_selectedIndex}" bind:selectedId="{comboBox2_selectedId}"
titleText="Secondary contact" titleText="Secondary contact"
placeholder="Select secondary contact method" placeholder="Select secondary contact method"
items="{items}" items="{items}"

View file

@ -1,12 +1,12 @@
<script> <script>
import { ComboBox, Button } from "carbon-components-svelte"; import { ComboBox, Button } from "carbon-components-svelte";
let selectedIndex = 1; let selectedId = "1";
</script> </script>
<ComboBox <ComboBox
titleText="Contact" titleText="Contact"
placeholder="Select contact method" placeholder="Select contact method"
bind:selectedIndex bind:selectedId
items="{[ items="{[
{ id: '0', text: 'Slack' }, { id: '0', text: 'Slack' },
{ id: '1', text: 'Email' }, { id: '1', text: 'Email' },
@ -14,5 +14,7 @@
]}" ]}"
/> />
<br /> <br />
<Button on:click="{() => (selectedIndex = -1)}">Set to -1 (unselected)</Button> <Button on:click="{() => (selectedId = undefined)}"
<Button on:click="{() => (selectedIndex = 2)}">Set to 2 (Fax)</Button> >Set to undefined (unselected)</Button
>
<Button on:click="{() => (selectedId = '2')}">Set to 2 (Fax)</Button>

View file

@ -1,7 +1,7 @@
<script> <script>
/** /**
* @typedef {{ id: string; text: string; }} ComboBoxItem * @typedef {{ id: string; text: string; }} ComboBoxItem
* @event {{ selectedId: string; selectedIndex: number; selectedItem: ComboBoxItem }} select * @event {{ selectedId: string; selectedItem: ComboBoxItem }} select
*/ */
/** /**
@ -16,8 +16,11 @@
*/ */
export let itemToString = (item) => item.text || item.id; export let itemToString = (item) => item.text || item.id;
/** Set the selected item by value index */ /**
export let selectedIndex = -1; * Set the selected item by value id
* @type {string}
*/
export let selectedId = undefined;
/** Specify the selected combobox value */ /** Specify the selected combobox value */
export let value = ""; export let value = "";
@ -107,10 +110,9 @@
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
let selectedId = undefined;
let selectedItem = undefined; let selectedItem = undefined;
let inputValue = value; let inputValue = value;
let prevSelectedIndex = undefined; let prevSelectedId = null;
let highlightedIndex = -1; let highlightedIndex = -1;
function change(dir) { function change(dir) {
@ -129,8 +131,7 @@
* @type {(options?: { focus?: boolean; }) => void} * @type {(options?: { focus?: boolean; }) => void}
*/ */
export function clear(options = {}) { export function clear(options = {}) {
prevSelectedIndex = undefined; prevSelectedId = null;
selectedIndex = -1;
highlightedIndex = -1; highlightedIndex = -1;
highlightedId = undefined; highlightedId = undefined;
selectedId = undefined; selectedId = undefined;
@ -149,34 +150,31 @@
filteredItems = []; filteredItems = [];
if (!selectedItem) { if (!selectedItem) {
selectedId = undefined; selectedId = undefined;
selectedIndex = -1;
inputValue = ""; inputValue = "";
highlightedIndex = -1; highlightedIndex = -1;
highlightedId = undefined; highlightedId = undefined;
prevSelectedIndex = undefined;
} else { } else {
// programmatically set selectedIndex // programmatically set inputValue
inputValue = selectedItem.text; inputValue = selectedItem.text;
} }
} }
}); });
$: if (selectedIndex > -1) { $: if (selectedId !== undefined) {
if (prevSelectedIndex !== selectedIndex) { if (prevSelectedId !== selectedId) {
prevSelectedIndex = selectedIndex; prevSelectedId = selectedId;
if (filteredItems?.length === 1 && open) { if (filteredItems?.length === 1 && open) {
selectedId = filteredItems[0].id; selectedId = filteredItems[0].id;
selectedItem = filteredItems[0]; selectedItem = filteredItems[0];
highlightedIndex = -1; highlightedIndex = -1;
highlightedId = undefined; highlightedId = undefined;
} else { } else {
selectedId = items[selectedIndex].id; selectedItem = items.find((item) => item.id === selectedId);
selectedItem = items[selectedIndex];
} }
dispatch("select", { selectedId, selectedIndex, selectedItem }); dispatch("select", { selectedId, selectedItem });
} }
} else { } else {
prevSelectedIndex = selectedIndex; prevSelectedId = selectedId;
selectedItem = undefined; selectedItem = undefined;
} }
@ -269,29 +267,25 @@
if (key === 'Enter') { if (key === 'Enter') {
open = !open; open = !open;
if (highlightedIndex > -1 && highlightedIndex !== selectedIndex) { if (
selectedIndex = highlightedIndex; highlightedIndex > -1 &&
filteredItems[highlightedIndex]?.id !== selectedId
) {
open = false; open = false;
highlightedIndex = -1; if (filteredItems[highlightedIndex]) {
if (filteredItems[selectedIndex]) { inputValue = filteredItems[highlightedIndex].text;
inputValue = filteredItems[selectedIndex].text; selectedItem = filteredItems[highlightedIndex];
selectedItem = filteredItems[selectedIndex]; selectedId = filteredItems[highlightedIndex].id;
selectedId = filteredItems[selectedIndex].id;
} }
selectedIndex = items.findIndex((item) => item.id === selectedId);
} else { } else {
selectedIndex = 0;
open = false; open = false;
highlightedIndex = -1; if (filteredItems[0]) {
if (filteredItems[selectedIndex]) { inputValue = filteredItems[0].text;
inputValue = filteredItems[selectedIndex].text; selectedItem = filteredItems[0];
selectedItem = filteredItems[selectedIndex]; selectedId = filteredItems[0].id;
selectedId = filteredItems[selectedIndex].id;
selectedIndex = items.findIndex(
(item) => item.id === selectedId
);
} }
} }
highlightedIndex = -1;
} else if (key === 'Tab') { } else if (key === 'Tab') {
open = false; open = false;
} else if (key === 'ArrowDown') { } else if (key === 'ArrowDown') {
@ -354,12 +348,10 @@
{#each filteredItems as item, i (item.id)} {#each filteredItems as item, i (item.id)}
<ListBoxMenuItem <ListBoxMenuItem
id="{item.id}" id="{item.id}"
active="{selectedIndex === i || selectedId === item.id}" active="{selectedId === item.id}"
highlighted="{highlightedIndex === i || selectedIndex === i}" highlighted="{highlightedIndex === i}"
on:click="{() => { on:click="{() => {
selectedIndex = items selectedId = item.id;
.map(({ id }) => id)
.indexOf(filteredItems[i].id);
open = false; open = false;
if (filteredItems[i]) { if (filteredItems[i]) {

View file

@ -25,7 +25,7 @@
<ComboBox <ComboBox
titleText="Contact" titleText="Contact"
placeholder="Select contact method" placeholder="Select contact method"
selectedIndex="{1}" selectedId="1"
items="{items}" items="{items}"
/> />

View file

@ -21,10 +21,9 @@ export interface ComboBoxProps
itemToString?: (item: ComboBoxItem) => string; itemToString?: (item: ComboBoxItem) => string;
/** /**
* Set the selected item by value index * Set the selected item by value id
* @default -1
*/ */
selectedIndex?: number; selectedId?: string;
/** /**
* Specify the selected combobox value * Specify the selected combobox value
@ -141,11 +140,7 @@ export interface ComboBoxProps
export default class ComboBox extends SvelteComponentTyped< export default class ComboBox extends SvelteComponentTyped<
ComboBoxProps, ComboBoxProps,
{ {
select: CustomEvent<{ select: CustomEvent<{ selectedId: string; selectedItem: ComboBoxItem }>;
selectedId: string;
selectedIndex: number;
selectedItem: ComboBoxItem;
}>;
keydown: WindowEventMap["keydown"]; keydown: WindowEventMap["keydown"];
keyup: WindowEventMap["keyup"]; keyup: WindowEventMap["keyup"];
focus: WindowEventMap["focus"]; focus: WindowEventMap["focus"];