{"id":13992127,"url":"https://github.com/josh/selector-set","last_synced_at":"2025-10-22T21:15:20.314Z","repository":{"id":11426493,"uuid":"13879099","full_name":"josh/selector-set","owner":"josh","description":null,"archived":true,"fork":false,"pushed_at":"2020-08-04T22:17:39.000Z","size":247,"stargazers_count":280,"open_issues_count":0,"forks_count":14,"subscribers_count":12,"default_branch":"master","last_synced_at":"2025-07-22T02:07:09.237Z","etag":null,"topics":["javascript"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/josh.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2013-10-26T06:42:29.000Z","updated_at":"2024-12-03T19:24:43.000Z","dependencies_parsed_at":"2022-09-13T13:12:29.976Z","dependency_job_id":null,"html_url":"https://github.com/josh/selector-set","commit_stats":null,"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"purl":"pkg:github/josh/selector-set","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/josh%2Fselector-set","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/josh%2Fselector-set/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/josh%2Fselector-set/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/josh%2Fselector-set/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/josh","download_url":"https://codeload.github.com/josh/selector-set/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/josh%2Fselector-set/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266520639,"owners_count":23942307,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-22T02:00:09.085Z","response_time":66,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["javascript"],"created_at":"2024-08-09T14:01:48.801Z","updated_at":"2025-10-22T21:15:19.999Z","avatar_url":"https://github.com/josh.png","language":"JavaScript","readme":"# SelectorSet\n\nAn efficient data structure for matching and querying elements against a large set of CSS selectors.\n\n## Usage\n\n```javascript\nvar set = new SelectorSet();\nset.add('#logo');\nset.add('.post');\nset.add('h1');\n\nvar el = document.createElement('h1');\nset.matches(el);\n// =\u003e [ { selector: 'h1' } ]\n\ndocument.body.appendChild(el);\nset.queryAll(document.body);\n// =\u003e [ { selector: 'h1': elements: [el] } ]\n```\n\nAn arbitrary `data` object can be associated with each selector to help identify matches.\n\n```javascript\nvar set = new SelectorSet();\n\nvar data = function setupForm() {};\nset.add('form', data);\n\nvar el = document.createElement('form');\nset.matches(el);\n// =\u003e [ { selector: 'form', data: setupForm } ]\n```\n\n## Installation\n\nAvailable on npm as **selector-set**.\n\n```\n$ npm install selector-set\n```\n\nAlternatively you can download the single `selector-set.js` file. There are no additional dependencies.\n\n```\n$ curl -O https://raw.github.com/josh/selector-set/master/selector-set.js\n```\n\n## Use cases\n\n### Batch find calls\n\nTypical web apps run a bunch of `querySelector` calls on load.\n\n```javascript\ndocument.addEventListener('DOMContentLoaded', function() {\n  var el;\n\n  if ((el = document.querySelector('form.signup'))) {\n    // ...\n  }\n\n  if ((el = document.querySelector('#sidebar'))) {\n    // ...\n  }\n\n  if ((el = document.querySelector('.menu'))) {\n    // ...\n  }\n});\n```\n\nLarge numbers of `querySelectorAll` calls they are usually made on page load can be batched up for efficiency. Having them in a set also makes it convenient to rerun on changed content.\n\n```javascript\nvar set = new SelectorSet();\nset.add('form.signup', function(form) {\n  // ...\n});\nset.add('#sidebar', function(sidebar) {\n  // ...\n});\nset.add('.menu', function(menu) {\n  // ...\n});\n\ndocument.addEventListener('DOMContentLoaded', function() {\n  set.queryAll(document).forEach(function(match) {\n    match.elements.forEach(function(el) {\n      match.data.call(el, el);\n    });\n  });\n});\n```\n\n### Match delegated events\n\nHaving a large number of delegated handlers on for a single event can slow it down on every dispatch. Doing one set match against an element is much faster than attempting to iterate and match every selector again and again.\n\n```javascript\nvar handlers = new SelectorSet();\nhandlers.add('.menu', function(event) {\n  // ...\n});\nhandlers.add('.modal', function(event) {\n  // ...\n});\n\ndocument.addEventListener(\n  'click',\n  function(event) {\n    handlers.matches(event.target).forEach(function(rule) {\n      rule.data.call(event.target, event);\n    });\n  },\n  false\n);\n```\n\n_(This is a trivialized example that doesn't handle propagated matches)_\n\n### Apply CSS rules\n\nFor fun, we could implement CSS style matching and application in pure JS.\n\n```javascript\nvar styles = new SelectorSet();\nstyles.add('p', {\n  fontSize: '12px',\n  color: 'red'\n});\nstyles.add('p.item', {\n  background: 'white'\n});\n\n// apply matching styles\nstyles.matches(el).forEach(function(rule) {\n  for (name in rule.data) el.style[name] = rule.data[name];\n});\n```\n\nOkay, you wouldn't want to ever actually do this. But consider the use case of implementing some sort of CSS property polyfill in pure JS.\n\n## Browser Support\n\nChrome (latest), Safari (6.0+), Firefox (latest) and IE 9+.\n\nThe main requirement is `querySelectorAll` and some sort of `prefixMatchesSelector` support. You can support older browser versions by supplying your own fallback versions of these functions.\n\n```javascript\n// Use Sizzle's match and query functions\nSelectorSet.prototype.querySelectorAll = Sizzle;\nSelectorSet.prototype.matchesSelector = Sizzle.matchesSelector;\n\n// Or use jQuery's internal Sizzle\nSelectorSet.prototype.querySelectorAll = jQuery.find;\nSelectorSet.prototype.matchesSelector = jQuery.find.matchesSelector;\n```\n\nUsing Sizzle will also allow you to use more advanced selectors like `:first`, `:has`, `:input` that do not exist in native `querySelector`.\n\n## Development\n\nClone the repository from GitHub.\n\n```\n$ git clone https://github.com/josh/selector-set\n```\n\nYou'll need to have [Grunt](http://gruntjs.com) installed. If you don't have the `grunt` executable available, you can install it with:\n\n```\n$ npm install -g grunt-cli\n```\n\nNow just `cd` into the directory and install the local npm dependencies.\n\n```\n$ cd selector-set/\n$ npm install\n```\n\nUse `grunt test` to run the test suite.\n\n```\n$ grunt test\nRunning \"jshint:all\" (jshint) task\n\u003e\u003e 5 files lint free.\n\nRunning \"qunit:all\" (qunit) task\nTesting test/test.html .....................OK\n\u003e\u003e 100 assertions passed (50ms)\n\nDone, without errors.\n```\n\n## Implementation\n\nActually matching a group of selectors against an element via `Element#matches` or `Sizzle` can actually be slow when you get to matching 100+ selectors. Real world applications tend to follow common selector patterns. This fact and the knowledge of a group of selectors ahead of time allows us to apply some optimizations.\n\nFirst, selectors added to the set are quickly analyzed and indexed under a key. This key is derived from a significant part of the right most side of the selector. If the selector targets an id, the id name is used as the key. If theres a class, the class name is used and so forth. The selector is then put into a map indexed by this key. Looking up the key is constant time.\n\nWhen its time to match the element against the group. The element's properties are examined for possible keys. These keys are then looked up in the mapping which returns a smaller set of selectors which then perform a full matches test against the element.\n\n### Inspired by browsers\n\nThe grouping technique isn't a new idea. In fact, it's how WebKit and Firefox work already. In order to calculate the styles of a single element, a huge number of CSS rules need to be considered. Browsers don't just iterate over every selector and test it. That would be way too slow.\n\nWebKit has a [`RuleSet`](https://github.com/WebKit/webkit/blob/c0885665302c752230987427d4021b6df634087d/Source/WebCore/css/RuleSet.cpp) class that very much inspired this library. Checkout it's definition of [`RuleSet::findBestRuleSetAndAdd`](https://github.com/WebKit/webkit/blob/c0885665302c752230987427d4021b6df634087d/Source/WebCore/css/RuleSet.cpp#L180-L231) to see how it groups CSS rules by selector category.\n\nIn the future I hope something like WebKit's `RuleSet` could be made directly available to the browser. Not only would be writing it in C++ be faster, having access to a real CSS parser would make it much more robust than this library's hacky regexp matchers.\n\n### Profile\n\nThis graph compares selector-set match time to a naive loop that tests each selector every time.\n\n![](https://f.cloud.github.com/assets/137/1523467/9370cb62-4bb6-11e3-9649-bce7f24b7042.png)\n\nAs you can see, the set-selector is mostly constant time as the number of selectors in the set grows O(1). But matching every selector every time is linear as to the number selectors O(N). There is a slight overhead to using selector-set when there are only a few selectors (\u003c5). This is something that can be improved, but it maybe pointless to use the set if you only want to match one or two selectors.\n\nHere is a jsPerf test matching a single element to all of the selectors on github.com.\n\nhttp://jsperf.com/selectorset-match\n\n### Custom indexes\n\nCurrently, only first class attributes like `id`, `class` and the tag name are indexed. But if you have some sort of application specific attribute you frequently use, you can write your own custom index on the attribute.\n\n```javascript\nSelectorSet.prototype.indexes.push({\n  name: 'data-behavior~=',\n  selector: function(sel) {\n    var m;\n    if ((m = sel.match(/\\[data-behaviors~=(\\w+)\\]/))) {\n      return m[1];\n    }\n  },\n  element: function(el) {\n    var behaviors = el.getAttribute('data-behavior');\n    if (behaviors) {\n      return behaviors.split(/\\s+/);\n    }\n  }\n});\n```\n\n## License\n\nCopyright (c) 2013 Joshua Peek\n\nDistributed under an MIT-style license. See LICENSE for details.\n","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjosh%2Fselector-set","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjosh%2Fselector-set","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjosh%2Fselector-set/lists"}