svelte-typeahead 4.2.3 → 4.3.1
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/CHANGELOG.md +18 -0
- package/README.md +34 -32
- package/lib/index.js +172 -130
- package/lib/index.mjs +172 -130
- package/package.json +2 -3
- package/src/Typeahead.svelte +58 -23
- package/types/Typeahead.svelte.d.ts +6 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svelte-typeahead",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.3.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Accessible, fuzzy search typeahead component",
|
|
6
6
|
"author": "Eric Liu (https://github.com/metonym)",
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
"main": "./lib/index.js",
|
|
9
9
|
"module": "./lib/index.mjs",
|
|
10
10
|
"types": "./types/index.d.ts",
|
|
11
|
-
"sideEffects": false,
|
|
12
11
|
"scripts": {
|
|
13
12
|
"dev": "rollup -cw",
|
|
14
13
|
"predeploy": "rollup -c",
|
|
@@ -22,7 +21,7 @@
|
|
|
22
21
|
},
|
|
23
22
|
"devDependencies": {
|
|
24
23
|
"@rollup/plugin-commonjs": "^21.0.2",
|
|
25
|
-
"svelte": "^3.
|
|
24
|
+
"svelte": "^3.49.0",
|
|
26
25
|
"svelte-check": "^2.8.0",
|
|
27
26
|
"svelte-readme": "^3.6.3"
|
|
28
27
|
},
|
package/src/Typeahead.svelte
CHANGED
|
@@ -33,6 +33,9 @@
|
|
|
33
33
|
/** Set to `true` to re-focus the input after selecting a result */
|
|
34
34
|
export let focusAfterSelect = false;
|
|
35
35
|
|
|
36
|
+
/** Set to `true` to only show results when the input is focused */
|
|
37
|
+
export let showDropdownOnFocus = false;
|
|
38
|
+
|
|
36
39
|
/**
|
|
37
40
|
* Specify the maximum number of results to return
|
|
38
41
|
* @type {number}
|
|
@@ -50,10 +53,11 @@
|
|
|
50
53
|
let hideDropdown = false;
|
|
51
54
|
let selectedIndex = -1;
|
|
52
55
|
let prevResults = "";
|
|
56
|
+
let isFocused = false;
|
|
53
57
|
|
|
54
58
|
afterUpdate(() => {
|
|
55
59
|
if (prevResults !== resultsId && autoselect) {
|
|
56
|
-
selectedIndex =
|
|
60
|
+
selectedIndex = getNextNonDisabledIndex();
|
|
57
61
|
}
|
|
58
62
|
|
|
59
63
|
if (prevResults !== resultsId && !$$slots["no-results"]) {
|
|
@@ -85,14 +89,33 @@
|
|
|
85
89
|
await tick();
|
|
86
90
|
|
|
87
91
|
if (focusAfterSelect) searchRef.focus();
|
|
92
|
+
close();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** @type {() => number} */
|
|
96
|
+
function getNextNonDisabledIndex() {
|
|
97
|
+
let index = 0;
|
|
98
|
+
let disabled = results[index]?.disabled ?? false;
|
|
99
|
+
|
|
100
|
+
while (disabled) {
|
|
101
|
+
if (index === results.length) {
|
|
102
|
+
index = 0;
|
|
103
|
+
} else {
|
|
104
|
+
index += 1;
|
|
105
|
+
}
|
|
88
106
|
|
|
89
|
-
|
|
107
|
+
disabled = results[index]?.disabled ?? false;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return index;
|
|
90
111
|
}
|
|
91
112
|
|
|
92
113
|
/** @type {(direction: -1 | 1) => void} */
|
|
93
114
|
function change(direction) {
|
|
94
115
|
let index =
|
|
95
|
-
direction === 1 && selectedIndex === results.length - 1
|
|
116
|
+
direction === 1 && selectedIndex === results.length - 1
|
|
117
|
+
? 0
|
|
118
|
+
: selectedIndex + direction;
|
|
96
119
|
if (index < 0) index = results.length - 1;
|
|
97
120
|
|
|
98
121
|
let disabled = results[index].disabled;
|
|
@@ -110,6 +133,9 @@
|
|
|
110
133
|
selectedIndex = index;
|
|
111
134
|
}
|
|
112
135
|
|
|
136
|
+
const open = () => (hideDropdown = false);
|
|
137
|
+
const close = () => (hideDropdown = true);
|
|
138
|
+
|
|
113
139
|
$: options = { pre: "<mark>", post: "</mark>", extract };
|
|
114
140
|
$: results = fuzzy
|
|
115
141
|
.filter(value, data, options)
|
|
@@ -118,12 +144,16 @@
|
|
|
118
144
|
.filter((result) => !filter(result.original))
|
|
119
145
|
.map((result) => ({ ...result, disabled: disable(result.original) }));
|
|
120
146
|
$: resultsId = results.map((result) => extract(result.original)).join("");
|
|
147
|
+
$: showResults = !hideDropdown && results.length > 0;
|
|
148
|
+
$: if (showDropdownOnFocus) {
|
|
149
|
+
showResults = showResults && isFocused;
|
|
150
|
+
}
|
|
121
151
|
</script>
|
|
122
152
|
|
|
123
153
|
<svelte:window
|
|
124
154
|
on:click={({ target }) => {
|
|
125
|
-
if (!hideDropdown &&
|
|
126
|
-
|
|
155
|
+
if (!hideDropdown && !comboboxRef?.contains(target)) {
|
|
156
|
+
close();
|
|
127
157
|
}
|
|
128
158
|
}}
|
|
129
159
|
/>
|
|
@@ -135,7 +165,7 @@
|
|
|
135
165
|
aria-haspopup="listbox"
|
|
136
166
|
aria-owns="{id}-listbox"
|
|
137
167
|
class:dropdown={results.length > 0}
|
|
138
|
-
aria-expanded={
|
|
168
|
+
aria-expanded={showResults}
|
|
139
169
|
id="{id}-typeahead"
|
|
140
170
|
>
|
|
141
171
|
<Search
|
|
@@ -157,13 +187,15 @@
|
|
|
157
187
|
on:change
|
|
158
188
|
on:focus
|
|
159
189
|
on:focus={() => {
|
|
160
|
-
|
|
190
|
+
open();
|
|
191
|
+
isFocused = true;
|
|
161
192
|
}}
|
|
162
193
|
on:clear
|
|
163
|
-
on:clear={
|
|
164
|
-
hideDropdown = false;
|
|
165
|
-
}}
|
|
194
|
+
on:clear={open}
|
|
166
195
|
on:blur
|
|
196
|
+
on:blur={() => {
|
|
197
|
+
isFocused = false;
|
|
198
|
+
}}
|
|
167
199
|
on:keydown
|
|
168
200
|
on:keydown={(e) => {
|
|
169
201
|
if (results.length === 0) return;
|
|
@@ -183,8 +215,8 @@
|
|
|
183
215
|
case "Escape":
|
|
184
216
|
e.preventDefault();
|
|
185
217
|
value = "";
|
|
186
|
-
searchRef
|
|
187
|
-
|
|
218
|
+
searchRef?.focus();
|
|
219
|
+
close();
|
|
188
220
|
break;
|
|
189
221
|
}
|
|
190
222
|
}}
|
|
@@ -195,26 +227,25 @@
|
|
|
195
227
|
aria-labelledby="{id}-label"
|
|
196
228
|
id="{id}-listbox"
|
|
197
229
|
>
|
|
198
|
-
{#if
|
|
199
|
-
{#each results as result,
|
|
230
|
+
{#if showResults}
|
|
231
|
+
{#each results as result, index}
|
|
200
232
|
<li
|
|
201
233
|
role="option"
|
|
202
|
-
id="{id}-result-{
|
|
203
|
-
class:selected={selectedIndex ===
|
|
234
|
+
id="{id}-result-{index}"
|
|
235
|
+
class:selected={selectedIndex === index}
|
|
204
236
|
class:disabled={result.disabled}
|
|
205
|
-
aria-selected={selectedIndex ===
|
|
237
|
+
aria-selected={selectedIndex === index}
|
|
206
238
|
on:click={() => {
|
|
207
|
-
if (
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
239
|
+
if (result.disabled) return;
|
|
240
|
+
selectedIndex = index;
|
|
241
|
+
select();
|
|
211
242
|
}}
|
|
212
243
|
on:mouseenter={() => {
|
|
213
244
|
if (result.disabled) return;
|
|
214
|
-
selectedIndex =
|
|
245
|
+
selectedIndex = index;
|
|
215
246
|
}}
|
|
216
247
|
>
|
|
217
|
-
<slot {result} index
|
|
248
|
+
<slot {result} {index} {value}>
|
|
218
249
|
{@html result.string}
|
|
219
250
|
</slot>
|
|
220
251
|
</li>
|
|
@@ -245,6 +276,10 @@
|
|
|
245
276
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
|
246
277
|
}
|
|
247
278
|
|
|
279
|
+
[aria-expanded="true"] ul {
|
|
280
|
+
z-index: 1;
|
|
281
|
+
}
|
|
282
|
+
|
|
248
283
|
li,
|
|
249
284
|
.no-results {
|
|
250
285
|
padding: 0.25rem 1rem;
|
|
@@ -62,6 +62,12 @@ export interface TypeaheadProps<TItem> extends SearchProps {
|
|
|
62
62
|
*/
|
|
63
63
|
focusAfterSelect?: boolean;
|
|
64
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Set to `true` to only show results when the input is focused
|
|
67
|
+
* @default false
|
|
68
|
+
*/
|
|
69
|
+
showDropdownOnFocus?: boolean;
|
|
70
|
+
|
|
65
71
|
/**
|
|
66
72
|
* Specify the maximum number of results to return
|
|
67
73
|
* @default Infinity
|