JSFiddle - React, Tailwind, and code Playground

by mgibsonbr

HTML

<ul>
</ul>
<input />
<button>buscar</button>
<button>arvore</button>
<button>\xe1rvore</button>
<button>a\u0301rvore</button>
<br/>
<label><input type="checkbox" checked disabled />Ignorar maiúsculas/minúsculas</label>
<label title="Se checado, acentos no termo de busca casam com palavras não acentuadas também. Se deixado sem, acentos no termo de busca só casam com letras igualmente acentuadas (embora as mesmas podem estar representadas de uma forma diferente)."><input type="checkbox" disabled />Ignorar acento no termo de busca</label>
<hr/>
Tempo de preparo: <span>&#x221e;</span> µs (só faz sentido se a lista for grande)
<br/>
Tempo médio de busca: <span>&#x221e;</span> µs (<span>0</span> casos)

CSS

.achou {
    background: lightgray;
}

JavaScript

/**
 * Cria uma RegExp que aceita qualquer palavra como string.
 * Ignora acentuação, maiúsculas e minusculas.
 */
function criarPadrao(str, ignoreCase) {
    // remove os meta caracteres
    str = str.replace(/([|()[{.+*?^$\\])/g,"\\$1");

    // Separa em palavras
    var words = str.split(/\s+/);

    // ordena pelo tamanho
    words.sort(function (a, b) {
        return b.length - a.length;
    });

    // troca caracteres pelos seus compositores
    var accentReplacer = function(chr) {
        return accented[chr.toUpperCase()] || chr;
    }
    for (var i = 0; i < words.length; i++) {
        words[i] = words[i].replace(/\S/g, accentReplacer);
    }

    // junta as alternativas
    var regexp = words.join("|");
    return new RegExp(regexp, 'g' + (ignoreCase ? 'i' : ''));
}

// Lista de acentuações possíveis
var accented = {
    'A': '[Aa\xaa\xc0-\xc5\xe0-\xe5\u0100-\u0105\u01cd\u01ce\u0200-\u0203\u0226\u0227\u1d2c\u1d43\u1e00\u1e01\u1e9a\u1ea0-\u1ea3\u2090\u2100\u2101\u213b\u249c\u24b6\u24d0\u3371-\u3374\u3380-\u3384\u3388\u3389\u33a9-\u33af\u33c2\u33ca\u33df\u33ff\uff21\uff41]',
    'B': '[Bb\u1d2e\u1d47\u1e02-\u1e07\u212c\u249d\u24b7\u24d1\u3374\u3385-\u3387\u33c3\u33c8\u33d4\u33dd\uff22\uff42]',
    'C': '[Cc\xc7\xe7\u0106-\u010d\u1d9c\u2100\u2102\u2103\u2105\u2106\u212d\u216d\u217d\u249e\u24b8\u24d2\u3376\u3388\u3389\u339d\u33a0\u33a4\u33c4-\u33c7\uff23\uff43]',
    'D': '[Dd\u010e\u010f\u01c4-\u01c6\u01f1-\u01f3\u1d30\u1d48\u1e0a-\u1e13\u2145\u2146\u216e\u217e\u249f\u24b9\u24d3\u32cf\u3372\u3377-\u3379\u3397\u33ad-\u33af\u33c5\u33c8\uff24\uff44]',
    'E': '[Ee\xc8-\xcb\xe8-\xeb\u0112-\u011b\u0204-\u0207\u0228\u0229\u1d31\u1d49\u1e18-\u1e1b\u1eb8-\u1ebd\u2091\u2121\u212f\u2130\u2147\u24a0\u24ba\u24d4\u3250\u32cd\u32ce\uff25\uff45]',
    'F': '[Ff\u1da0\u1e1e\u1e1f\u2109\u2131\u213b\u24a1\u24bb\u24d5\u338a-\u338c\u3399\ufb00-\ufb04\uff26\uff46]',
    'G':...