diff --git a/.eslintrc.json b/.eslintrc.json index 71ec499..0a5c57b 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -39,7 +39,7 @@ "@typescript-eslint/prefer-for-of": 0, "@typescript-eslint/member-ordering": 0, "@typescript-eslint/explicit-function-return-type": 0, - "@typescript-eslint/no-unused-vars": 0, + "@typescript-eslint/no-non-null-assertion": 0, "@typescript-eslint/no-use-before-define": [ 2, { "functions": false } diff --git a/src/index.spec.ts b/src/index.spec.ts index 346b37e..909c03e 100644 --- a/src/index.spec.ts +++ b/src/index.spec.ts @@ -49,5 +49,9 @@ describe("index", () => { expect(filter("p:first", ps)).toHaveLength(1); expect(filter("div p:first", ps)).toHaveLength(1); expect(filter("div:first p:first", ps)).toHaveLength(1); + expect(filter("p:nth-child(1), :last", ps)).toHaveLength(2); + expect( + filter("div p:not(:scope)", ps, { context: [ps[1]] }) + ).toHaveLength(1); }); }); diff --git a/src/index.ts b/src/index.ts index e982956..29578a5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -62,17 +62,13 @@ export function filter( elements: Element[], options: Options = {} ): Element[] { - return DomUtils.uniqueSort( - filterParsed(parse(selector, options), elements, options) - ) as Element[]; + return filterParsed(parse(selector, options), elements, options); } /** * Filter a set of elements by a selector. * - * If there are multiple selectors, this can - * return elements multiple times; use `uniqueSort` - * to eliminate duplicates afterwards. + * Will return elements in the original order. * * @param selector Selector to filter by. * @param elements Elements to filter. @@ -86,13 +82,31 @@ function filterParsed( if (elements.length === 0) return []; const [plainSelectors, filteredSelectors] = groupSelectors(selector); - const results = []; + let found: undefined | Set; if (plainSelectors.length) { - results.push(filterElements(elements, plainSelectors, options)); + const filtered = filterElements(elements, plainSelectors, options); + + // If there are no filters, just return + if (filteredSelectors.length === 0) { + return filtered; + } + + // Otherwise, we have to do some filtering + if (filtered.length) { + found = new Set(filtered); + } } - for (const filteredSelector of filteredSelectors) { + for (let i = 0; i < filteredSelectors.length; i++) { + const filteredSelector = filteredSelectors[i]; + const missing = found + ? elements.filter((e) => !found!.has(e)) + : elements; + + if (missing.length === 0) break; + let filtered: Element[]; + if (filteredSelector.some(isTraversal)) { /* * Get one root node, run selector with the scope @@ -100,28 +114,42 @@ function filterParsed( */ const root = getDocumentRoot(elements[0]); const sel = [...filteredSelector, CUSTOM_SCOPE_PSEUDO]; - results.push( - findFilterElements( - root as Element, - sel, - options, - true, - elements - ) + filtered = findFilterElements( + root as Element, + sel, + options, + true, + elements ); } else { // Performance optimization: If we don't have to traverse, just filter set. - results.push( - findFilterElements(elements, filteredSelector, options, false) + filtered = findFilterElements( + elements, + filteredSelector, + options, + false ); } - } - if (results.length === 1) { - return results[0]; + if (!found) { + /* + * If we haven't found anything before the last selector, + * just return what we found now. + */ + if (i === filteredSelectors.length - 1) { + return filtered; + } + if (filtered.length) { + found = new Set(filtered); + } + } else if (filtered.length) { + filtered.forEach((el) => found!.add(el)); + } } - return results.reduce((arr, rest) => [...arr, ...rest], []); + return typeof found !== "undefined" + ? elements.filter((el) => found!.has(el)) + : []; } export function select(