24 |
microfuzz demo
25 |
26 | Find out about microfuzz on GitHub.
27 |
28 |
29 |
Dataset:
30 |
31 |
32 | {testdataKeys.map((aDataset) => (
33 | -
34 |
40 |
41 | ))}
42 |
43 |
44 |
45 |
46 |
Fuzzy search strategy:
47 |
48 |
49 | {strategies.map((aStrategy) => (
50 | -
51 |
57 |
58 | ))}
59 |
60 |
61 |
62 |
63 | setQueryText(e.target.value)}
67 | placeholder={`Start typing to search ${datasetName}…`}
68 | autoFocus
69 | />
70 |
71 | {dataset.length >= 10_000 ? (
72 |
73 | Note: microfuzz works best with datasets below 10,000 items (this one has {dataset.length}
74 | )
75 |
76 | ) : null}
77 |
78 | {filtered.slice(0, 40).map(([item, highlightRanges]) => (
79 | -
80 |
81 |
82 | ))}
83 |
84 |
85 | Matched {filtered.length} items (out of {dataset.length}) in {filterTime.toFixed(1)} ms.
86 |
87 |
88 | )
89 | }
90 |
91 | export default App
92 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | const config = {
2 | env: {
3 | es6: true,
4 | jest: true,
5 | },
6 | plugins: ['flowtype', '@typescript-eslint'],
7 | extends: [
8 | // https://github.com/airbnb/javascript
9 | 'airbnb',
10 | 'plugin:flowtype/recommended',
11 | 'prettier',
12 | 'plugin:jest/recommended',
13 | ],
14 | parser: '@babel/eslint-parser',
15 | ignorePatterns: 'examples/typescript/**/*.ts',
16 | settings: {
17 | flowtype: {
18 | onlyFilesWithFlowAnnotation: true,
19 | },
20 | },
21 | rules: {
22 | curly: ['error', 'all'],
23 | 'class-methods-use-this': 'off',
24 | 'comma-dangle': ['error', 'always-multiline'],
25 | 'no-console': ['error'],
26 | 'no-unused-expressions': 'off',
27 | 'no-param-reassign': [
28 | 'error',
29 | {
30 | props: false,
31 | },
32 | ],
33 | 'no-useless-escape': 'off',
34 | 'func-names': 'off',
35 | 'no-underscore-dangle': 'off',
36 | 'no-use-before-define': 'off',
37 | 'no-unused-vars': [
38 | 'error',
39 | {
40 | argsIgnorePattern: '^_',
41 | },
42 | ],
43 | 'no-else-return': [
44 | 'error',
45 | {
46 | allowElseIf: true,
47 | },
48 | ],
49 | // formatting (off - formatting is Prettier's job)
50 | semi: ['error', 'never'],
51 | 'arrow-parens': 'off',
52 | 'react/jsx-closing-bracket-location': 'off',
53 | 'react/jsx-first-prop-new-line': 'off',
54 | 'operator-linebreak': 'off',
55 | 'object-curly-newline': 'off',
56 | 'function-paren-newline': 'off',
57 | 'max-classes-per-file': 'off',
58 | camelcase: 'off',
59 | 'react/jsx-indent': 'off',
60 | quotes: 'off',
61 | 'react/jsx-curly-newline': 'off',
62 | 'lines-between-class-members': 'off',
63 | 'one-var': 'off',
64 | 'arrow-body-style': 'off',
65 | // react
66 | 'react/prop-types': 'off',
67 | 'react/jsx-filename-extension': 'off',
68 | 'react/jsx-indent-props': ['error'],
69 | 'react/prefer-stateless-function': [
70 | 1,
71 | {
72 | ignorePureComponents: true,
73 | },
74 | ],
75 | 'react/jsx-boolean-value': ['error', 'always'],
76 | 'react/no-unused-prop-types': 'off',
77 | 'react/destructuring-assignment': 'off',
78 | 'react/jsx-one-expression-per-line': 'off',
79 | 'import/prefer-default-export': 'off',
80 | 'import/named': 'off', // doesn't seem to work with Flow
81 | 'import/no-extraneous-dependencies': 'off',
82 | 'import/no-cycle': 'error',
83 | 'jest/no-large-snapshots': 'warn',
84 | 'jest/no-disabled-tests': 'off',
85 | 'jest/expect-expect': 'off',
86 | 'global-require': 'off',
87 | 'no-plusplus': 'off',
88 | 'prefer-object-spread': 'off',
89 | 'react/jsx-props-no-spreading': 'off',
90 | 'react/jsx-no-bind': 'off',
91 | 'flowtype/space-after-type-colon': 'off',
92 | 'flowtype/generic-spacing': 'off',
93 | 'flowtype/delimiter-dangle': ['error', 'always-multiline'],
94 | 'flowtype/require-return-type': [
95 | 'error',
96 | 'always',
97 | {
98 | excludeArrowFunctions: true,
99 | annotateUndefined: 'always',
100 | },
101 | ],
102 | },
103 | overrides: [
104 | {
105 | files: ['src/**/*.js'],
106 | excludedFiles: ['*integrationTest.js', '*test.js', '**/__tests__/**', '*test.*.js'],
107 | rules: {
108 | 'flowtype/require-valid-file-annotation': ['error', 'always'],
109 | },
110 | },
111 | {
112 | files: ['src/**/*.ts', 'examples/typescript/*.ts'],
113 | parser: '@typescript-eslint/parser',
114 | rules: {
115 | 'flowtype/no-types-missing-file-annotation': 'off',
116 | 'no-unused-vars': 'off',
117 | },
118 | },
119 | ],
120 | globals: {
121 | document: true,
122 | window: true,
123 | self: true,
124 | globalThis: true,
125 | },
126 | }
127 |
128 | module.exports = config
129 |
--------------------------------------------------------------------------------
/src/index.d.ts:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | General idea:
4 | - case-insensitive
5 | - diacritics-insensitive
6 | - works with Latin script, Cyrillic, rudimentary CJK support
7 | - limited fuzzing: matches query letters in order, but they don't have to be consecutive
8 | (but no or very limited transposition or removals are allowed - they cause more confusion than help)
9 | - no FTS-style stemming, soundex, levenstein, autocorrect - query can be lazy, but not sloppy
10 | - sort by how well text matches the query
11 |
12 | Think VS Code/Dash-like search, not Google-like search
13 |
14 | ## Sorting
15 |
16 | Results are sorted in roughly this order:
17 |
18 | 1. [0] Exact match (found === query)
19 | 2. [0.1] Full match (like exact, but case&diacritics-insensitive)
20 | 3. [0.5] "Starts with" match
21 | 4. [0.9] Contains query (starting at word boundary) exactly
22 | 5. [1] Contains query (starting at word boundary)
23 | 6. [1.5+] Contains query words (space separated) in any order
24 | 7. [2] Contains query
25 | 8. [2+] Contains letters -- the fewer chunks, the better
26 |
27 | Note:
28 | - Lower score is better (think "error level")
29 | - Exact scoring values are not an API contract, can change between releases
30 | - Secondary sort criteria (for equal fuzzy sort) is input order
31 |
32 | */
33 |
34 | /**
35 | * Range of indices in a string, [index of first character, index of last character]
36 | */
37 | export type Range = [number, number]
38 |
39 | /**
40 | * List of character ranges in a string that should be highlighted
41 | */
42 | export type HighlightRanges = Range[]
43 |
44 | /**
45 | * List of fuzzy search matches (ranges of matching characters) for an item. This usually has one item, but can have more if `getText`
46 | * was used to return multiple strings for an item.
47 | */
48 | export type FuzzyMatches = Array