Repository: metonym/svelte-typeahead
Branch: master
Commit: 678597726233
Files: 15
Total size: 31.1 KB
Directory structure:
gitextract_vgom6s0h/
├── .github/
│ └── workflows/
│ ├── ci.yml
│ ├── github-pages.yml
│ └── release.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── package.json
├── rollup.config.ts
├── src/
│ ├── Typeahead.svelte
│ ├── Typeahead.svelte.d.ts
│ ├── index.d.ts
│ └── index.js
├── tests/
│ └── Typeahead.test.svelte
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/ci.yml
================================================
on:
pull_request:
push:
branches: [master]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: oven-sh/setup-bun@v2
- name: Install dependencies
run: bun ci
- name: Run unit tests
run: bun run test
================================================
FILE: .github/workflows/github-pages.yml
================================================
on:
push:
branches: [master]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: oven-sh/setup-bun@v2
- name: Install dependencies
run: bun ci
- name: Build app
run: bun run rollup -c
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: dist
================================================
FILE: .github/workflows/release.yml
================================================
on:
push:
tags:
- "v*"
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 22
registry-url: "https://registry.npmjs.org"
- name: Prune package.json
run: npx culls --preserve=svelte
- name: Publish package
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
run: npm publish --provenance --access public
================================================
FILE: .gitignore
================================================
.DS_Store
*.tgz
/dist
/lib
/node_modules
================================================
FILE: CHANGELOG.md
================================================
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [5.0.2](https://github.com/metonym/svelte-typeahead/releases/tag/v5.0.2) - 2025-11-24
**Fixes**
- close dropdown for keyboard navigation
## [5.0.1](https://github.com/metonym/svelte-typeahead/releases/tag/v5.0.1) - 2025-09-11
**Fixes**
- patch `svelte-search` to v2.1.2
## [5.0.0](https://github.com/metonym/svelte-typeahead/releases/tag/v5.0.0) - 2025-01-20
**Breaking Changes**
- set `package.json#type` to `module` with `exports` field
- drop bundled ESM/UMD builds (only Svelte source code is distributed)
- colocate TypeScript definitions with Svelte source code
**Fixes**
- patch `svelte-search` to v2.1.1
- run `npm pkg fix` to fix `package.json` metadata
## [4.4.2](https://github.com/metonym/svelte-typeahead/releases/tag/v4.4.2) - 2024-11-20
- fix `$$restProps` type errors by upgrading `svelte-search`
## [4.4.1](https://github.com/metonym/svelte-typeahead/releases/tag/v4.4.1) - 2022-11-12
- replace `aria-owns` with `aria-controls` to resolve `a11y-role-has-required-aria-props` warning
## [4.4.0](https://github.com/metonym/svelte-typeahead/releases/tag/v4.4.0) - 2022-11-08
**Features**
- add `showAllResultsOnFocus` prop to display all results when focusing an empty input
**Fixes**
- silence a11y warnings emitted by Svelte@3.52
- event type for dispatched `clear` event should be `null`
- unset `ul` margin
## [4.3.2](https://github.com/metonym/svelte-typeahead/releases/tag/v4.3.2) - 2022-09-01
**Fixes**
- allow results to be selected if using `showDropdownOnFocus`
## [4.3.1](https://github.com/metonym/svelte-typeahead/releases/tag/v4.3.1) - 2022-08-29
**Fixes**
- skip disabled results when auto-selecting a result
## [4.3.0](https://github.com/metonym/svelte-typeahead/releases/tag/v4.3.0) - 2022-08-29
**Features**
- add `showDropdownOnFocus` prop to only show dropdown if the search input is focused
## [4.2.4](https://github.com/metonym/svelte-typeahead/releases/tag/v4.2.4) - 2022-07-05
**Fixes**
- set dropdown menu `z-index: 1` when expanded
## [4.2.3](https://github.com/metonym/svelte-typeahead/releases/tag/v4.2.3) - 2022-07-04
**Fixes**
- update selected index when hovering over a result
## [4.2.2](https://github.com/metonym/svelte-typeahead/releases/tag/v4.2.2) - 2022-03-20
**Fixes**
- pressing "ArrowUp" on last item should not reset highlighted index
## [4.2.1](https://github.com/metonym/svelte-typeahead/releases/tag/v4.2.1) - 2021-10-30
**Fixes**
- prevent keyboard selection and navigation of disabled results
- add missing annotation for `TItem.disabled` to `results` prop
**Documentation**
- add `pnpm` installation command
- remove "disabling items after selection" example
- remove `id` fields from `data` array
- simplify dispatched events example
## [4.2.0](https://github.com/metonym/svelte-typeahead/releases/tag/v4.2.0) - 2021-10-26
**Features**
- use TypeScript generics so that the item type can be inferred from `data` value
## [4.1.0](https://github.com/metonym/svelte-typeahead/releases/tag/v4.1.0) - 2021-10-03
**Features**
- render a "no-results" slot when the search value does not yield results
- add `value` to the default slot props
## [4.0.0](https://github.com/metonym/svelte-typeahead/releases/tag/v4.0.0) - 2021-09-05
**Breaking Changes**
- use `.svelte.d.ts` extension for component TypeScript definition
## [3.0.0](https://github.com/metonym/svelte-typeahead/releases/tag/v3.0.0) - 2021-03-16
**Breaking Changes**
- the `ul` element is rendered even without results to preserve the accessibility label
**Fixes**
- disable form ARIA attributes in `svelte-search`
- pass the correct `aria-activedescendant` item to `svelte-search`
## [2.4.1](https://github.com/metonym/svelte-typeahead/releases/tag/v2.4.1) - 2021-03-14
**Fixes**
- pass `id` to `Search` to fix `aria-labelledby` references
## [2.4.0](https://github.com/metonym/svelte-typeahead/releases/tag/v2.4.0) - 2021-03-04
**Features**
- add `limit` prop to limit number of results; default value is `Infinity`
## [2.3.0](https://github.com/metonym/svelte-typeahead/releases/tag/v2.3.0) - 2021-02-21
**Features**
- add `disable`, `filter` props to disable and filter items from the result set
**Fixes**
- bind the input element reference correctly to fix focusing behavior
- don't pass the Typeahead id to Search
## [2.2.0](https://github.com/metonym/svelte-typeahead/releases/tag/v2.2.0) - 2021-02-20
**Features**
- add `inputAfterSelect` prop to allow user to preserve or clear the input field after selecting a result; possible values are `"update" | "clear" | "keep"` (default is `"update"`)
- add searched value to dispatched "select" event detail (`e.detail.searched`)
## [2.1.0](https://github.com/metonym/svelte-typeahead/releases/tag/v2.1.0) - 2021-02-20
**Features**
- include `original` item and `originalIndex` in dispatched "select" event
## [2.0.0](https://github.com/metonym/svelte-typeahead/releases/tag/v2.0.0) - 2020-12-31
**Breaking Changes**
- upgrade `svelte-search` to version 1.0.0
- defer to default `label`, `placeholder` props from `search-svelte`
- use `SvelteComponentTyped` interface in TypeScript definitions
## [1.0.0](https://github.com/metonym/svelte-typeahead/releases/tag/v1.0.0) - 2020-11-28
**Features**
- export reactive `results` array containing fuzzy results
- add `focusAfterSelect` to opt in to focusing input after selecting a result
- keydown default behavior is preventing if pressing "ArrowUp", "ArrowDown", or "Escape"
**Breaking changes**
- `focusAfterSelect` is `false` by default
- redesigned default styles
- if using TS, Svelte version >=3.30 is required
## [0.2.0](https://github.com/metonym/svelte-typeahead/releases/tag/v0.2.0) - 2020-11-17
- Add TypeScript definitions
## [0.1.1](https://github.com/metonym/svelte-typeahead/releases/tag/v0.1.1) - 2020-08-06
- Remove `filter` named export
## [0.1.0](https://github.com/metonym/svelte-typeahead/releases/tag/v0.1.0) - 2020-04-15
- Initial release
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2020-present Eric Liu
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# svelte-typeahead
[![NPM][npm]][npm-url]
> Accessible, fuzzy search typeahead component.
<!-- REPO_URL -->
This component uses the lightweight [fuzzy](https://github.com/mattyork/fuzzy) library for client-side, fuzzy search and follows [WAI-ARIA guidelines](https://www.w3.org/TR/wai-aria-practices/examples/combobox/aria1.1pattern/listbox-combo.html).
Try it in the [Svelte REPL](https://svelte.dev/repl/a1b828d80de24f7e995b2365782c8d04).
---
<!-- TOC -->
## Installation
```bash
# npm
npm i svelte-typeahead
# pnpm
pnpm i svelte-typeahead
# Yarn
yarn add svelte-typeahead
# Bun
bun add svelte-typeahead
```
## Usage
### Basic
Pass an array of objects to the `data` prop. Use the `extract` prop to specify the value to search on.
```svelte
<script>
import Typeahead from "svelte-typeahead";
const data = [
{ state: "California" },
{ state: "North Carolina" },
{ state: "North Dakota" },
{ state: "South Carolina" },
{ state: "South Dakota" },
{ state: "Michigan" },
{ state: "Tennessee" },
{ state: "Nevada" },
{ state: "New Hampshire" },
{ state: "New Jersey" },
];
const extract = (item) => item.state;
</script>
<Typeahead {data} {extract} />
```
### Custom label
`$$restProps` are forwarded to [svelte-search](https://github.com/metonym/svelte-search).
Use the `label` prop to specify a custom label.
```svelte
<Typeahead label="U.S. States" {data} {extract} />
```
### Hidden label
Set `hideLabel` to `true` to visually hide the label.
It's recommended that you set the `label` – even if hidden – for accessibility.
```svelte
<Typeahead label="U.S. States" hideLabel {data} {extract} />
```
### Custom-styled results
This component uses the [fuzzy](https://github.com/mattyork/fuzzy) library to highlight matching characters with the [mark](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/mark) element.
Use the default slot to render custom results.
```svelte
<Typeahead {data} {extract} let:result let:index>
<strong>{@html result.string}</strong>
{index}
</Typeahead>
```
### No results
Use the "no-results" slot to render a message if the search value does not yield results.
```svelte
<Typeahead value="abcd" {data} {extract} let:value>
<svelte:fragment slot="no-results">
No results found for "{value}"
</svelte:fragment>
</Typeahead>
```
### Limit the number of results
Use the `limit` prop to specify the maximum number of results to display. The default is `Infinity`.
```svelte
<Typeahead limit={2} {data} {extract} />
```
### Disabled items
Disable items using the `disable` filter. Disabled items are not selectable or navigable by keyboard.
In the following example, items with a `state` value containing "Carolina" are disabled.
```svelte
<Typeahead
{data}
value="ca"
extract={(item) => item.state}
disable={(item) => /Carolina/.test(item.state)}
/>
```
### Focus after select
Set `focusAfterSelect` to `true` to re-focus the search input after selecting a result.
```svelte
<Typeahead focusAfterSelect {data} {extract} />
```
### Show dropdown on focus
By default, the dropdown will be shown if the `value` has results.
Set `showDropdownOnFocus` to `true` to only show the dropdown when the search input is focused.
```svelte
<Typeahead value="ca" showDropdownOnFocus {data} {extract} />
```
### Show all results on focus
By default, no results are shown if an empty input (i.e., `value=""`) is focused.
Set `showAllResultsOnFocus` to `true` for all results to be shown when an empty input is focused.
```svelte
<Typeahead showAllResultsOnFocus {data} {extract} />
```
### Styling
**Note:** this component is minimally styled by design. You can target the component using the `[data-svelte-typeahead]` selector.
```css
:global([data-svelte-typeahead]) {
margin: 1rem;
}
```
## API
### Props
| Name | Type | Default value | Description |
| :-------------------- | :------------------------------------------------------------------------- | :---------------- | :------------------------------------------------------------------------------------------------------------------------------------ |
| value | `string` | `""` | Input search value. |
| data | `TItem[]` | `[]` | Items to search. |
| extract | `(TItem) => any` | `(item) => item` | Target the value if `TItem` is an object. |
| disable | `(TItem) => boolean` | `(item) => false` | Disabled items are shown in results but are not selectable. |
| filter | `(TItem) => boolean` | `(item) => false` | Filtered out items will not be displayed in the results. |
| autoselect | `boolean` | `true` | Whether to automatically select the first result. |
| inputAfterSelect | `"update" \| "clear" \| "keep"` | `"update"` | Set to `"clear"` to clear the `value` after selecting a result. Set to `"keep"` to keep the search field unchanged after a selection. |
| results | `FuzzyResult[]` | `[]` | Raw fuzzy results from the [fuzzy](https://github.com/mattyork/fuzzy) module |
| focusAfterSelect | `boolean` | `false` | Set to `true` to re-focus the input after selecting a result. |
| showDropdownOnFocus | `boolean` | `false` | Set to `true` to only show results when the input is focused. |
| showAllResultsOnFocus | `boolean` | `false` | Set to `true` for all results to be shown when an empty input is focused. |
| limit | `number` | `Infinity` | Specify the maximum number of results to display. |
| `...$$restProps` | (forwarded to [`svelte-search`](https://github.com/metonym/svelte-search)) | N/A | All other props are forwarded to the input element. |
### Dispatched events
- **on:select**: dispatched when selecting a result
- **on:clear**: dispatched when clearing the input field
```svelte
<script>
import Typeahead from "svelte-typeahead";
let e = [];
</script>
<Typeahead
{data}
{extract}
on:select={({ detail }) => (e = [...e, { event: "select", detail }])}
on:clear={() => (e = [...e, { event: "clear" }])}
/>
<pre>{JSON.stringify(e, null, 2)}</pre>
```
### Forwarded events
The following events are forwarded to the [svelte-search](https://github.com/metonym/svelte-search) component.
- on:type
- on:input
- on:change
- on:focus
- on:clear
- on:blur
- on:keydown
## Changelog
[Changelog](CHANGELOG.md)
## License
[MIT](LICENSE)
[npm]: https://img.shields.io/npm/v/svelte-typeahead.svg?color=%23ff3e00&style=for-the-badge
[npm-url]: https://npmjs.com/package/svelte-typeahead
================================================
FILE: package.json
================================================
{
"name": "svelte-typeahead",
"version": "5.0.2",
"license": "MIT",
"description": "Accessible, fuzzy search typeahead component",
"author": "Eric Liu (https://github.com/metonym)",
"type": "module",
"svelte": "./src/index.js",
"main": "./src/index.js",
"types": "./src/index.d.ts",
"exports": {
"./*.svelte": {
"types": "./src/*.svelte.d.ts",
"import": "./src/*.svelte"
},
"./*.js": {
"types": "./src/*.d.ts",
"import": "./src/*.js"
},
".": {
"types": "./src/index.d.ts",
"import": "./src/index.js",
"svelte": "./src/index.js"
}
},
"scripts": {
"dev": "rollup -cw",
"test": "svelte-check --workspace tests"
},
"dependencies": {
"fuzzy": "0.1.3",
"svelte-search": "^2.1.2"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^21.1.0",
"@types/bun": "^1.3.3",
"culls": "^0.1.2",
"svelte": "^3.59.1",
"svelte-check": "^4.1.4",
"svelte-readme": "^3.6.3",
"typescript": "^5.9.3"
},
"repository": {
"type": "git",
"url": "git+https://github.com/metonym/svelte-typeahead.git"
},
"homepage": "https://github.com/metonym/svelte-typeahead",
"bugs": "https://github.com/metonym/svelte-typeahead/issues",
"keywords": [
"svelte",
"svelte component",
"typeahead",
"fuzzy",
"highlight",
"search",
"filter",
"WAI-ARIA",
"accessibility"
],
"files": [
"src"
]
}
================================================
FILE: rollup.config.ts
================================================
import commonjs from "@rollup/plugin-commonjs";
import svelteReadme from "svelte-readme";
export default svelteReadme({
plugins: [commonjs()],
style: `
.code-fence li + li { margin: 0; }
.code-fence { min-height: 16rem; }
.code-fence pre { margin-bottom: 0; margin-top: 12px; }
`,
});
================================================
FILE: src/Typeahead.svelte
================================================
<script>
/**
* @template TItem = string | number | Record<string, any>
*/
export let id = "typeahead-" + Math.random().toString(36);
export let value = "";
/** @type {TItem[]} */
export let data = [];
/** @type {(item: TItem) => any} */
export let extract = (item) => item;
/** @type {(item: TItem) => boolean} */
export let disable = (item) => false;
/** @type {(item: TItem) => boolean} */
export let filter = (item) => false;
/** Set to `false` to prevent the first result from being selected */
export let autoselect = true;
/**
* Set to `keep` to keep the search field unchanged after select, set to `clear` to auto-clear search field
* @type {"update" | "clear" | "keep"}
*/
export let inputAfterSelect = "update";
/** @type {{ original: TItem; index: number; score: number; string: string; disabled?: boolean; }[]} */
export let results = [];
/** Set to `true` to re-focus the input after selecting a result */
export let focusAfterSelect = false;
/** Set to `true` to only show results when the input is focused */
export let showDropdownOnFocus = false;
/** Set to `true` for all results to be shown when an empty input is focused */
export let showAllResultsOnFocus = false;
/**
* Specify the maximum number of results to return
* @type {number}
*/
export let limit = Infinity;
import fuzzy from "fuzzy";
import Search from "svelte-search";
import { tick, createEventDispatcher, afterUpdate } from "svelte";
const dispatch = createEventDispatcher();
let comboboxRef = null;
let searchRef = null;
let hideDropdown = false;
let selectedIndex = -1;
let prevResults = "";
let isFocused = false;
afterUpdate(() => {
if (prevResults !== resultsId && autoselect) {
selectedIndex = getNextNonDisabledIndex();
}
if (prevResults !== resultsId && !$$slots["no-results"]) {
hideDropdown = results.length === 0;
}
prevResults = resultsId;
});
async function select() {
const result = results[selectedIndex];
if (result.disabled) return;
const selectedValue = extract(result.original);
const searchedValue = value;
if (inputAfterSelect == "clear") value = "";
if (inputAfterSelect == "update") value = selectedValue;
dispatch("select", {
selectedIndex,
searched: searchedValue,
selected: selectedValue,
original: result.original,
originalIndex: result.index,
});
await tick();
if (focusAfterSelect) searchRef.focus();
close();
}
/** @type {() => number} */
function getNextNonDisabledIndex() {
let index = 0;
let disabled = results[index]?.disabled ?? false;
while (disabled) {
if (index === results.length) {
index = 0;
} else {
index += 1;
}
disabled = results[index]?.disabled ?? false;
}
return index;
}
/** @type {(direction: -1 | 1) => void} */
function change(direction) {
let index =
direction === 1 && selectedIndex === results.length - 1
? 0
: selectedIndex + direction;
if (index < 0) index = results.length - 1;
let disabled = results[index].disabled;
while (disabled) {
if (index === results.length) {
index = 0;
} else {
index += direction;
}
disabled = results[index].disabled;
}
selectedIndex = index;
}
const open = () => (hideDropdown = false);
const close = () => {
hideDropdown = true;
isFocused = false;
};
$: options = { pre: "<mark>", post: "</mark>", extract };
$: results = fuzzy
.filter(value, data, options)
.filter(({ score }) => score > 0)
.slice(0, limit)
.filter((result) => !filter(result.original))
.map((result) => ({ ...result, disabled: disable(result.original) }));
$: resultsId = results.map((result) => extract(result.original)).join("");
$: showResults = !hideDropdown && results.length > 0;
$: if (showDropdownOnFocus) {
showResults = showResults && isFocused;
}
$: if (isFocused && showAllResultsOnFocus && value.length === 0) {
results = data
.filter((datum) => !filter(datum))
.map((original, index) => ({
disabled: disable(original),
index,
original,
score: 0,
string: extract(original),
}));
}
</script>
<svelte:window
on:click={({ target }) => {
if (!hideDropdown && !comboboxRef?.contains(target)) {
close();
}
}}
/>
<div
data-svelte-typeahead
bind:this={comboboxRef}
role="combobox"
aria-haspopup="listbox"
aria-controls="{id}-listbox"
class:dropdown={results.length > 0}
aria-expanded={showResults}
id="{id}-typeahead"
>
<Search
{id}
removeFormAriaAttributes={true}
{...$$restProps}
bind:ref={searchRef}
aria-autocomplete="list"
aria-controls="{id}-listbox"
aria-labelledby="{id}-label"
aria-activedescendant={selectedIndex >= 0 &&
!hideDropdown &&
results.length > 0
? `${id}-result-${selectedIndex}`
: null}
bind:value
on:type
on:input
on:change
on:focus
on:focus={() => {
open();
if (showDropdownOnFocus || showAllResultsOnFocus) {
showResults = true;
isFocused = true;
}
}}
on:clear
on:clear={open}
on:blur={(e) => {
// Check if focus is moving to an element within the combobox
const relatedTarget = e.relatedTarget;
if (relatedTarget && comboboxRef?.contains(relatedTarget)) {
return;
}
// Close immediately for keyboard navigation (Tab, Shift+Tab)
close();
}}
on:keydown
on:keydown={(e) => {
if (results.length === 0) return;
switch (e.key) {
case "Enter":
select();
break;
case "ArrowDown":
e.preventDefault();
change(1);
break;
case "ArrowUp":
e.preventDefault();
change(-1);
break;
case "Escape":
e.preventDefault();
value = "";
searchRef?.focus();
close();
break;
}
}}
/>
<ul
class:svelte-typeahead-list={true}
role="listbox"
aria-labelledby="{id}-label"
id="{id}-listbox"
>
{#if showResults}
{#each results as result, index}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<li
role="option"
id="{id}-result-{index}"
class:selected={selectedIndex === index}
class:disabled={result.disabled}
aria-selected={selectedIndex === index}
on:mousedown={(e) => {
if (result.disabled) return;
e.preventDefault(); // Prevent input from losing focus
selectedIndex = index;
select();
}}
on:mouseenter={() => {
if (result.disabled) return;
selectedIndex = index;
}}
>
<slot {result} {index} {value}>
{@html result.string}
</slot>
</li>
{/each}
{/if}
{#if $$slots["no-results"] && !hideDropdown && value.length > 0 && results.length === 0}
<div class:no-results={true}>
<slot name="no-results" {value} />
</div>
{/if}
</ul>
</div>
<style>
[data-svelte-typeahead] {
position: relative;
background-color: #fff;
}
ul {
position: absolute;
top: 100%;
left: 0;
width: 100%;
margin: 0;
padding: 0;
list-style: none;
background-color: inherit;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
[aria-expanded="true"] ul {
z-index: 1;
}
li,
.no-results {
padding: 0.25rem 1rem;
}
li {
cursor: pointer;
}
li:not(:last-of-type) {
border-bottom: 1px solid #e0e0e0;
}
li:hover {
background-color: #e5e5e5;
}
.selected {
background-color: #e5e5e5;
}
.selected:hover {
background-color: #cacaca;
}
.disabled {
opacity: 0.4;
cursor: not-allowed;
}
:global([data-svelte-search] label) {
margin-bottom: 0.25rem;
display: inline-flex;
font-size: 0.875rem;
}
:global([data-svelte-search] input) {
width: 100%;
padding: 0.5rem 0.75rem;
background: none;
font-size: 1rem;
border: 0;
border-radius: 0;
border: 1px solid #e5e5e5;
}
:global([data-svelte-search] input:focus) {
outline-color: #0f62fe;
outline-offset: 2px;
outline-width: 1px;
}
</style>
================================================
FILE: src/Typeahead.svelte.d.ts
================================================
import type { SvelteComponentTyped } from "svelte";
import type { SearchProps } from "svelte-search/src/Search.svelte";
export interface TypeaheadProps<TItem> extends Omit<SearchProps, "results"> {
/**
* @default "typeahead-" + Math.random().toString(36)
*/
id?: string;
/**
* @default ""
*/
value?: string;
/**
* @default []
*/
data?: TItem[];
/**
* @default (item) => item
*/
extract?: (item: TItem) => any;
/**
* @default (item) => false
*/
disable?: (item: TItem) => boolean;
/**
* @default (item) => false
*/
filter?: (item: TItem) => boolean;
/**
* Set to `false` to prevent the first result from being selected
* @default true
*/
autoselect?: boolean;
/**
* Set to `keep` to keep the search field unchanged after select, set to `clear` to auto-clear search field
* @default "update"
*/
inputAfterSelect?: "update" | "clear" | "keep";
/**
* @default []
*/
results?: {
original: TItem;
index: number;
score: number;
string: string;
disabled?: boolean;
}[];
/**
* Set to `true` to re-focus the input after selecting a result
* @default false
*/
focusAfterSelect?: boolean;
/**
* Set to `true` to only show results when the input is focused
* @default false
*/
showDropdownOnFocus?: boolean;
/**
* Set to `true` for all results to be shown when an empty input is focused
* @default false
*/
showAllResultsOnFocus?: boolean;
/**
* Specify the maximum number of results to return
* @default Infinity
*/
limit?: number;
}
export default class Typeahead<
TItem = string | number | Record<string, any>
> extends SvelteComponentTyped<
TypeaheadProps<TItem>,
{
select: CustomEvent<{
searched: string;
selected: TItem;
selectedIndex: number;
original: TItem;
originalIndex: number;
}>;
type: CustomEvent<string>;
clear: CustomEvent<null>;
input: WindowEventMap["input"];
change: WindowEventMap["change"];
focus: WindowEventMap["focus"];
blur: WindowEventMap["blur"];
keydown: WindowEventMap["keydown"];
},
{
default: {
result: {
original: TItem;
index: number;
score: number;
string: string;
};
index: number;
value: string;
};
"no-results": {
value: string;
};
}
> {}
================================================
FILE: src/index.d.ts
================================================
export { default } from "./Typeahead.svelte";
================================================
FILE: src/index.js
================================================
export { default } from "./Typeahead.svelte";
================================================
FILE: tests/Typeahead.test.svelte
================================================
<script lang="ts">
import Typeahead from "svelte-typeahead";
import type { TypeaheadProps } from "svelte-typeahead/Typeahead.svelte";
import T from "svelte-typeahead";
type Item = (typeof data)[number];
let results: TypeaheadProps<Item>["results"] = [];
$: console.log(results?.[0]?.disabled);
const data = [
{ id: 0, state: "California" },
{ id: 1, state: "North Carolina" },
{ id: 2, state: "North Dakota" },
{ id: 3, state: "South Carolina" },
{ id: 4, state: "South Dakota" },
{ id: 5, state: "Michigan" },
{ id: 6, state: "Tennessee" },
{ id: 7, state: "Nevada" },
{ id: 8, state: "New Hampshire" },
{ id: 9, state: "New Jersey" },
];
const extract = (item: Item) => item.state;
const disable = (item: Item) => item.state.length > 10;
const filter = (item: Item) => item.id < 4;
</script>
<Typeahead
{extract}
{disable}
{filter}
limit={1}
autocapitalize={false + ""}
placeholder="#{4}"
autofocus
hideLabel
focusAfterSelect
inputAfterSelect="keep"
debounce={800}
showAllResultsOnFocus
{data}
on:select={(e) => {
console.log("select", e.detail);
}}
on:clear
on:type={(e) => {
console.log(e.detail);
}}
bind:results
let:result
let:index
let:value={searchedValue}
>
{result.original}
{@html result.string}
{index}
{result.score}
{searchedValue}
<svelte:fragment slot="no-results" let:value>
No results {value}
</svelte:fragment>
</Typeahead>
<T debounce={300} />
================================================
FILE: tsconfig.json
================================================
{
"compilerOptions": {
"noEmit": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"verbatimModuleSyntax": true,
"isolatedModules": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "node",
"strict": true,
"skipLibCheck": true,
"paths": {
"svelte-typeahead": ["./src"],
"svelte-typeahead/*": ["./src/*"]
}
},
"include": ["src", "tests"]
}
gitextract_vgom6s0h/ ├── .github/ │ └── workflows/ │ ├── ci.yml │ ├── github-pages.yml │ └── release.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── package.json ├── rollup.config.ts ├── src/ │ ├── Typeahead.svelte │ ├── Typeahead.svelte.d.ts │ ├── index.d.ts │ └── index.js ├── tests/ │ └── Typeahead.test.svelte └── tsconfig.json
SYMBOL INDEX (2 symbols across 1 files)
FILE: src/Typeahead.svelte.d.ts
type TypeaheadProps (line 4) | interface TypeaheadProps<TItem> extends Omit<SearchProps, "results"> {
class Typeahead (line 83) | class Typeahead<
Condensed preview — 15 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (34K chars).
[
{
"path": ".github/workflows/ci.yml",
"chars": 285,
"preview": "on:\n pull_request:\n push:\n branches: [master]\n\njobs:\n test:\n runs-on: ubuntu-latest\n steps:\n - uses: ac"
},
{
"path": ".github/workflows/github-pages.yml",
"chars": 447,
"preview": "on:\n push:\n branches: [master]\n\njobs:\n deploy:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout"
},
{
"path": ".github/workflows/release.yml",
"chars": 543,
"preview": "on:\n push:\n tags:\n - \"v*\"\n\njobs:\n build:\n runs-on: ubuntu-latest\n permissions:\n contents: read\n "
},
{
"path": ".gitignore",
"chars": 41,
"preview": ".DS_Store\n*.tgz\n/dist\n/lib\n/node_modules\n"
},
{
"path": "CHANGELOG.md",
"chars": 6220,
"preview": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Change"
},
{
"path": "LICENSE",
"chars": 1073,
"preview": "MIT License\n\nCopyright (c) 2020-present Eric Liu\n\nPermission is hereby granted, free of charge, to any person obtaining "
},
{
"path": "README.md",
"chars": 8593,
"preview": "# svelte-typeahead\n\n[![NPM][npm]][npm-url]\n\n> Accessible, fuzzy search typeahead component.\n\n<!-- REPO_URL -->\n\nThis com"
},
{
"path": "package.json",
"chars": 1453,
"preview": "{\n \"name\": \"svelte-typeahead\",\n \"version\": \"5.0.2\",\n \"license\": \"MIT\",\n \"description\": \"Accessible, fuzzy search typ"
},
{
"path": "rollup.config.ts",
"chars": 304,
"preview": "import commonjs from \"@rollup/plugin-commonjs\";\nimport svelteReadme from \"svelte-readme\";\n\nexport default svelteReadme({"
},
{
"path": "src/Typeahead.svelte",
"chars": 8494,
"preview": "<script>\n /**\n * @template TItem = string | number | Record<string, any>\n */\n\n export let id = \"typeahead-\" + Math"
},
{
"path": "src/Typeahead.svelte.d.ts",
"chars": 2404,
"preview": "import type { SvelteComponentTyped } from \"svelte\";\nimport type { SearchProps } from \"svelte-search/src/Search.svelte\";\n"
},
{
"path": "src/index.d.ts",
"chars": 46,
"preview": "export { default } from \"./Typeahead.svelte\";\n"
},
{
"path": "src/index.js",
"chars": 46,
"preview": "export { default } from \"./Typeahead.svelte\";\n"
},
{
"path": "tests/Typeahead.test.svelte",
"chars": 1503,
"preview": "<script lang=\"ts\">\n import Typeahead from \"svelte-typeahead\";\n import type { TypeaheadProps } from \"svelte-typeahead/T"
},
{
"path": "tsconfig.json",
"chars": 444,
"preview": "{\n \"compilerOptions\": {\n \"noEmit\": true,\n \"esModuleInterop\": true,\n \"forceConsistentCasingInFileNames\": true,\n"
}
]
About this extraction
This page contains the full source code of the metonym/svelte-typeahead GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 15 files (31.1 KB), approximately 8.7k tokens, and a symbol index with 2 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.