Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@medv/finder

Package Overview
Dependencies
Maintainers
1
Versions
15
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@medv/finder - npm Package Compare versions

Comparing version 2.1.0 to 3.0.0

2

finder.d.ts

@@ -1,2 +0,2 @@

export declare type Options = {
export type Options = {
root: Element;

@@ -3,0 +3,0 @@ idName: (name: string) => boolean;

@@ -1,9 +0,4 @@

var Limit;
(function (Limit) {
Limit[Limit["All"] = 0] = "All";
Limit[Limit["Two"] = 1] = "Two";
Limit[Limit["One"] = 2] = "One";
})(Limit || (Limit = {}));
let config;
let rootDocument;
let uniqueCache;
export function finder(input, options) {

@@ -13,4 +8,4 @@ if (input.nodeType !== Node.ELEMENT_NODE) {

}
if ("html" === input.tagName.toLowerCase()) {
return "html";
if ('html' === input.tagName.toLowerCase()) {
return 'html';
}

@@ -28,5 +23,6 @@ const defaults = {

};
config = Object.assign(Object.assign({}, defaults), options);
config = { ...defaults, ...options };
rootDocument = findRootDocument(config.root, defaults);
let path = bottomUpSearch(input, Limit.All, () => bottomUpSearch(input, Limit.Two, () => bottomUpSearch(input, Limit.One)));
uniqueCache = new Map();
let path = bottomUpSearch(input, 'all', () => bottomUpSearch(input, 'two', () => bottomUpSearch(input, 'one', () => bottomUpSearch(input, 'none'))));
if (path) {

@@ -57,3 +53,3 @@ const optimized = sort(optimize(path, input));

let i = 0;
while (current && current !== config.root.parentElement) {
while (current) {
let level = maybe(id(current)) ||

@@ -64,3 +60,3 @@ maybe(...attr(current)) ||

const nth = index(current);
if (limit === Limit.All) {
if (limit == 'all') {
if (nth) {

@@ -70,3 +66,3 @@ level = level.concat(level.filter(dispensableNth).map((node) => nthChild(node, nth)));

}
else if (limit === Limit.Two) {
else if (limit == 'two') {
level = level.slice(0, 1);

@@ -77,3 +73,3 @@ if (nth) {

}
else if (limit === Limit.One) {
else if (limit == 'one') {
const [node] = (level = level.slice(0, 1));

@@ -84,2 +80,8 @@ if (nth && dispensableNth(node)) {

}
else if (limit == 'none') {
level = [any()];
if (nth) {
level = [nthChild(level[0], nth)];
}
}
for (let node of level) {

@@ -101,2 +103,5 @@ node.level = i;

}
if (!path && fallback) {
return fallback();
}
return path;

@@ -135,8 +140,11 @@ }

function unique(path) {
switch (rootDocument.querySelectorAll(selector(path)).length) {
const css = selector(path);
switch (rootDocument.querySelectorAll(css).length) {
case 0:
throw new Error(`Can't select any node with this selector: ${selector(path)}`);
throw new Error(`Can't select any node with this selector: ${css}`);
case 1:
uniqueCache.set(css, true);
return true;
default:
uniqueCache.set(css, false);
return false;

@@ -146,6 +154,6 @@ }

function id(input) {
const elementId = input.getAttribute("id");
const elementId = input.getAttribute('id');
if (elementId && config.idName(elementId)) {
return {
name: "#" + cssesc(elementId, { isIdentifier: true }),
name: '#' + cssesc(elementId, { isIdentifier: true }),
penalty: 0,

@@ -159,3 +167,3 @@ };

return attrs.map((attr) => ({
name: "[" +
name: '[' +
cssesc(attr.name, { isIdentifier: true }) +

@@ -171,3 +179,3 @@ '="' +

return names.map((name) => ({
name: "." + cssesc(name, { isIdentifier: true }),
name: '.' + cssesc(name, { isIdentifier: true }),
penalty: 1,

@@ -188,3 +196,3 @@ }));

return {
name: "*",
name: '*',
penalty: 3,

@@ -221,3 +229,3 @@ };

function dispensableNth(node) {
return node.name !== "html" && !node.name.startsWith("#");
return node.name !== 'html' && !node.name.startsWith('#');
}

@@ -245,3 +253,3 @@ function maybe(...level) {

function sort(paths) {
return Array.from(paths).sort((a, b) => penalty(a) - penalty(b));
return [...paths].sort((a, b) => penalty(a) - penalty(b));
}

@@ -281,14 +289,14 @@ function* optimize(path, input, scope = {

isIdentifier: false,
quotes: "single",
quotes: 'single',
wrap: false,
};
function cssesc(string, opt = {}) {
const options = Object.assign(Object.assign({}, defaultOptions), opt);
if (options.quotes != "single" && options.quotes != "double") {
options.quotes = "single";
const options = { ...defaultOptions, ...opt };
if (options.quotes != 'single' && options.quotes != 'double') {
options.quotes = 'single';
}
const quote = options.quotes == "double" ? '"' : "'";
const quote = options.quotes == 'double' ? '"' : '\'';
const isIdentifier = options.isIdentifier;
const firstChar = string.charAt(0);
let output = "";
let output = '';
let counter = 0;

@@ -315,3 +323,3 @@ const length = string.length;

}
value = "\\" + codePoint.toString(16).toUpperCase() + " ";
value = '\\' + codePoint.toString(16).toUpperCase() + ' ';
}

@@ -321,17 +329,17 @@ else {

if (regexAnySingleEscape.test(character)) {
value = "\\" + character;
value = '\\' + character;
}
else {
value = "\\" + codePoint.toString(16).toUpperCase() + " ";
value = '\\' + codePoint.toString(16).toUpperCase() + ' ';
}
}
else if (/[\t\n\f\r\x0B]/.test(character)) {
value = "\\" + codePoint.toString(16).toUpperCase() + " ";
value = '\\' + codePoint.toString(16).toUpperCase() + ' ';
}
else if (character == "\\" ||
else if (character == '\\' ||
(!isIdentifier &&
((character == '"' && quote == character) ||
(character == "'" && quote == character))) ||
(character == '\'' && quote == character))) ||
(isIdentifier && regexSingleEscape.test(character))) {
value = "\\" + character;
value = '\\' + character;
}

@@ -346,6 +354,6 @@ else {

if (/^-[-\d]/.test(output)) {
output = "\\-" + output.slice(1);
output = '\\-' + output.slice(1);
}
else if (/\d/.test(firstChar)) {
output = "\\3" + firstChar + " " + output.slice(1);
output = '\\3' + firstChar + ' ' + output.slice(1);
}

@@ -362,3 +370,3 @@ }

// Strip the space.
return ($1 || "") + $2;
return ($1 || '') + $2;
});

@@ -365,0 +373,0 @@ if (!isIdentifier && options.wrap) {

@@ -1,39 +0,35 @@

type Node = {
name: string;
penalty: number;
level?: number;
};
// License: MIT
// Author: Anton Medvedev <anton@medv.io>
// Source: https://github.com/antonmedv/finder
type Path = Node[];
enum Limit {
All,
Two,
One,
type Knot = {
name: string
penalty: number
level?: number
}
type Path = Knot[]
export type Options = {
root: Element;
idName: (name: string) => boolean;
className: (name: string) => boolean;
tagName: (name: string) => boolean;
attr: (name: string, value: string) => boolean;
seedMinLength: number;
optimizedMinLength: number;
threshold: number;
maxNumberOfTries: number;
};
root: Element
idName: (name: string) => boolean
className: (name: string) => boolean
tagName: (name: string) => boolean
attr: (name: string, value: string) => boolean
seedMinLength: number
optimizedMinLength: number
threshold: number
maxNumberOfTries: number
}
let config: Options;
let config: Options
let rootDocument: Document | Element
let rootDocument: Document | Element;
export function finder(input: Element, options?: Partial<Options>) {
if (input.nodeType !== Node.ELEMENT_NODE) {
throw new Error(`Can't generate CSS selector for non-element node type.`);
throw new Error(`Can't generate CSS selector for non-element node type.`)
}
if ("html" === input.tagName.toLowerCase()) {
return "html";
if ('html' === input.tagName.toLowerCase()) {
return 'html'
}
const defaults: Options = {

@@ -49,22 +45,21 @@ root: document.body,

maxNumberOfTries: 10000,
};
}
config = { ...defaults, ...options };
config = {...defaults, ...options}
rootDocument = findRootDocument(config.root, defaults)
rootDocument = findRootDocument(config.root, defaults);
let path =
bottomUpSearch(input, 'all',
() => bottomUpSearch(input, 'two',
() => bottomUpSearch(input, 'one',
() => bottomUpSearch(input, 'none'))))
let path = bottomUpSearch(input, Limit.All, () =>
bottomUpSearch(input, Limit.Two, () => bottomUpSearch(input, Limit.One))
);
if (path) {
const optimized = sort(optimize(path, input));
const optimized = sort(optimize(path, input))
if (optimized.length > 0) {
path = optimized[0];
path = optimized[0]
}
return selector(path);
return selector(path)
} else {
throw new Error(`Selector was not found.`);
throw new Error(`Selector was not found.`)
}

@@ -75,8 +70,8 @@ }

if (rootNode.nodeType === Node.DOCUMENT_NODE) {
return rootNode;
return rootNode
}
if (rootNode === defaults.root) {
return rootNode.ownerDocument as Document;
return rootNode.ownerDocument as Document
}
return rootNode;
return rootNode
}

@@ -86,138 +81,130 @@

input: Element,
limit: Limit,
limit: 'all' | 'two' | 'one' | 'none',
fallback?: () => Path | null
): Path | null {
let path: Path | null = null;
let stack: Node[][] = [];
let current: Element | null = input;
let i = 0;
while (current && current !== config.root.parentElement) {
let level: Node[] = maybe(id(current)) ||
let path: Path | null = null
let stack: Knot[][] = []
let current: Element | null = input
let i = 0
while (current) {
let level: Knot[] = maybe(id(current)) ||
maybe(...attr(current)) ||
maybe(...classNames(current)) ||
maybe(tagName(current)) || [any()];
const nth = index(current);
if (limit === Limit.All) {
maybe(tagName(current)) || [any()]
const nth = index(current)
if (limit == 'all') {
if (nth) {
level = level.concat(
level.filter(dispensableNth).map((node) => nthChild(node, nth))
);
)
}
} else if (limit === Limit.Two) {
level = level.slice(0, 1);
} else if (limit == 'two') {
level = level.slice(0, 1)
if (nth) {
level = level.concat(
level.filter(dispensableNth).map((node) => nthChild(node, nth))
);
)
}
} else if (limit === Limit.One) {
const [node] = (level = level.slice(0, 1));
} else if (limit == 'one') {
const [node] = (level = level.slice(0, 1))
if (nth && dispensableNth(node)) {
level = [nthChild(node, nth)];
level = [nthChild(node, nth)]
}
} else if (limit == 'none') {
level = [any()]
if (nth) {
level = [nthChild(level[0], nth)]
}
}
for (let node of level) {
node.level = i;
node.level = i
}
stack.push(level);
stack.push(level)
if (stack.length >= config.seedMinLength) {
path = findUniquePath(stack, fallback);
path = findUniquePath(stack, fallback)
if (path) {
break;
break
}
}
current = current.parentElement;
i++;
current = current.parentElement
i++
}
if (!path) {
path = findUniquePath(stack, fallback);
path = findUniquePath(stack, fallback)
}
return path;
if (!path && fallback) {
return fallback()
}
return path
}
function findUniquePath(
stack: Node[][],
stack: Knot[][],
fallback?: () => Path | null
): Path | null {
const paths = sort(combinations(stack));
const paths = sort(combinations(stack))
if (paths.length > config.threshold) {
return fallback ? fallback() : null;
return fallback ? fallback() : null
}
for (let candidate of paths) {
if (unique(candidate)) {
return candidate;
return candidate
}
}
return null;
return null
}
function selector(path: Path): string {
let node = path[0];
let query = node.name;
let node = path[0]
let query = node.name
for (let i = 1; i < path.length; i++) {
const level = path[i].level || 0;
const level = path[i].level || 0
if (node.level === level - 1) {
query = `${path[i].name} > ${query}`;
query = `${path[i].name} > ${query}`
} else {
query = `${path[i].name} ${query}`;
query = `${path[i].name} ${query}`
}
node = path[i];
node = path[i]
}
return query;
return query
}
function penalty(path: Path): number {
return path.map((node) => node.penalty).reduce((acc, i) => acc + i, 0);
return path.map((node) => node.penalty).reduce((acc, i) => acc + i, 0)
}
function unique(path: Path) {
switch (rootDocument.querySelectorAll(selector(path)).length) {
const css = selector(path)
switch (rootDocument.querySelectorAll(css).length) {
case 0:
throw new Error(
`Can't select any node with this selector: ${selector(path)}`
);
`Can't select any node with this selector: ${css}`
)
case 1:
return true;
return true
default:
return false;
return false
}
}
function id(input: Element): Node | null {
const elementId = input.getAttribute("id");
function id(input: Element): Knot | null {
const elementId = input.getAttribute('id')
if (elementId && config.idName(elementId)) {
return {
name: "#" + cssesc(elementId, { isIdentifier: true }),
name: '#' + cssesc(elementId, {isIdentifier: true}),
penalty: 0,
};
}
}
return null;
return null
}
function attr(input: Element): Node[] {
function attr(input: Element): Knot[] {
const attrs = Array.from(input.attributes).filter((attr) =>
config.attr(attr.name, attr.value)
);
)
return attrs.map(
(attr): Node => ({
(attr): Knot => ({
name:
"[" +
cssesc(attr.name, { isIdentifier: true }) +
'[' +
cssesc(attr.name, {isIdentifier: true}) +
'="' +

@@ -228,18 +215,17 @@ cssesc(attr.value) +

})
);
)
}
function classNames(input: Element): Node[] {
const names = Array.from(input.classList).filter(config.className);
function classNames(input: Element): Knot[] {
const names = Array.from(input.classList).filter(config.className)
return names.map(
(name): Node => ({
name: "." + cssesc(name, { isIdentifier: true }),
(name): Knot => ({
name: '.' + cssesc(name, {isIdentifier: true}),
penalty: 1,
})
);
)
}
function tagName(input: Element): Node | null {
const name = input.tagName.toLowerCase();
function tagName(input: Element): Knot | null {
const name = input.tagName.toLowerCase()
if (config.tagName(name)) {

@@ -249,71 +235,66 @@ return {

penalty: 2,
};
}
}
return null;
return null
}
function any(): Node {
function any(): Knot {
return {
name: "*",
name: '*',
penalty: 3,
};
}
}
function index(input: Element): number | null {
const parent = input.parentNode;
const parent = input.parentNode
if (!parent) {
return null;
return null
}
let child = parent.firstChild;
let child = parent.firstChild
if (!child) {
return null;
return null
}
let i = 0;
let i = 0
while (child) {
if (child.nodeType === Node.ELEMENT_NODE) {
i++;
i++
}
if (child === input) {
break;
break
}
child = child.nextSibling;
child = child.nextSibling
}
return i;
return i
}
function nthChild(node: Node, i: number): Node {
function nthChild(node: Knot, i: number): Knot {
return {
name: node.name + `:nth-child(${i})`,
penalty: node.penalty + 1,
};
}
}
function dispensableNth(node: Node) {
return node.name !== "html" && !node.name.startsWith("#");
function dispensableNth(node: Knot) {
return node.name !== 'html' && !node.name.startsWith('#')
}
function maybe(...level: (Node | null)[]): Node[] | null {
const list = level.filter(notEmpty);
function maybe(...level: (Knot | null)[]): Knot[] | null {
const list = level.filter(notEmpty)
if (list.length > 0) {
return list;
return list
}
return null;
return null
}
function notEmpty<T>(value: T | null | undefined): value is T {
return value !== null && value !== undefined;
return value !== null && value !== undefined
}
function* combinations(stack: Node[][], path: Node[] = []): Generator<Node[]> {
function* combinations(stack: Knot[][], path: Knot[] = []): Generator<Knot[]> {
if (stack.length > 0) {
for (let node of stack[0]) {
yield* combinations(stack.slice(1, stack.length), path.concat(node));
yield* combinations(stack.slice(1, stack.length), path.concat(node))
}
} else {
yield path;
yield path
}

@@ -323,9 +304,9 @@ }

function sort(paths: Iterable<Path>): Path[] {
return Array.from(paths).sort((a, b) => penalty(a) - penalty(b));
return [...paths].sort((a, b) => penalty(a) - penalty(b))
}
type Scope = {
counter: number;
visited: Map<string, boolean>;
};
counter: number
visited: Map<string, boolean>
}

@@ -339,19 +320,19 @@ function* optimize(

}
): Generator<Node[]> {
): Generator<Knot[]> {
if (path.length > 2 && path.length > config.optimizedMinLength) {
for (let i = 1; i < path.length - 1; i++) {
if (scope.counter > config.maxNumberOfTries) {
return; // Okay At least I tried!
return // Okay At least I tried!
}
scope.counter += 1;
const newPath = [...path];
newPath.splice(i, 1);
const newPathKey = selector(newPath);
scope.counter += 1
const newPath = [...path]
newPath.splice(i, 1)
const newPathKey = selector(newPath)
if (scope.visited.has(newPathKey)) {
return;
return
}
if (unique(newPath) && same(newPath, input)) {
yield newPath;
scope.visited.set(newPathKey, true);
yield* optimize(newPath, input, scope);
yield newPath
scope.visited.set(newPathKey, true)
yield* optimize(newPath, input, scope)
}

@@ -363,9 +344,9 @@ }

function same(path: Path, input: Element) {
return rootDocument.querySelector(selector(path)) === input;
return rootDocument.querySelector(selector(path)) === input
}
const regexAnySingleEscape = /[ -,\.\/:-@\[-\^`\{-~]/;
const regexSingleEscape = /[ -,\.\/:-@\[\]\^`\{-~]/;
const regexAnySingleEscape = /[ -,\.\/:-@\[-\^`\{-~]/
const regexSingleEscape = /[ -,\.\/:-@\[\]\^`\{-~]/
const regexExcessiveSpaces =
/(^|\\+)?(\\[A-F0-9]{1,6})\x20(?![a-fA-F0-9\x20])/g;
/(^|\\+)?(\\[A-F0-9]{1,6})\x20(?![a-fA-F0-9\x20])/g

@@ -375,22 +356,21 @@ const defaultOptions = {

isIdentifier: false,
quotes: "single",
quotes: 'single',
wrap: false,
};
}
function cssesc(string: string, opt: Partial<typeof defaultOptions> = {}) {
const options = { ...defaultOptions, ...opt };
if (options.quotes != "single" && options.quotes != "double") {
options.quotes = "single";
const options = {...defaultOptions, ...opt}
if (options.quotes != 'single' && options.quotes != 'double') {
options.quotes = 'single'
}
const quote = options.quotes == "double" ? '"' : "'";
const isIdentifier = options.isIdentifier;
const firstChar = string.charAt(0);
let output = "";
let counter = 0;
const length = string.length;
const quote = options.quotes == 'double' ? '"' : '\''
const isIdentifier = options.isIdentifier
const firstChar = string.charAt(0)
let output = ''
let counter = 0
const length = string.length
while (counter < length) {
const character = string.charAt(counter++);
let codePoint = character.charCodeAt(0);
let value: string | undefined = void 0;
const character = string.charAt(counter++)
let codePoint = character.charCodeAt(0)
let value: string | undefined = void 0
// If it’s not a printable ASCII character…

@@ -400,45 +380,43 @@ if (codePoint < 0x20 || codePoint > 0x7e) {

// It’s a high surrogate, and there is a next character.
const extra = string.charCodeAt(counter++);
const extra = string.charCodeAt(counter++)
if ((extra & 0xfc00) == 0xdc00) {
// next character is low surrogate
codePoint = ((codePoint & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000;
codePoint = ((codePoint & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000
} else {
// It’s an unmatched surrogate; only append this code unit, in case
// the next code unit is the high surrogate of a surrogate pair.
counter--;
counter--
}
}
value = "\\" + codePoint.toString(16).toUpperCase() + " ";
value = '\\' + codePoint.toString(16).toUpperCase() + ' '
} else {
if (options.escapeEverything) {
if (regexAnySingleEscape.test(character)) {
value = "\\" + character;
value = '\\' + character
} else {
value = "\\" + codePoint.toString(16).toUpperCase() + " ";
value = '\\' + codePoint.toString(16).toUpperCase() + ' '
}
} else if (/[\t\n\f\r\x0B]/.test(character)) {
value = "\\" + codePoint.toString(16).toUpperCase() + " ";
value = '\\' + codePoint.toString(16).toUpperCase() + ' '
} else if (
character == "\\" ||
character == '\\' ||
(!isIdentifier &&
((character == '"' && quote == character) ||
(character == "'" && quote == character))) ||
(character == '\'' && quote == character))) ||
(isIdentifier && regexSingleEscape.test(character))
) {
value = "\\" + character;
value = '\\' + character
} else {
value = character;
value = character
}
}
output += value;
output += value
}
if (isIdentifier) {
if (/^-[-\d]/.test(output)) {
output = "\\-" + output.slice(1);
output = '\\-' + output.slice(1)
} else if (/\d/.test(firstChar)) {
output = "\\3" + firstChar + " " + output.slice(1);
output = '\\3' + firstChar + ' ' + output.slice(1)
}
}
// Remove spaces after `\HEX` escapes that are not followed by a hex digit,

@@ -450,12 +428,11 @@ // since they’re redundant. Note that this is only possible if the escape

// It’s not safe to remove the space, so don’t.
return $0;
return $0
}
// Strip the space.
return ($1 || "") + $2;
});
return ($1 || '') + $2
})
if (!isIdentifier && options.wrap) {
return quote + output + quote;
return quote + output + quote
}
return output;
return output
}
{
"name": "@medv/finder",
"version": "3.0.0",
"description": "CSS Selector Generator",
"type": "module",
"version": "2.1.0",
"description": "CSS Selector Generator",
"repository": "antonmedv/finder",
"homepage": "https://github.com/antonmedv/finder",
"author": "Anton Medvedev <anton@medv.io>",
"license": "MIT",
"keywords": [
"css",
"selector",
"generator"
],
"main": "finder.js",

@@ -22,33 +13,21 @@ "types": "finder.d.ts",

"scripts": {
"start": "tsc -w",
"test": "tsc && ava",
"prepare": "tsc",
"release": "release-it --access public",
"size": "minify finder.js --sourceType module | gzip-size"
"build": "tsc",
"test": "tsc && uvu",
"release": "release-it --access public"
},
"devDependencies": {
"ava": "^3.15.0",
"babel-minify": "*",
"browser-env": "^3.3.0",
"esm": "^3.2.25",
"gzip-size-cli": "*",
"release-it": "^13.6.1",
"ts-node": "^10.2.1",
"typescript": "3.9.3"
"jsdom": "^21.1.0",
"release-it": "^15.7.0",
"typescript": "4.9.5",
"uvu": "^0.5.6"
},
"ava": {
"require": [
"./test/helpers/setup-browser-env.js"
],
"extensions": {
"ts": "module",
"js": true
},
"nonSemVerExperiments": {
"configurableModuleFormat": true
},
"nodeArguments": [
"--loader=ts-node/esm"
]
}
"author": "Anton Medvedev <anton@medv.io>",
"license": "MIT",
"homepage": "https://github.com/antonmedv/finder",
"repository": "antonmedv/finder",
"keywords": [
"css",
"selector",
"generator"
]
}

@@ -5,14 +5,14 @@ ![finder](https://medv.io/assets/finder.png)

[![npm](https://img.shields.io/npm/v/@medv/finder?color=grightgreen)](https://www.npmjs.com/package/@medv/finder)
[![Build status](https://img.shields.io/travis/antonmedv/finder)](https://travis-ci.org/antonmedv/finder)
[![npm bundle size](https://img.shields.io/bundlephobia/minzip/@medv/finder?label=size)](https://bundlephobia.com/result?p=@medv/finder)
[![Version](https://img.shields.io/npm/v/@medv/finder?color=grightgreen)](https://www.npmjs.com/package/@medv/finder)
[![Test](https://github.com/antonmedv/finder/actions/workflows/test.yml/badge.svg)](https://github.com/antonmedv/finder/actions/workflows/test.yml)
[![Size](https://img.shields.io/bundlephobia/minzip/@medv/finder?label=size)](https://bundlephobia.com/result?p=@medv/finder)
> CSS Selector Generator
**The CSS Selector Generator**
## Features
* Generates **shortest** selectors
* Generates the **shortest** selector
* **Unique** selectors per page
* Stable and **robust** selectors
* **2.1 kB** gzip and minify size
* **2kB** minified + gzipped

@@ -25,10 +25,2 @@ ## Install

Finder can be used via modules:
```html
<script type="module">
import {finder} from 'https://medv.io/finder/finder.js'
</script>
```
## Usage

@@ -47,3 +39,3 @@

Example of generated selector:
An example of a generated selector:

@@ -56,11 +48,10 @@ ```css

`finder` takes configuration object as second parameters. Here is example of all params with default values:
```js
const selector = finder(event.target, {
root: document.body,
className: (name) => true,
tagName: (name) => true,
attr: (name, value) => false,
seedMinLength: 1,
root: document.body, // Root of search, defaults to document.body.
idName: (name) => true, // Check if this ID can be used.
className: (name) => true, // Check if this class name can be used.
tagName: (name) => true, // Check if tag name can be used.
attr: (name, value) => false, // Check if attr name can be used.
seedMinLength: 1,
optimizedMinLength: 2,

@@ -72,66 +63,28 @@ threshold: 1000,

#### `root: Element`
### seedMinLength
Root of search, defaults to `document.body`.
#### `idName: (name: string) => boolean`
Check if this ID can be used. For example you can restrict using framework specific IDs:
```js
const selector = finder(event.target, {
idName: name => !name.startsWith('ember')
})
```
#### `className: (name: string) => boolean`
Check if this class name can be used. For example you can restrict using _is-*_ class names:
```js
const selector = finder(event.target, {
className: name => !name.startsWith('is-')
})
```
#### `tagName: (name: string) => boolean`
Check if tag name can be used, same as `className`.
#### `attr: (name: string, value: string) => boolean`
Check if attr name can be used.
#### `seedMinLength: number`
Minimum length of levels in fining selector. Starts from `1`.
For more robust selectors give this param value around 4-5 depending on depth of you DOM tree.
If `finder` hits `root` this param is ignored.
For more robust selectors give this param value around 4-5 depending on depth of
you DOM tree. If `finder` hits `root` this param is ignored.
#### `optimizedMinLength: number`
### optimizedMinLength
Minimum length for optimising selector. Starts from `2`.
For example selector `body > div > div > p` can be optimized to `body p`.
For example selector `body > div > div > p` can be optimised to `body p`.
#### `threshold: number`
### threshold
Max number of selectors to check before falling into `nth-child` usage.
Checking for uniqueness of selector is very costs operation, if you have DOM tree depth of 5, with 5 classes on each level,
that gives you more than 3k selectors to check.
`finder` uses two step approach so it's reaching this threshold in some cases twice.
Default `1000` is good enough in most cases.
Checking for uniqueness of selector is very costs operation, if you have DOM
tree depth of 5, with 5 classes on each level, that gives you more than 3k
selectors to check. Finder uses two-step approach,Ï so it's reaching this
threshold in some cases twice. Default `1000` is good enough in most cases.
#### `maxNumberOfTries: number`
### maxNumberOfTries
Max number of tries when we do the optimization. It is a trade-off between optimization and efficiency.
Default `10_000` is good enough in most cases.
Max number of tries when we do the optimization. It is a trade-off between
optimization and efficiency. Default `10_000` is good enough in most cases.
### Google Chrome Extension
![Chrome Extension](https://user-images.githubusercontent.com/141232/36737287-4a999d84-1c0d-11e8-8a14-43bcf9baf7ca.png)
Generate the unique selectors in your browser by using [Chrome Extension](https://chrome.google.com/webstore/detail/get-unique-css-selector/lkfaghhbdebclkklgjhhonadomejckai)
## License
[MIT](LICENSE)
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc