diff --git a/README.md b/README.md
index 1103238..929681a 100644
--- a/README.md
+++ b/README.md
@@ -40,7 +40,6 @@ npm i -D svelte-typeahead
Pass an array of objects to the `data` prop. Use the `extractor` to specify the key value to search on.
-
```svelte
+
+ item.state} selection={(item) => item.selected} on:select="{handleSelect} />
+```
+
+
+*Hint: Required items should match `selection` and `disabled` to be shown as selected and prevent them from unselection. Further styling may be needed.*
### Disable and Filter Items
Use the `filter` to filter Items out and `disable` to disable them in the result set.
+- Filtered items are not part of the results at all.
+- Disabled itesm receive the class `disbaled` and will not fire an `on:select` event.
Example for disabling and filtering items by their title length:
@@ -100,6 +122,7 @@ Example for disabling and filtering items by their title length:
```
+
Example for disabling items after selecting them:
@@ -107,11 +130,11 @@ Example for disabling items after selecting them:
- item.state} disable={(item) => item.selected} on:select="{handleSelect}" />
+ item.state} disable={(item) => item.disabled} on:select="{handleSelect}" />
```
@@ -129,18 +152,19 @@ Set `focusAfterSelect` to `true` to re-focus the search input after selecting a
### Props
-| Prop name | Value | Description |
-| :--------------- | :-------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------- |
-| value | `string` (default: `""`) | Input search value |
-| data | `T[]` (default: `[]`) | Items to search |
-| extract | `(T) => T` | Target an item key if `data` is an object array |
-| disable | `(T) => T` | Pass in a function to disable items. They will show up in the results list, but wont be selectable. |
-| filter | `(T) => T` | Pass in a function to filter items. Thei will be hidden and do not show up at all in the results list. |
-| autoselect | `boolean` (default: `true`) | Automatically select the first (top) result |
-| inputAfterSelect | `"update" or "clear" or "keep"`(default:`"update"`) | Set to `"clear"` to clear the `value` after selecting a result. Set to `"keep"` keep the search field unchanged after a selection. |
-| results | `FuzzyResult[]` (default: `[]`) | Raw fuzzy results from the [fuzzy](https://github.com/mattyork/fuzzy) module |
-| focusAfterSelect | `boolean` (default: `false`) | Set to `true` to re-focus the input after selecting a result. |
-| `...$$restProps` | (forwarded to `Search` component) | All other props are forwarded to the input element. |
+| Prop name | Value | Description |
+| :--------------- | :-------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------- |
+| value | `string` (default: `""`) | Input search value |
+| data | `T[]` (default: `[]`) | Items to search |
+| extract | `(T) => T` | Target an item key if `data` is an object array |
+| selection | `(T) => T` | Pass in a function to select items. They will reveice the class `selected`. |
+| disable | `(T) => T` | Pass in a function to disable items. They will show up in the results list and receive the class `disabled`, but wont be selectable. |
+| filter | `(T) => T` | Pass in a function to filter items. Thei will be hidden and do not show up at all in the results list. |
+| autoselect | `boolean` (default: `true`) | Automatically select the first (top) result |
+| inputAfterSelect | `"update" or "clear" or "keep"`(default:`"update"`) | Set to `"clear"` to clear the `value` after selecting a result. Set to `"keep"` keep the search field unchanged after a selection. |
+| results | `FuzzyResult[]` (default: `[]`) | Raw fuzzy results from the [fuzzy](https://github.com/mattyork/fuzzy) module |
+| focusAfterSelect | `boolean` (default: `false`) | Set to `true` to re-focus the input after selecting a result. |
+| `...$$restProps` | (forwarded to `Search` component) | All other props are forwarded to the input element. |
### Dispatched events
@@ -207,6 +231,10 @@ module.exports = {
Svelte version 3.31 or greater is required if using TypeScript.
+## Internet Explorer
+
+To make this component compatible with IE11 you'll need to polyfill `findIndex`.
+
## Changelog
[Changelog](CHANGELOG.md)
diff --git a/src/Typeahead.svelte b/src/Typeahead.svelte
index 0fbc7d7..f6c192e 100644
--- a/src/Typeahead.svelte
+++ b/src/Typeahead.svelte
@@ -15,6 +15,9 @@
/** @type {(item: Item) => Item} */
export let extract = (item) => item;
+
+ /** @type {(item: Item) => Item} */
+ export let selection = (item) => false;
/** @type {(item: Item) => Item} */
export let disable = (item) => false;
@@ -51,7 +54,7 @@
afterUpdate(() => {
if (prevResults !== resultsId && autoselect) {
- selectedIndex = 0;
+ selectedIndex = results.findIndex(result => !result.disabled);
}
if (prevResults !== resultsId) {
@@ -89,7 +92,11 @@
.filter(value, data, options)
.filter(({ score }) => score > 0)
.filter((result) => !filter(result.original))
- .map((result) => ({ ...result, disabled: disable(result.original) }));
+ .map((result)=> ({
+ ...result,
+ disabled: disable(result.original),
+ selected: selection(result.original)
+ }));
$: resultsId = results.map((result) => extract(result.original)).join("");
@@ -139,16 +146,17 @@
break;
case 'ArrowDown':
e.preventDefault();
- selectedIndex += 1;
- if (selectedIndex === results.length) {
- selectedIndex = 0;
+ for (selectedIndex++;(selectedIndex in results && results[selectedIndex].disabled); selectedIndex++);
+ if(!(selectedIndex in results) || (selectedIndex in results && results[selectedIndex].disabled)) {
+ selectedIndex = results.findIndex(result => !result.disabled);
}
break;
case 'ArrowUp':
e.preventDefault();
- selectedIndex -= 1;
- if (selectedIndex < 0) {
- selectedIndex = results.length - 1;
+ for (selectedIndex--;(selectedIndex in results && results[selectedIndex].disabled); selectedIndex--);
+ if(!(selectedIndex in results) || (selectedIndex in results && results[selectedIndex].disabled)) {
+ let reverseselectedIndex = results.slice().reverse().findIndex(result => !result.disabled) + 1;
+ selectedIndex = (reverseselectedIndex == -1) ? -1 : (results.length - reverseselectedIndex);
}
break;
case 'Escape':
@@ -171,7 +179,8 @@
{
@@ -228,18 +237,22 @@
background-color: #cacaca;
}
+ .active {
+ background-color: #d8e9f3;
+ }
+
.disabled {
opacity: 0.4;
cursor: not-allowed;
}
- :global([data-svelte-search] label) {
+ [data-svelte-typeahead] :global([data-svelte-search] label) {
margin-bottom: 0.25rem;
display: inline-flex;
font-size: 0.875rem;
}
- :global([data-svelte-search] input) {
+ [data-svelte-typeahead] :global([data-svelte-search] input) {
width: 100%;
padding: 0.5rem 0.75rem;
background: none;
@@ -249,7 +262,7 @@
border: 1px solid #e5e5e5;
}
- :global([data-svelte-search] input:focus) {
+ [data-svelte-typeahead] :global([data-svelte-search] input:focus) {
outline-color: #0f62fe;
outline-offset: 2px;
outline-width: 1px;
diff --git a/types/Typeahead.d.ts b/types/Typeahead.d.ts
index d1a65b7..6e66826 100644
--- a/types/Typeahead.d.ts
+++ b/types/Typeahead.d.ts
@@ -31,6 +31,11 @@ export interface TypeaheadProps extends SearchProps {
* @default (item) => item
*/
extract?: (item: Item) => Item;
+
+ /**
+ * @default (item) => item
+ */
+ selection?: (item: Item) => Item;
/**
* @default (item) => false