{"id":13447455,"url":"https://github.com/telenko/matcher-js","last_synced_at":"2025-09-22T04:33:27.816Z","repository":{"id":57165443,"uuid":"171959636","full_name":"telenko/matcher-js","owner":"telenko","description":"Article about using matcher-js","archived":false,"fork":false,"pushed_at":"2019-04-07T13:13:55.000Z","size":22,"stargazers_count":6,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-08-24T19:54:47.542Z","etag":null,"topics":["dom","shadowdom","webcomponents"],"latest_commit_sha":null,"homepage":"https://medium.com/@mangolik931/matcherjs-new-way-of-making-dom-extensions-ddb8a6f088ec","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/telenko.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":"2019-02-21T23:13:28.000Z","updated_at":"2019-09-10T13:47:50.000Z","dependencies_parsed_at":"2022-08-30T15:10:17.474Z","dependency_job_id":null,"html_url":"https://github.com/telenko/matcher-js","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/telenko/matcher-js","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/telenko%2Fmatcher-js","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/telenko%2Fmatcher-js/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/telenko%2Fmatcher-js/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/telenko%2Fmatcher-js/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/telenko","download_url":"https://codeload.github.com/telenko/matcher-js/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/telenko%2Fmatcher-js/sbom","scorecard":{"id":873176,"data":{"date":"2025-08-11","repo":{"name":"github.com/telenko/matcher-js","commit":"a8e23ba07241311f6995efdc1a2bed4974b3e1cb"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3,"checks":[{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Code-Review","score":0,"reason":"Found 0/10 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}}]},"last_synced_at":"2025-08-24T04:56:05.544Z","repository_id":57165443,"created_at":"2025-08-24T04:56:05.544Z","updated_at":"2025-08-24T04:56:05.544Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":276347148,"owners_count":25626597,"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-09-22T02:00:08.972Z","response_time":79,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","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":["dom","shadowdom","webcomponents"],"created_at":"2024-07-31T05:01:18.209Z","updated_at":"2025-09-22T04:33:27.533Z","avatar_url":"https://github.com/telenko.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"# General description\nLibrary allows to register callbacks to node based on attribute existence or value.\n\n# Purpose of creating\nNew standard of registering elements (CustomElements) allows us to register new HTMLElements to the DOM. It allows us to handle some callbacks of new registered element (connectedCallback, disconnectedCallback, attributeChangedCallback). For details, see: https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements.\n\nThat new technology allows developer to write code by extending current DOM model with custom reusable html elements which really changed the approaches of web-development nowadays.\n\nSo with CustomElements definition we are able to extend DOM with whathever we want and use this new \"patched\" DOM environment for our applications. But is it really allows us to extend DOM in any possible way?\n\nMy vision that - **no**. And below why.\n\n1) Reusable attributes - matchers\n\nIn current DOM API there are set of reusable attributes which can work for any html element: 'title', 'aria-label', 'role', 'class', 'id' etc. All these attributes are responsible for some behavior for a node, to which they are connected. If programically or via inspector such attribute will be removed - then it will be disconnected from \"node\". Looks pretty similar to \"something\" isn't it? Some piece of code can be \"connected\" to node by some \"condition\" and \"disconnected\" when this condition won't match anymore.\n\nSince these attributes are not just regular attributes I've decided to invent a separate term for them - **Matcher**. Matcher is an entity which connects to html node when node matches matcher's condition. When node stops match this condition - matcher disconnects from a node.\n\nImagine that we want to implement new tooltip for our DOM elements and we want it to look much more modern than default native 'title'. We can write a new matcher with condition (f.e.) [custom-title] and then use it everywhere in our DOM:\n```HTML\n\u003cdiv custom-title=\"some text in new title\"\u003e\n  \u003cspan custom-title=\"hey, I also want to use new title!!\"\u003e\n    Should I be a WC to use a custom title ? :(\n  \u003c/span\u003e\n\u003c/div\u003e\n```\n\nLots of new libraries implements tooltip-like features via web components like this:\n```HTML\n\u003ccustom-title text='some text in new title'\u003e\n  \u003cdiv not-a-container-anymore\u003e\n    \u003ccustom-title text='hey, I also want to use new title!!'\u003e\n      \u003cspan\u003eouch :( Likely I'm not a direct children of my parent div element...\u003c/span\u003e\n    \u003c/custom-title\u003e\n  \u003c/div\u003e\n\u003c/custom-title\u003e\n```\nwhich I think is not pretty good usage for web components.\n\n2) Extending native elements\n\nImagine that we need to support one more 'type' for input element - 'phone'. What will we do now? Hmmm, I think we can write a web component which will be a wrapper for a native one, support new type and it should definetely REFLECT everything we have in HTMLInputElement! Isn't it too costly for a one more type??\n\n```HTML\n\u003csome-new-input ouch-forgot-to-reflect-blur-event type='phone'\u003e\u003c/some-new-input\u003e\n```\n\nWhich matchers we can define new matcher for already working input element and use it everywhere.\n```HTML\n\u003cinput type='phone'/\u003e\n```\n\nTo summarize, I want to highlight that I'm really a fan of new web components technologies, but I think that it is not appropriate everywhere. In some cases matchers are more suitable and easy to maintain.\n\n# Difference between matcher and custom element\n| Criteria                         | Custom element                   | Matcher                          |\n| -------------------------------- | -------------------------------- | -------------------------------- |\n| matches by                       | by tagname                       | by attribute                     |\n| when created (constructor call)  | html element create              | right before connectedCallback() |\n| phase when connected             | connectedCallback()              | connectedCallback()              |\n| phase when disconnected          | disconnectedCallback()           | disconnectedCallback()           |\n| observed attributes property     | static observedAttributes        | static observedAttributes        |\n| phase when observed attr changed | attributeChangedCallback()       | attributeChangedCallback()       |\n| phase moved to a new document    | adoptedCallback()                | -                                |\n\nAs you see API of defining matcher is very-very similar to custom elements (one difference is matchers dont support adoptedCallback() unlike to custom elements)\n\n\n# API\n\n1) Install library\n```\nnpm i @telenko/matcher\n```\n2) Include library to your code-base\n```\nimport { defineMatcher, getMatchers } from '@telenko/matcher';\n```\n3) Define matchers via `defineMatcher` function\n```\n//only by attribute\ndefineMatcher(\"[custom-title]\", class {\n    connectedCallback() {\n        //access real node by this.element\n        console,log(\"Ok, now make tooltip for\", this.element);\n    }\n    disconnectedCallback() {\n        console.log(\"Remove new tooltip from\", this.element);\n    }\n});\n```\n```\n//by attribute value\ndefineMatcher(\"[some-attr='only-this-val'\", class {\n    connectedCallback() {\n    }\n    disconnectedCallback() {\n    }\n});\n```\n```\n//observe attributes of node\ndefineMatcher(\"[type='datepicker'\", class {\n    \n  connectedCallback() {\n    const { element } = this;\n    somePickerLibrary(this.element,\n      { from: element.getAttribute(\"from\"),\n        to: element.getAttribute(\"to\")\n      });\n  }\n\n  disconnectedCallback() {\n    somePickerLibrary(this.element).off();\n  }\n\n  attributeChangedCallback(attr, oldv, newv) {\n    if (attr === \"from\") {\n      somePickerLibrary(this.element).setFrom(newv);\n    }\n    if (attr === \"to\") {\n      somePickerLibrary(this.element).setTo(newv);\n    }\n  }\n\n  static get observedAttributes() {\n    return [\"from\", \"to\"];\n  }\n\n});\n```\n\n4) Access matchers via 'getMatchers' function\n```\nconst inputWithDatepicker = this.querySelector(\"[type='datepicker]\");\nconst matchers = getMatchers(inputWithDatepicker); // [DatepickerMatcher]\n```\n\n# Working with ShadowDOM\nSince **matcher-js** is based on MutationObserver there are 2 ways to use matchers inside ShadowDOM:\n1) Use 'patchShadowDOM' function (**should be called only once!!!**)\n```\nimport { patchShadowDOM } from '@telenko/matcher';\npatchShadowDOM();//will patch native .attachShadow() function with matchers support\n\nconst container = document.createElement(\"div\");\ncontainer.attachShadow({mode : \"open\"});\ncontainer.shadowRoot.innerHTML = `\n  \u003cinput type='datepicker' custom-title='wow, can use it here!!'/\u003e\n`;\n```\n2) Use ``` \u003cmatchers-container\u003e``` container\n```\nconst container = document.createElement(\"div\");\ncontainer.attachShadow({mode : \"open\"});\ncontainer.shadowRoot.innerHTML = `\n  \u003cinput type='datepicker' custom-title='ouch, matchers won't see me :('/\u003e\n  \u003cmatchers-container\u003e\n    \u003cinput type='datepicker' custom-title='good, unlike to bro above'/\u003e\n  \u003c/matchers-container\u003e\n`;\n```\n\n# Development status\nCore functionality works. Currently library is under performance and unit testing coverage.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftelenko%2Fmatcher-js","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftelenko%2Fmatcher-js","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftelenko%2Fmatcher-js/lists"}