├── .github └── workflows │ ├── coverage.yml │ ├── pre-commit.yml │ ├── publish.yml │ └── test.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .project ├── .settings ├── .jsdtscope ├── org.eclipse.wst.jsdt.ui.superType.container └── org.eclipse.wst.jsdt.ui.superType.name ├── LICENSE ├── README.md ├── babel.config.js ├── demo ├── ActivityIndicatorExample.svelte ├── AdvancedExample.svelte ├── App.svelte ├── AsyncExample.svelte ├── AsyncGeneratorExample.svelte ├── AsyncPreloadedExample.svelte ├── CreatableExample.svelte ├── CustomFunctionsExample.svelte ├── CustomizationExample.svelte ├── LockedExample.svelte ├── MatchingStrategyExample.svelte ├── MultipleExample.svelte ├── ReadOnlyExample.svelte ├── RequiredExample.svelte ├── SimpleExample.svelte └── main.js ├── jest.config.js ├── package-lock.json ├── package.json ├── public ├── CNAME ├── bulma.css ├── default.css └── index.html ├── rollup.config.js ├── rollup.dev.config.js ├── src ├── SimpleAutocomplete.svelte └── tests │ ├── async.test.ts │ ├── highlight.test.ts │ ├── multiple.test.ts │ └── sync.test.ts └── tsconfig.json /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "coverage" 3 | on: 4 | pull_request: 5 | jobs: 6 | coverage: 7 | permissions: 8 | checks: write 9 | pull-requests: write 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: ArtiomTr/jest-coverage-report-action@v2 14 | -------------------------------------------------------------------------------- /.github/workflows/pre-commit.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: pre-commit 3 | 4 | on: 5 | pull_request: 6 | push: 7 | branches: [master] 8 | 9 | jobs: 10 | pre-commit: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions/setup-python@v2 15 | - uses: pre-commit/action@v2.0.3 16 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: publish 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v1 13 | - uses: actions/setup-node@v1 14 | with: 15 | node-version: 12 16 | - run: npm ci 17 | - run: npm test 18 | 19 | publish-npm: 20 | needs: build 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v1 24 | - uses: actions/setup-node@v1 25 | with: 26 | node-version: 12 27 | registry-url: https://registry.npmjs.org/ 28 | - run: npm ci 29 | - run: npm publish 30 | env: 31 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 32 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CI 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - name: Install modules 11 | run: npm install 12 | - name: Run tests 13 | run: npm test 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | public/bundle.* 4 | index.js 5 | index.mjs 6 | coverage 7 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | repos: 3 | - repo: https://github.com/pre-commit/pre-commit-hooks 4 | rev: v4.2.0 5 | hooks: 6 | - id: check-byte-order-marker 7 | - id: trailing-whitespace 8 | - id: end-of-file-fixer 9 | - repo: https://github.com/pre-commit/mirrors-prettier 10 | rev: v2.6.2 11 | hooks: 12 | - id: prettier 13 | types_or: [css, javascript, html, svelte, yaml] 14 | additional_dependencies: 15 | - prettier@2.1.2 16 | - prettier-plugin-svelte@2.5.1 17 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | svelte-simple-autocomplete 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.wst.validation.validationbuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.wst.jsdt.core.jsNature 16 | 17 | 18 | -------------------------------------------------------------------------------- /.settings/.jsdtscope: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.jsdt.ui.superType.container: -------------------------------------------------------------------------------- 1 | org.eclipse.wst.jsdt.launching.JRE_CONTAINER 2 | -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.jsdt.ui.superType.name: -------------------------------------------------------------------------------- 1 | Global 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 these people](https://github.com/pstanoev/simple-svelte-autocomplete/graphs/contributors) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple Svelte Autocomplete 2 | 3 | Autocomplete / Select / Typeahead component made with [Svelte](https://svelte.dev/) 4 | 5 | ### Live demo http://simple-svelte-autocomplete.surge.sh/ 6 | 7 | - no dependencies 8 | - use plain lists or array of objects 9 | - option to define a label field or function 10 | - option to define more fields used for search 11 | - support for async load of items 12 | - can hold one or several values 13 | 14 | ## Install 15 | 16 | ```bash 17 | npm i -D simple-svelte-autocomplete 18 | ``` 19 | 20 | ## Usage 21 | 22 | Import the component and define items: 23 | 24 | ```javascript 25 | import AutoComplete from "simple-svelte-autocomplete" 26 | 27 | const colors = ["White", "Red", "Yellow", "Green", "Blue", "Black"] 28 | let selectedColor 29 | ``` 30 | 31 | And use it like this: 32 | 33 | ```html 34 | 35 | ``` 36 | 37 | You can also use it with array of objects: 38 | 39 | ```javascript 40 | const colorList = [ 41 | { id: 1, name: "White", code: "#FFFFFF" }, 42 | { id: 2, name: "Red", code: "#FF0000" }, 43 | { id: 3, name: "Yellow", code: "#FF00FF" }, 44 | { id: 4, name: "Green", code: "#00FF00" }, 45 | { id: 5, name: "Blue", code: "#0000FF" }, 46 | { id: 6, name: "Black", code: "#000000" }, 47 | ] 48 | 49 | let selectedColorObject 50 | ``` 51 | 52 | Just define which field should be used as label: 53 | 54 | ```html 55 | 56 | ``` 57 | 58 | Specifying function for label instead of field name is also supported: 59 | 60 | ```html 61 | 62 | color.id + '. ' + color.name} /> 63 | ``` 64 | 65 | By default the component searches by the item label, but it can also search by custom fields by specifying `keywords` function. For example to enable searching by color name and color HEX code: 66 | 67 | ```html 68 | color.name + ' ' + color.code} /> 70 | ``` 71 | 72 | ## Asynchronous loading of items 73 | 74 | Define a `searchFunction` which will be called with `keyword` and `maxItemsToShowInList` parameters. 75 | If you have `searchFunction` defined you don't need to specify `items` since the function will be used for loading. 76 | The `delay` parameter specifies the time to wait between user input and calling the `searchFunction`. 77 | It is recommend that delay > 200ms is set when using a remote search function to avoid sending too many requests. 78 | The `localFiltering` parameter can be set to false if the search function already returns filtered items according to the user input. 79 | 80 | ```html 81 | 89 | ``` 90 | 91 | ```js 92 | async function getItems(keyword) { 93 | const url = "/api/my-items/?format=json&name=" + encodeURIComponent(keyword) 94 | 95 | const response = await fetch(url) 96 | const json = await response.json() 97 | 98 | return json.results 99 | } 100 | ``` 101 | 102 | ```json 103 | { 104 | "results": [ 105 | { 106 | "id": 1, 107 | "name": "Sample One", 108 | "date": "2020-09-25" 109 | }, 110 | { 111 | "id": 2, 112 | "name": "Sample Two", 113 | "date": "2020-09-26" 114 | } 115 | ] 116 | } 117 | ``` 118 | 119 | ## Properties 120 | 121 | ### Behaviour 122 | 123 | - `items` - array of items the user can select from (optional, use `searchFunction` for async loading of items) 124 | - `searchFunction` - optional function to load items asynchronously from HTTP call for example, the searchFunction can also return all items and additional local search will still be performed 125 | - `delay` - delay in milliseconds to wait after user input to do the local searching or call `searchFunction` if provided, defaults to 0 126 | - `localFiltering` - boolean specifying if `searchFunction` is used, to still perform local filtering of the items to only ones that match the user input, defaults to true 127 | - `localSorting` - boolean specifying if result items should be sorted locally by `itemSortFunction` or `sortByMatchedKeywords`. If set to false, no local sorting will be done 128 | - `cleanUserText` - by default the component removes special characters and spaces from the user entered text, set `cleanUserText=false` to prevent this 129 | - `multiple` - enable multiple selection (false by default) 130 | - `orderableSelection` - enable selection reordering with drag and drop. needs multiple = true 131 | - `selectedItem` - the current item that is selected (object if the array of items contains objects) 132 | - `highlightedItem` - the current item that is highlighted in the dropdown menu 133 | - `labelFieldName` - the name of the field to be used for showing the items as text in the dropdown 134 | - `keywordsFieldName` - the name of the field to search by, if not specified the label will be used 135 | - `value` - derived value from the `selectedItem`, equals to `selectedItem` if `valueFieldName` is not specified 136 | - `valueFieldName` - field to use to derive the value from the selected item 137 | - `labelFunction` - optional function that creates label from the item. If used `labelFieldName` is ignored 138 | - `keywordsFunction` - optional function that creates text to search from the item. If used `keywordsFieldName` is ignored 139 | - `valueFunction` - optional function that derives the value from the selected item. If used `valueFieldName` is ignored 140 | - `keywordsCleanFunction` - optional function to additionally process the derived keywords from the item 141 | - `lowercaseKeywords` - set to false to not lowercase the keywords extracted from the items 142 | - `textCleanFunction` - optional function to additionally process the user entered text. Ignored if `cleanUserText=false` 143 | - `selectFirstIfEmpty` - set to true to select the first item if the user clears the text and closes the dropdown, defaults to false 144 | - `minCharactersToSearch` - minimum length of search text to perform search, defaults to 1 145 | - `maxItemsToShowInList` - maximum number of items to show in the dropdown list, defaults 0 (no limit) 146 | - `ignoreAccents` - ignores the accents/umlauts (è,ü,ö etc) to match items, defaults to true 147 | - `matchAllKeywords` - defaults to true, if false, any item will be suggested if it shares at least one common keyword with the input. Ignored if sorting function is given with `itemSortFunction` 148 | - `sortByMatchedKeywords` - defaults to false, if true, items are sorted by the number of matched keywords, only useful if `matchAllKeywords` is false. Ignored if sorting function is given with `itemSortFunction` 149 | - `itemSortFunction` - Optional custom function to order items. Parameters are two list items to compare and the cleaned up search query. Returns an integer indicating wether the first item comes before the seconde one. Only used if `sortByMatchedKeywords` is true. 150 | - `itemFilterFunction` - Optional custom function to filter items. Parameters are a list item and the cleaned up search query. Returns a boolean indicated wether to keep the item. If this is used, the `keywordsFieldName` and `keywordsFunction` are ignored 151 | 152 | - `disabled` - disable the control completely 153 | - `readonly` - make the input readonly, no user entered text (simulates combobox), item from the list can still be selected 154 | - `lock` - defaults to false, locks the input for user entered text when an item has been selected 155 | - `create` - true to enable accepting of unlisted values 156 | - `closeOnBlur` - true to close the dropdown when the component loses focus 157 | - `debug` - flag to enable detailed log statements from the component 158 | 159 | ### Events 160 | 161 | - `beforeChange` - function called before a new value is selected 162 | - `onChange` - function called after new value is selected 163 | - `onFocus` - function called on focus of the input control 164 | - `onBlur` - function called on blur of the input control 165 | - `onCreate` - function called when `create` is true and the user presses enter, the function must return add the created item to the `items` array and return it 166 | 167 | ### UI options 168 | 169 | - `placeholder` - change the text displayed when no option is selected 170 | - `noResultsText` - text to show in the dropdown when the search text does not match any item. Defaults to "No results found". Can be set to "" to not show anything. 171 | - `moreItemsText` - text displayed when the user text matches a lot of items and we can display only up to `maxItemsToShowInList` items 172 | - `createText` - text to show when `create` is true, and the user text doesn't match any of the items 173 | - `hideArrow` - set to true to not show the blue dropdown arrow 174 | - `showClear` - set to true to show X button that can be used to deselect the selected item 175 | - `showLoadingIndicator` - defaults to false, set to true to show loading spinner when the async `searchFunction` is executed, bulma class 'is-loading' is added to the input control 176 | 177 | ### CSS classes and IDs 178 | 179 | - `className` - apply a className to the control 180 | - `inputClassName` - apply a className to the input control 181 | - `noInputStyles` - set to true to disable the `input autocomplete-input` classes which are added to the input by default 182 | - `inputId` - apply an id attribute to the the input control 183 | - `dropdownClassName` - apply a className to the dropdown div showing the list of items 184 | - `name` - generate an HTML input with this name, containing the current value 185 | - `html5autocomplete` - value to enable or disable the [HTML5 autocomplete](https://developer.mozilla.org/en/docs/Web/HTML/Element/form#attr-autocomplete) attribute. 186 | - `autocompleteOffValue` - the value when `html5autocomplete=false`, defaults to `off` but can be set to `none` for Chrome 187 | - `selectName` - apply a name attribute to the tag that holds the selected value 189 | - `required` - adds the `required` attribute to the input 190 | - `tabIndex` - adds the `tabIndex` attribute https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex 191 | 192 | ### UI Slots 193 | 194 | - `item` - change the apearance of items in the dropdown list: 195 | 196 | ```html 197 |
198 | {@html label} 199 | 200 | 201 | {item.propertyY} 202 |
203 | ``` 204 | 205 | - `no-results` - customize the div that shows the "no results" text: 206 | 207 | ```html 208 |
209 | {noResultsText} 210 |
211 | ``` 212 | 213 | The noResultsText variable is optional and can be ommited. 214 | 215 | - `loading` - customize the div that shows the "loading" text: 216 | 217 | ```html 218 |
219 | {loadingText} 220 |
221 | ``` 222 | 223 | - `tag` - customize the tag blocks displayed when multiple selection is enabled: 224 | 225 | ```html 226 |
227 | {label} 228 | 229 | 230 | ``` 231 | 232 | - `dropdown-header` - customize what is displayed before the item list in the dropdown. By default there is nothing displayed. 233 | 234 | ```html 235 |
236 | 237 | 238 |
239 | ``` 240 | 241 | - `dropdown-footer` - customize what is displayed after the item list in the dropdown. By default this is where the `moreItemsText` is displayed if there are too many items to be displayed. 242 | 243 | ```html 244 |
245 | ... 246 |
247 | ``` 248 | 249 | #### CSS properties 250 | 251 | - `autocomplete` the class applied to the main control 252 | - `autocomplete-input` the class applied to the input list 253 | - `autocomplete-list` the class applied to the dropdown list 254 | - `autocomplete-list-item` the class applied to items in the dropdown list 255 | 256 | Note: Setting `noInputStyles=true` will disable the use of the `autocomplete-input` class above. 257 | 258 | Use global to apply styles, example: 259 | ``` 260 | .parent :global(.childClass) { 261 | color: red; 262 | } 263 | 264 | ``` 265 | 266 | The component is inteded to use with [Bulma](https://bulma.io/) but it can be adapted to use Boostrap or anything else. 267 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | targets: { 7 | node: "current", 8 | }, 9 | }, 10 | ], 11 | ], 12 | } 13 | -------------------------------------------------------------------------------- /demo/ActivityIndicatorExample.svelte: -------------------------------------------------------------------------------- 1 | 26 | 27 |
28 |

Loading indicator

29 |
30 |
31 | When showLoadingIndicator is set to true, loading spinner is shown when the async 32 | searchFunction is executed. 33 |
34 | Bulma class 'is-loading' is added to the input control. If you do not use Bulma you need to add 35 | style for the 'is-loading' class. 36 |
37 |
38 | 39 |
40 |
41 |
42 |
43 |

Pick a color

44 |
45 |
46 | 47 |
48 |
49 |
50 | 51 |
52 |
53 |
54 |

Code

55 |
56 |
57 | 58 |
59 |
60 |
61 |
62 |
63 | -------------------------------------------------------------------------------- /demo/AdvancedExample.svelte: -------------------------------------------------------------------------------- 1 | 43 | 44 |
45 |

Objects selection

46 |
47 |
48 | If you have an array of objects as items, they can be feed directly into the AutoComplete 49 | component and you can specify which field to use as label. 50 |
51 | You can also specifiy a function to combine multiple fields to search by. Try searching by HEX 52 | code of a color. 53 |
54 |
55 | 56 |
57 |
58 |
59 |
60 |

Pick a color

61 |
62 |
63 | Selected color item: {JSON.stringify(selectedColorObject)} 64 |
65 | Highlighted color item: 66 | {highlightedColorObject ? highlightedColorObject.name : highlightedColorObject} 69 |
70 | Selected value: {selectedColorValue} 71 | 72 | color.name + " " + color.code} 80 | showClear={true} 81 | hideArrow={false} 82 | placeholder="Please select color" 83 | /> 84 | 85 |
86 |

Change selected item from outside:

87 | 93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |

Code

101 |
102 |
103 | 104 |
105 |
106 |
107 |
108 |
109 | -------------------------------------------------------------------------------- /demo/App.svelte: -------------------------------------------------------------------------------- 1 | 48 | 49 | 50 | {@html solarized} 51 | 52 | 53 | 54 |
55 |
56 |

Svelte Simple Autocomplete Demo

57 |

58 | 59 | 60 | https://github.com/pstanoev/simple-svelte-autocomplete 61 | 62 |

63 |
64 | 65 | 66 |
67 |
68 |
    69 |
  • showTab(e.target.parentElement, "simple")} class="tab"> 70 | Basic usage 71 |
  • 72 |
  • showTab(e.target.parentElement, "customization")} 75 | class="tab" 76 | > 77 | Customization 78 |
  • 79 |
  • showTab(e.target.parentElement, "async")} class="tab"> 80 | Async 81 |
  • 82 |
  • showTab(e.target.parentElement, "advanced")} 85 | class="tab" 86 | > 87 | Advanced usage 88 |
  • 89 |
90 |
91 | 92 |
93 | 94 | 95 | 96 | 97 | 98 |
99 | 100 |
101 | 102 | 103 |
104 | 105 |
106 | 107 | 108 | 109 | 110 |
111 | 112 |
113 | 114 | 115 | 116 |
117 |
118 |
119 | -------------------------------------------------------------------------------- /demo/AsyncExample.svelte: -------------------------------------------------------------------------------- 1 | 54 | 55 |
56 |

Items asynchronous loading

57 | 58 |
59 |
60 | searchFunction can be used to dynamically generate items. 61 |
62 |
63 | 64 |
65 |
66 |
67 |
68 |

Pick a color

69 |
70 |
71 | 72 | 73 |
74 |

Selected color: {JSON.stringify(selectedColor)}

75 |
76 |
77 |
78 |
79 | 80 |
81 |
82 |
83 |

Code

84 |
85 |
86 | 87 |
88 |
89 |
90 |
91 | 92 |
93 |
94 | The delay parameter makes the component wait for 200ms after you typed something 95 | before generating a request. Set localFiltering to false if your search 96 | function already returnes filtered results, so results won't be filtered a second time client-side. 97 |
98 |
99 | 100 |
101 |
102 |
103 |
104 |

Pick a country

105 |
106 |
107 | 115 | 116 |
117 |

Selected country: {JSON.stringify(selectedCountry)}

118 |
119 |
120 |
121 |
122 | 123 |
124 |
125 |
126 |

Code

127 |
128 |
129 | 130 |
131 |
132 |
133 |
134 | 135 | 143 |
144 | -------------------------------------------------------------------------------- /demo/AsyncGeneratorExample.svelte: -------------------------------------------------------------------------------- 1 | 45 | 46 |
47 |

Async generators

48 |
49 |
50 |

51 | If your async data takes time to generate from your server, you may want to 52 | stream 56 | the response so the end user can have the first suggestions displayed while the latter are being 57 | computed.
58 | searchFunction can be a generator that yields chunks of items. 59 |

60 |

61 | Here we artificially simulate delay between chunks but you can see a real-life snippet here. 65 |

66 |
67 |
68 | 69 |
70 |
71 |
72 |
73 |

Pick a country

74 |
75 |
76 | 82 | 83 |
84 |

Selected country: {JSON.stringify(selectedCountry)}

85 |
86 |
87 |
88 |
89 | 90 |
91 |
92 |
93 |

Code

94 |
95 |
96 | 97 |
98 |
99 |
100 |
101 |
102 | -------------------------------------------------------------------------------- /demo/AsyncPreloadedExample.svelte: -------------------------------------------------------------------------------- 1 | 35 | 36 |
37 |

Preloaded item

38 | 39 |
40 |
41 | When an async autocomplete compenent is initialized with a value, the suggestion list will be 42 | loaded when the user focuses the input field. 43 |
44 |
45 | 46 |
47 |
48 |
49 |
50 |

Pick a country

51 |
52 |
53 | 61 | 62 |
63 |

Selected country: {JSON.stringify(selectedCountry)}

64 |
65 |
66 |
67 |
68 | 69 |
70 |
71 |
72 |

Code

73 |
74 |
75 | 76 |
77 |
78 |
79 |
80 |
81 | -------------------------------------------------------------------------------- /demo/CreatableExample.svelte: -------------------------------------------------------------------------------- 1 | 41 | 42 |
43 |

Item creation

44 | 45 |
46 |
47 | You can bind to the text property to get the current text that is typed by the 48 | user. The 49 | create property will enable the controll to show 50 |
51 | After, with the event on onCreate you can get the entered text and create the 52 | item. 53 |
54 | A slot called create and createText is available for customizing the 55 | UI. 56 |
57 |
58 | 59 |
60 |
61 |
62 |
63 |

Pick a color

64 |
65 |
66 | 74 |

Current user entered text: {text}

75 |

Selected color: {selectedColor}

76 |

To create: {toCreate}

77 |
78 |
79 |
80 | 81 |
82 |
83 |
84 |

Code

85 |
86 |
87 | 88 |
89 |
90 |
91 |
92 |
93 | -------------------------------------------------------------------------------- /demo/CustomFunctionsExample.svelte: -------------------------------------------------------------------------------- 1 | 68 | 69 |
70 |

Custom filtering and sorting

71 | 72 |
73 |
74 | itemFilterFunction 75 | Optional function to override the default local filtering that the component does based on keyword 76 | matching. 77 |
78 | The function arguments are individual item from the item list, and the cleaned up words from the 79 | input field. It should return a boolean indicating wether to keep the item. 80 |
81 |
82 | itemSortFunction 83 | Optional function to defined the sort order of the list. 84 |
85 | The function arguments are two distincts items from the item list, and the cleaned up words from 86 | the input field. It should return an integer. If greater than 0 the first item comes first, if 87 | lesser than 0 the second item comes first. 88 |
89 |
90 | 91 |
92 |
93 |
94 |
95 |

Pick a color

96 |
97 |
98 | 108 |

109 | Selected color item: {JSON.stringify(selectedColorObject)} 110 |
111 | Selected value: {selectedColorValue} 112 |

113 |
114 |
115 |
116 |
117 |
118 |
119 |

Code

120 |
121 |
122 | 123 |
124 |
125 |
126 |
127 |
128 | -------------------------------------------------------------------------------- /demo/CustomizationExample.svelte: -------------------------------------------------------------------------------- 1 | 45 | 46 |
47 |

Slots

48 |
49 |
50 | You can use the svelte slots mechanism to customize the component. 51 |
    52 |
  • item: change the apearance of items in the dropdown list
  • 53 |
  • no-results: customize the div that shows the "no results" text
  • 54 |
  • loading: customize the div that shows the "loading" text
  • 55 |
  • tag: customize the tag blocks displayed when multiple selection is enabled
  • 56 |
  • dropdown-header: customize what is displayed before the item list in the dropdown. By default there is nothing displayed.
  • 57 |
  • dropdown-footer: customize what is displayed before the item list in the dropdown. This is where the moreItemsText is displayed if there is too much items to be displayed.
  • 58 |
59 |
60 |
61 | 62 |
63 |
64 |
65 |
66 |

Pick a color

67 |
68 |
69 | color.name + " " + color.code} 77 | placeholder="Please select color" 78 | maxItemsToShowInList="100" 79 | > 80 |
81 | 82 | 83 |
84 | 85 |
86 | 87 | 88 |
89 | 90 |
91 | {@html label} 92 | {item.code} 93 |
94 | 95 |
96 | NO RESULTS - {noResultsText} 97 |
98 |
99 |
100 |
101 |
102 | 103 |
104 |
105 |
106 |

Code

107 |
108 |
109 | 110 |
111 |
112 |
113 |
114 |
115 | -------------------------------------------------------------------------------- /demo/LockedExample.svelte: -------------------------------------------------------------------------------- 1 | 16 | 17 |
18 |

Locked example:

19 |
20 |
21 | The lock attribute dissalows free text re-selection after the first selection until 22 | the control is cleared with the x button. 23 |
24 |
25 | 26 |
27 |
28 |
29 |
30 |

Pick a color

31 |
32 |
33 | 34 |

Selected color: {selectedColor}

35 |
36 |
37 |
38 | 39 |
40 |
41 |
42 |

Code

43 |
44 |
45 | 46 |
47 |
48 |
49 |
50 |
51 | -------------------------------------------------------------------------------- /demo/MatchingStrategyExample.svelte: -------------------------------------------------------------------------------- 1 | 29 | 30 |
31 |

Matching keyword strategy

32 |
33 |
34 | When matchAllKeywords is set to false, the component will show all items that match 35 | at least one keyword. The sorting of the items can also be changed to show the ones with most matches 36 | first. 37 |
38 |
39 | 40 |
41 |
42 |
43 |
44 |

Pick an animal

45 |
46 |
47 | 53 |

Selected animal: {selectedAnimal}

54 |
55 |
56 |
57 | 58 |
59 |
60 |
61 |

Code

62 |
63 |
64 | 65 |
66 |
67 |
68 |
69 |
70 | -------------------------------------------------------------------------------- /demo/MultipleExample.svelte: -------------------------------------------------------------------------------- 1 | 33 | 34 |
35 |

Multiple selection

36 |
37 |
38 | The multiple attribute allows you to select several items.
39 | orderableSelection enables item reordering with drag and drop. 40 |
41 |
42 | 43 |
44 |
45 |
46 |
47 |

Pick a color

48 |
49 |
50 | 59 | 60 |

61 | Selected color items: {JSON.stringify(selectedColorsItems)} 62 |
63 | Selected color values: {JSON.stringify(selectedColorsValues)} 64 |

65 |
66 |
67 |
68 | 69 |
70 |
71 |
72 |

Code

73 |
74 |
75 | 76 |
77 |
78 |
79 |
80 |
81 | -------------------------------------------------------------------------------- /demo/ReadOnlyExample.svelte: -------------------------------------------------------------------------------- 1 | 18 | 19 |
20 |

Read-only selection

21 |
22 |
23 | The readonly attribute is passed to the HTML input. When `true` then the text input 24 | cannot be edited, but items can still be selected in the menu. 25 |
26 |
27 | 28 |
29 |
30 |
31 |
32 |

Pick a color

33 |
34 |
35 |

Selected color: {selectedColor}

36 | 37 |
38 |
39 |
40 | 41 |
42 |
43 |
44 |

Code

45 |
46 |
47 | 48 |
49 |
50 |
51 |
52 |
53 | -------------------------------------------------------------------------------- /demo/RequiredExample.svelte: -------------------------------------------------------------------------------- 1 | 29 | 30 |
31 |

Required selection

32 |
33 |
34 | The required attribute is passed to the HTML input. 35 |
36 |
37 | 38 |
39 |
40 |
41 |
42 |
43 |

Pick a color

44 |
45 |
46 |

Selected color: {selectedColor}

47 | 48 | 49 |
50 |
51 |
52 | 53 |
54 |
55 |
56 |

Code

57 |
58 |
59 | 60 |
61 |
62 |
63 |
64 |
65 |
66 | -------------------------------------------------------------------------------- /demo/SimpleExample.svelte: -------------------------------------------------------------------------------- 1 | 20 | 21 |
22 |

Basic usecase

23 | 24 |
25 |
26 | You can use the items attribute to set the list of the items available for 27 | autocompletion. 28 |
29 | selectedItem can be bound to the selected item. 30 |
31 |
32 | 33 |
34 |
35 | 59 |
60 | 61 |
62 |
63 |
64 |

Code

65 |
66 |
67 | 68 |
69 |
70 |
71 |
72 |
73 | 74 | 76 | -------------------------------------------------------------------------------- /demo/main.js: -------------------------------------------------------------------------------- 1 | import App from "./App.svelte" 2 | 3 | const app = new App({ 4 | target: document.body, 5 | }) 6 | 7 | export default app 8 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: "ts-jest", 3 | transform: { 4 | "^.+\\.svelte$": "svelte-jester", 5 | "^.+\\.js$": "babel-jest", 6 | }, 7 | moduleFileExtensions: ["js", "svelte", "ts"], 8 | testEnvironment: "jsdom", 9 | } 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple-svelte-autocomplete", 3 | "version": "2.5.2", 4 | "description": "Autocomplete / Select / Typeahead component made with Svelte 3", 5 | "svelte": "src/SimpleAutocomplete.svelte", 6 | "module": "dist/index.mjs", 7 | "main": "dist/index.js", 8 | "devDependencies": { 9 | "@babel/core": "^7.15.0", 10 | "@babel/preset-env": "^7.16.11", 11 | "@rollup/plugin-commonjs": "^12.0.0", 12 | "@rollup/plugin-node-resolve": "^8.0.0", 13 | "@testing-library/jest-dom": "^5.16.4", 14 | "@testing-library/svelte": "^3.0.3", 15 | "@tsconfig/svelte": "^3.0.0", 16 | "@types/jest": "^27.4.1", 17 | "babel-jest": "^27.0.6", 18 | "bulma": "^0.9.3", 19 | "jest": "^27.0.0", 20 | "jest-dom": "^4.0.0", 21 | "npm-run-all": "^4.1.5", 22 | "prettier": "^2.5.1", 23 | "prettier-plugin-svelte": "^2.5.1", 24 | "rollup": "^2.3.4", 25 | "rollup-plugin-livereload": "^1.0.0", 26 | "rollup-plugin-svelte": "~6.1.1", 27 | "rollup-plugin-terser": "^5.1.2", 28 | "sirv-cli": "^0.4.4", 29 | "svelte": "^3.24.0", 30 | "svelte-highlight": "^6.0.1", 31 | "svelte-jester": "^2.3.2", 32 | "svelte-preprocess": "^4.7.4", 33 | "ts-jest": "^27.1.4", 34 | "typescript": "^4.3.5" 35 | }, 36 | "scripts": { 37 | "build": "rollup -c", 38 | "prepublishOnly": "npm run build", 39 | "autobuild": "rollup -c rollup.dev.config.js -w", 40 | "dev": "run-p start:dev autobuild", 41 | "start": "sirv public --single", 42 | "start:dev": "sirv public --single --dev", 43 | "test": "jest --coverage", 44 | "prettier": "prettier --write public src", 45 | "prepare": "npm run build" 46 | }, 47 | "repository": { 48 | "type": "git", 49 | "url": "git+https://github.com/pstanoev/simple-svelte-autocomplete" 50 | }, 51 | "keywords": [ 52 | "svelte", 53 | "autocomplete", 54 | "svelte3", 55 | "svelte-components", 56 | "autocomplete", 57 | "auto complete", 58 | "autosuggest", 59 | "auto suggest", 60 | "typeahead", 61 | "type ahead" 62 | ], 63 | "files": [ 64 | "src", 65 | "index.mjs", 66 | "index.js" 67 | ], 68 | "prettier": { 69 | "printWidth": 100, 70 | "tabWidth": 2, 71 | "semi": false, 72 | "pluginSearchDirs": [ 73 | "." 74 | ] 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /public/CNAME: -------------------------------------------------------------------------------- 1 | simple-svelte-autocomplete.surge.sh 2 | -------------------------------------------------------------------------------- /public/default.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 0 3em; 3 | font-family: helvetica; 4 | } 5 | 6 | ul { 7 | list-style-type: none; 8 | } 9 | 10 | .tabs { 11 | text-align: center; 12 | } 13 | 14 | .tab { 15 | cursor: pointer; 16 | display: inline-block; 17 | margin: auto 1em; 18 | } 19 | 20 | .tab:hover { 21 | color: #6c71c4; 22 | } 23 | 24 | .tab-content.is-hidden { 25 | display: none; 26 | } 27 | 28 | .tab.is-active { 29 | color: #268bd2; 30 | } 31 | 32 | .column { 33 | display: inline-block; 34 | width: 66%; 35 | } 36 | 37 | .column.is-one-third { 38 | width: 33%; 39 | } 40 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Svelte Simple Autocomplete Demo 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import commonjs from "@rollup/plugin-commonjs" 2 | import resolve from "@rollup/plugin-node-resolve" 3 | import svelte from "rollup-plugin-svelte" 4 | import pkg from "./package.json" 5 | 6 | export default [ 7 | { 8 | input: "src/SimpleAutocomplete.svelte", 9 | output: [ 10 | { file: pkg.module, format: "es" }, 11 | { file: pkg.main, format: "umd", name: "Autocomplete" }, 12 | ], 13 | plugins: [svelte(), commonjs(), resolve()], 14 | }, 15 | ] 16 | -------------------------------------------------------------------------------- /rollup.dev.config.js: -------------------------------------------------------------------------------- 1 | import svelte from "rollup-plugin-svelte" 2 | import resolve from "@rollup/plugin-node-resolve" 3 | import commonjs from "@rollup/plugin-commonjs" 4 | import livereload from "rollup-plugin-livereload" 5 | import { terser } from "rollup-plugin-terser" 6 | 7 | const production = !process.env.ROLLUP_WATCH 8 | 9 | export default { 10 | input: "demo/main.js", 11 | output: { 12 | sourcemap: true, 13 | format: "iife", 14 | name: "app", 15 | file: "public/bundle.js", 16 | }, 17 | plugins: [ 18 | svelte({ 19 | // enable run-time checks when not in production 20 | dev: !production, 21 | // we'll extract any component CSS out into 22 | // a separate file — better for performance 23 | css: (css) => { 24 | css.write("bundle.css") 25 | }, 26 | }), 27 | 28 | // If you have external dependencies installed from 29 | // npm, you'll most likely need these plugins. In 30 | // some cases you'll need additional configuration — 31 | // consult the documentation for details: 32 | // https://github.com/rollup/rollup-plugin-commonjs 33 | resolve({ 34 | browser: true, 35 | dedupe: (importee) => importee === "svelte" || importee.startsWith("svelte/"), 36 | }), 37 | commonjs(), 38 | 39 | // Watch the `public` directory and refresh the 40 | // browser on changes when not in production 41 | !production && livereload("public"), 42 | 43 | // If we're building for production (npm run build 44 | // instead of npm run dev), minify 45 | production && terser(), 46 | ], 47 | watch: { 48 | clearScreen: false, 49 | }, 50 | } 51 | -------------------------------------------------------------------------------- /src/SimpleAutocomplete.svelte: -------------------------------------------------------------------------------- 1 | 1125 |
1132 | 1145 |
1146 | {#if multiple && hasSelection} 1147 | {#each selectedItem as tagItem, i (valueFunction(tagItem, true))} 1148 |
dragstart(event, i)} 1153 | on:dragover={(event) => dragover(event, i)} 1154 | on:dragleave={(event) => dragleave(event, i)} 1155 | on:drop={(event) => drop(event, i)} 1156 | class:is-active={draggingOver === i} 1157 | > 1158 | 1159 |
1160 | {safeLabelFunction(tagItem)} 1161 | {e.key == "Enter" && unselectItem(tagItem)}} 1165 | /> 1166 |
1167 |
1168 |
1169 | {/each} 1170 | {/if} 1171 | dragover(event, selectedItem.length - 1)} 1194 | on:drop={(event) => drop(event, selectedItem.length - 1)} 1195 | {...$$restProps} 1196 | /> 1197 | {#if clearable} 1198 | {e.key == "Enter" && clear()}} 1201 | class="autocomplete-clear-button" 1202 | >{@html clearText} 1203 | {/if} 1204 |
1205 | 1268 |
1269 | 1270 | setPositionOnNextUpdate = true} /> 1271 | 1272 | 1436 | -------------------------------------------------------------------------------- /src/tests/async.test.ts: -------------------------------------------------------------------------------- 1 | import { render, fireEvent, screen } from "@testing-library/svelte" 2 | import SimpleAutocomplete from "../SimpleAutocomplete.svelte" 3 | import '@testing-library/jest-dom/extend-expect' 4 | import '@testing-library/jest-dom' 5 | 6 | async function colors() { 7 | return ["White", "Red", "Yellow", "Green", "Blue", "Black", "Mät bläck", "Jét Black"] 8 | } 9 | 10 | window = Object.assign(window, { visualViewport: { height: 1500 } }) 11 | 12 | test("at first, menu is empty", async () => { 13 | render(SimpleAutocomplete, {searchFunction: colors}) 14 | 15 | expect(await screen.queryByText('White')).not.toBeInTheDocument() 16 | expect(await screen.queryByText('Red')).not.toBeInTheDocument() 17 | }) 18 | 19 | test("even with the input is focused, the menu is empty", async () => { 20 | const { container } = render(SimpleAutocomplete, {searchFunction: colors}) 21 | const queryInput = container.querySelector("input[type='text']"); 22 | 23 | await fireEvent.focus(queryInput) 24 | 25 | expect(await screen.queryByText('White')).not.toBeInTheDocument() 26 | expect(await screen.queryByText('Red')).not.toBeInTheDocument() 27 | }) 28 | 29 | test("when something is queried, only the matching items are shown", async () => { 30 | const { container } = render(SimpleAutocomplete, {searchFunction: colors}) 31 | const queryInput = container.querySelector("input[type='text']"); 32 | 33 | await fireEvent.input(queryInput, { target: { value: "white" } }) 34 | 35 | expect(await screen.queryByText('White')).toBeInTheDocument() 36 | expect(await screen.queryByText('White')).toBeVisible() 37 | expect(await screen.queryByText('Red')).not.toBeInTheDocument() 38 | }) 39 | 40 | test("when nothing matches the query, no item is show", async () => { 41 | 42 | const { container } = render(SimpleAutocomplete, {searchFunction: colors}) 43 | const queryInput = container.querySelector("input[type='text']"); 44 | 45 | await fireEvent.input(queryInput, { target: { value: "not-a-color" } }) 46 | 47 | expect(await screen.queryByText('White')).not.toBeInTheDocument() 48 | expect(await screen.queryByText('Red')).not.toBeInTheDocument() 49 | }) 50 | 51 | test("when something is queried, the query is highlighted", async () => { 52 | const { container } = render(SimpleAutocomplete, {searchFunction: colors}) 53 | const queryInput = container.querySelector("input[type='text']"); 54 | 55 | await fireEvent.input(queryInput, { target: { value: "whi" } }) 56 | 57 | const white_item = (await screen.queryByText('Whi')).closest(".autocomplete-list-item") 58 | expect(white_item).toContainHTML('White') 59 | }) 60 | -------------------------------------------------------------------------------- /src/tests/highlight.test.ts: -------------------------------------------------------------------------------- 1 | import { render } from "@testing-library/svelte" 2 | import SimpleAutocomplete from "../SimpleAutocomplete.svelte" 3 | 4 | window = Object.assign(window, { visualViewport: { height: 1500 } }) 5 | 6 | test("test simple hightlights", async () => { 7 | const { component } = render(SimpleAutocomplete) 8 | var item = { 9 | label: "foobar", 10 | } 11 | const result = component.highlightFilter(["foo"], "label")(item).highlighted 12 | expect(result).toStrictEqual("foobar") 13 | }) 14 | 15 | test("test accented words highlights", async () => { 16 | const { component } = render(SimpleAutocomplete) 17 | var item = { 18 | label: "foobär", 19 | } 20 | const result = component.highlightFilter(["föobar"], "label")(item).highlighted 21 | expect(result).toStrictEqual("foobär") 22 | }) 23 | -------------------------------------------------------------------------------- /src/tests/multiple.test.ts: -------------------------------------------------------------------------------- 1 | import { render, fireEvent, screen } from "@testing-library/svelte" 2 | import SimpleAutocomplete from "../SimpleAutocomplete.svelte" 3 | import '@testing-library/jest-dom/extend-expect' 4 | import '@testing-library/jest-dom' 5 | 6 | const colors = ["White", "Red", "Yellow", "Green", "Blue", "Black"] 7 | 8 | window = Object.assign(window, { visualViewport: { height: 1500 } }) 9 | 10 | test("when selection is multiple, selectedItem is a list", async () => { 11 | const { component, container } = render(SimpleAutocomplete, {items: colors, multiple: true}) 12 | const queryInput = container.querySelector("input[type='text']"); 13 | 14 | expect(component.selectedItem).toStrictEqual([]) 15 | 16 | await fireEvent.input(queryInput, { target: { value: "white" } }) 17 | await fireEvent.click(await screen.queryByText('White')) 18 | 19 | expect(component.selectedItem).toStrictEqual(["White"]) 20 | 21 | await fireEvent.input(queryInput, { target: { value: "Red" } }) 22 | await fireEvent.click(await screen.queryByText('Red')) 23 | 24 | expect(component.selectedItem).toStrictEqual(["White", "Red"]) 25 | }) 26 | 27 | test("multiple widget initialization", async () => { 28 | const { component, container } = render(SimpleAutocomplete, { 29 | items: colors, 30 | multiple: true, 31 | selectedItem:["White", "Red"], 32 | text: "foobar" 33 | }) 34 | const queryInput = container.querySelector("input[type='text']"); 35 | 36 | expect(component.selectedItem).toStrictEqual(["White", "Red"]) 37 | expect(component.text).toStrictEqual("foobar") 38 | }) 39 | -------------------------------------------------------------------------------- /src/tests/sync.test.ts: -------------------------------------------------------------------------------- 1 | import { render, fireEvent, screen } from "@testing-library/svelte" 2 | import SimpleAutocomplete from "../SimpleAutocomplete.svelte" 3 | import '@testing-library/jest-dom/extend-expect' 4 | import '@testing-library/jest-dom' 5 | 6 | const colors = ["White", "Red", "Yellow", "Green", "Blue", "Black", "Mät bläck", "Jét Black"] 7 | 8 | window = Object.assign(window, { visualViewport: { height: 1500 } }) 9 | 10 | test("items are generated but hidden", async () => { 11 | const { component, container } = render(SimpleAutocomplete, {items: colors}) 12 | 13 | expect(await screen.queryByText('White')).toBeInTheDocument() 14 | expect(await screen.queryByText('White')).not.toBeVisible() 15 | expect(await screen.queryByText('Red')).toBeInTheDocument() 16 | expect(await screen.queryByText('Red')).not.toBeVisible() 17 | }) 18 | 19 | test("on focus, menu is shown with all the elements", async () => { 20 | const { component, container } = render(SimpleAutocomplete, {items: colors}) 21 | const queryInput = container.querySelector("input[type='text']"); 22 | 23 | await fireEvent.focus(queryInput) 24 | 25 | expect(await screen.queryByText('White')).toBeInTheDocument() 26 | expect(await screen.queryByText('White')).toBeVisible() 27 | expect(await screen.queryByText('Red')).toBeInTheDocument() 28 | expect(await screen.queryByText('Red')).toBeVisible() 29 | }) 30 | 31 | test("when something is queried, only the matching items are shown", async () => { 32 | const { component, container } = render(SimpleAutocomplete, {items: colors}) 33 | const queryInput = container.querySelector("input[type='text']"); 34 | 35 | await fireEvent.input(queryInput, { target: { value: "white" } }) 36 | 37 | expect(await screen.queryByText('White')).toBeInTheDocument() 38 | expect(await screen.queryByText('White')).toBeVisible() 39 | expect(await screen.queryByText('Red')).not.toBeInTheDocument() 40 | }) 41 | 42 | test("when nothing matches the query, no item is show", async () => { 43 | const { component, container } = render(SimpleAutocomplete, {items: colors}) 44 | const queryInput = container.querySelector("input[type='text']"); 45 | 46 | await fireEvent.input(queryInput, { target: { value: "not-a-color" } }) 47 | 48 | expect(await screen.queryByText('White')).not.toBeInTheDocument() 49 | expect(await screen.queryByText('Red')).not.toBeInTheDocument() 50 | }) 51 | 52 | test("when something is queried, the query is highlighted", async () => { 53 | const { component, container } = render(SimpleAutocomplete, {items: colors}) 54 | const queryInput = container.querySelector("input[type='text']"); 55 | const list = container.querySelector("autocomplete-list"); 56 | 57 | await fireEvent.input(queryInput, { target: { value: "whi" } }) 58 | 59 | const white_item = (await screen.queryByText('Whi')).closest(".autocomplete-list-item") 60 | expect(white_item).toContainHTML('White') 61 | }) 62 | 63 | test("widget initialization with selectedItem", async () => { 64 | const { component, container } = render(SimpleAutocomplete, { 65 | items: colors, 66 | selectedItem:"White", 67 | }) 68 | const queryInput = container.querySelector("input[type='text']"); 69 | 70 | expect(component.selectedItem).toStrictEqual("White") 71 | expect(component.text).toStrictEqual("White") 72 | }) 73 | 74 | test("widget initialization with text", async () => { 75 | const { component, container } = render(SimpleAutocomplete, { 76 | items: colors, 77 | text:"White", 78 | }) 79 | const queryInput = container.querySelector("input[type='text']"); 80 | 81 | expect(component.selectedItem).toBeUndefined() 82 | expect(component.text).toStrictEqual("White") 83 | }) 84 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/svelte/tsconfig.json", 3 | "compilerOptions": { 4 | "types": ["node", "svelte", "jest", "@types/testing-library__jest-dom"] 5 | } 6 | } 7 | --------------------------------------------------------------------------------