{"id":15046823,"url":"https://github.com/kevinokerlund/content-change","last_synced_at":"2026-02-07T16:32:27.707Z","repository":{"id":57206392,"uuid":"76221034","full_name":"kevinokerlund/content-change","owner":"kevinokerlund","description":"Observe and react to changes in distributed nodes in web components, Shadow DOM v0.","archived":false,"fork":false,"pushed_at":"2017-01-12T04:30:31.000Z","size":33,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-08-28T01:56:20.510Z","etag":null,"topics":["mutations","shadow-dom","slotchange","watch-changes","web-components"],"latest_commit_sha":null,"homepage":"","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/kevinokerlund.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":"2016-12-12T04:07:40.000Z","updated_at":"2017-02-17T02:58:24.000Z","dependencies_parsed_at":"2022-09-04T03:02:28.309Z","dependency_job_id":null,"html_url":"https://github.com/kevinokerlund/content-change","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/kevinokerlund/content-change","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kevinokerlund%2Fcontent-change","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kevinokerlund%2Fcontent-change/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kevinokerlund%2Fcontent-change/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kevinokerlund%2Fcontent-change/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kevinokerlund","download_url":"https://codeload.github.com/kevinokerlund/content-change/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kevinokerlund%2Fcontent-change/sbom","scorecard":{"id":557259,"data":{"date":"2025-08-11","repo":{"name":"github.com/kevinokerlund/content-change","commit":"7779e75e1975f163eb9a657bee6bd6799fb42827"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3,"checks":[{"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/30 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":"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":"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":"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":"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":"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":"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":"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":"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":"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":"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":"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":"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-20T12:42:03.659Z","repository_id":57206392,"created_at":"2025-08-20T12:42:03.659Z","updated_at":"2025-08-20T12:42:03.659Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29199788,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-07T16:28:23.579Z","status":"ssl_error","status_checked_at":"2026-02-07T16:28:22.566Z","response_time":63,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["mutations","shadow-dom","slotchange","watch-changes","web-components"],"created_at":"2024-09-24T20:53:37.471Z","updated_at":"2026-02-07T16:32:27.669Z","avatar_url":"https://github.com/kevinokerlund.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# content-change\n\nObserve and react to changes in distributed nodes in web components, Shadow DOM v0.\n\nIt behaves, and is used similarly to [Shadow DOM v1's \"slotchange\" event](https://hayato.io/2016/shadowdomv1/#events-to-react-the-change-of-distributions).\n\n\u003e Just 2.1k gzipped\n\n## Why\nShadow DOM v0, unlike Shadow DOM v1, has no specification for watching changes to distributed nodes. Because the support for v1 is not as high as v0 at this point, this library exists to provide the ability to watch distribution changes to content elements in v0, while still working with the webcomponents.js polyfill library.\n\n#### A simple example:\n```javascript\nconst contentElement = shadow.getElementById('#content');\ncontentElement.addEventListener('contentchange', event =\u003e {\n\t// React to distributed node changes! Keep reading for details on the event data\n});\n```\n\n## Install\nYou can install it from npm\n```\nnpm install --save content-change\n```\n\nIf you're not using package management, you can [download a ZIP](https://github.com/kevinokerlund/content-change/archive/master.zip) file and use `lib/content-change.min.js`.\n\n## Setup\nThe script must be located before web components are imported. If you are using the `webcomponents.js` polyfill, it doesn't matter if is located before or after the inclusion of `webcomponent.js`.\n```html\n\u003cscript src=\"yourpath/content-change.min.js\"\u003e\u003c/script\u003e\n\u003c!-- now import your web components --\u003e\n```\n\nThis library is UMD wrapped and if you are using a module bundler, you have a couple of different options. You can just import the package to the appropriate location, or you can import the only functions available, `watch`, and `unwatch`.\n```javascript\nimport ContentChange from 'content-change';\n\n// or just import the watch function\nimport {watch} from 'content-change';\n``` \n\nIf the library is directly sourced to the window, it operates on the `ContentChange` global namespace (`window.ContentChange`).\n\n#Usage\nOnly 1 mutation observer is created per host. No additional MutationObservers are spawned on distributed nodes. Because this library provides functionality that is non-spec, it does not modify the api's and objects of any Shadow DOM behavior.\n\n### Specifying which components to watch\nA simple call inside of either the `createdCallback`, or the `attachedCallback` is made:\n\n```javascript\nContentChange.watch(hostElement);\n```\n\n`watch` accepts the host element as an argument.\n\nThere is a benefit to this approach as you can specify which components are watched, rather than this library attempting to watch all created components.\n\n### Reacting to changes in distributed nodes\nThe final piece is to specify which `content` elements should have an event handler. The event is called `contentchange` and is triggered on `content` elements. This is done exactly like the `slotchange` event on the `slot` elements in Shadow DOM v1.\n\n```javascript\n// get the content element you want to watch for distributed changes in from the shadow dom\nconst content = shadow.querySelector('selectorForYourContentElement');\n\ncontent.addEventListener('contentchange', e =\u003e {\n\tconsole.log(e.detail);\n});\n```\n\n### Completely stop watching a component\nYou can stop watching a particular web component completely, but this is different from removing the listeners on the content elements. No longer \"watching\" the component removes the MutationObserver, giving you back some performance.\n\n```javascript\nContentChange.unwatch(hostElement);\n```\n\n`unwatch` accepts the host element as an argument.\n\n## Reacting to the different event details\nBecause the event is created with CustomEvent, the information you will look for in the event will be under the detail key:\n`e.detail`. There are three different event `type`'s, you can receive:\n\n* nodesAdded\n* nodesRemoved\n* mutation\n\n### Reacting to newly distributed nodes\nThe event detail will contain an object that looks like the following:\n```javascript\n{\n    \"type\": \"nodesAdded\",\n    \"nodesAdded\": [Nodes] // An array of added nodes\n}\n```\n\n### Reacting to nodes that are no longer distributed\nThe event detail will contain an object that looks like the following:\n```javascript\n{\n    \"type\": \"nodesRemoved\",\n    \"nodesRemoved\": [Nodes] // An array of removed nodes\n}\n```\n\n### Reacting to mutations in distributed nodes\nWhen a currently distributed node has a mutation occur on it, you will receive the following:\n```javascript\n{\n    \"type\": \"mutation\",\n    \"mutation\": MutationRecord\n}\n```\nThis can be attribute changes, added or removed nodes on the distributed node, characterData changes, etc.\n\n**Note:** If the mutation causes the node to no longer be distributed, you will receive a `nodesRemoved` event instead.\n\n# Browser support\n| Chrome | Firefox | Safari | IE | Edge | Chrome Android | Mobile Safari |\n|:------:|:-------:|:------:|:--:|:----:|:--------------:|:-------------:|\n|    ✓   |    ✓    |   7+   | 11 |   ✓  |        ✓       |       ✓       |\n\n# A full example\nI will soon make a small github page so that a live example may be viewed. For now, the contents of the files will be placed here:\n\n#### example-component.html\nA simple web component\n```html\n\u003ctemplate id=\"example-component\"\u003e\n\t\u003cdiv\u003eI am an example component\u003c/div\u003e\n\t\u003c!-- Distribute paragraphs --\u003e\n\t\u003ccontent select=\"p\"\u003e\u003c/content\u003e\n\u003c/template\u003e\n\n\u003cscript\u003e\n\t(function () {\n\n\t\tvar doc = (document._currentScript || document.currentScript).ownerDocument;\n\t\tvar objectPrototype = Object.create(HTMLElement.prototype);\n\n\t\tobjectPrototype.createdCallback = function () {\n\t\t\tvar shadow = this.createShadowRoot();\n\t\t\tvar template = doc.querySelector('#example-component');\n\t\t\tshadow.appendChild(template.content.cloneNode(true));\n\n\t\t\t// Watch this components distributed nodes (\"this\" is currently the host element)\n\t\t\twindow.ContentChange.watch(this);\n\n\t\t\tvar pContent = shadow.querySelector('content[select=\"p\"]');\n\t\t\tpContent.addEventListener('contentchange', function (e) {\n\t\t\t\t// \"this\" is the content element\n\t\t\t\tconsole.log(this, e.detail);\n\t\t\t});\n\t\t};\n\n\t\tdocument.registerElement('example-component', {\n\t\t\tprototype: objectPrototype\n\t\t});\n\n\t})();\n\u003c/script\u003e\n\n```\n#### index.html\nThe web page that the web component will be rendered on\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml lang=\"en\"\u003e\n\u003chead\u003e\n\t\u003cmeta charset=\"UTF-8\"\u003e\n\t\u003ctitle\u003eWatch Changes in distributed nodes in Shadow DOM v0\u003c/title\u003e\n\n\t\u003cscript src=\"https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/0.7.23/webcomponents.min.js\"\u003e\u003c/script\u003e\n\t\u003cscript src=\"lib/content-change.js\"\u003e\u003c/script\u003e\n\t\u003clink rel=\"import\" href=\"example-component.html\"\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n\n\u003cexample-component\u003e\n\t\u003cp\u003eThis will be a distributed node\u003c/p\u003e\n\u003c/example-component\u003e\n\n\u003cscript\u003e\n\t// Manipulate the example-component so we see changes\n\tvar example = document.querySelector('example-component');\n\tvar p = document.createElement('p');\n\tp.textContent = 'Dynamically created';\n\n\t// Add to the component at some point\n\twindow.setTimeout(function () {\n\t\texample.appendChild(p);\n\t}, 500);\n\n\t// Modify a distributed node some point later\n\twindow.setTimeout(function () {\n\t\tp.classList.add('foo');\n\t}, 750);\n\n\t// Remove a distributed node\n\twindow.setTimeout(function () {\n\t\texample.removeChild(p);\n\t}, 1000);\n\u003c/script\u003e\n\n\u003c/body\u003e\n\u003c/html\u003e\n\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkevinokerlund%2Fcontent-change","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkevinokerlund%2Fcontent-change","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkevinokerlund%2Fcontent-change/lists"}