{"id":13827422,"url":"https://github.com/lamplightdev/compost","last_synced_at":"2025-06-27T11:35:38.977Z","repository":{"id":57126216,"uuid":"120027012","full_name":"lamplightdev/compost","owner":"lamplightdev","description":"A collection of web component mixins","archived":false,"fork":false,"pushed_at":"2018-07-02T13:38:56.000Z","size":101,"stargazers_count":19,"open_issues_count":0,"forks_count":2,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-05-13T01:39:30.646Z","etag":null,"topics":[],"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/lamplightdev.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":"2018-02-02T20:18:18.000Z","updated_at":"2022-01-12T21:12:46.000Z","dependencies_parsed_at":"2022-08-31T08:20:17.536Z","dependency_job_id":null,"html_url":"https://github.com/lamplightdev/compost","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/lamplightdev/compost","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lamplightdev%2Fcompost","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lamplightdev%2Fcompost/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lamplightdev%2Fcompost/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lamplightdev%2Fcompost/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lamplightdev","download_url":"https://codeload.github.com/lamplightdev/compost/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lamplightdev%2Fcompost/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260884159,"owners_count":23076875,"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","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":[],"created_at":"2024-08-04T09:01:56.491Z","updated_at":"2025-06-27T11:35:38.934Z","avatar_url":"https://github.com/lamplightdev.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://rawgit.com/lamplightdev/compost/master/images/compost.svg\" alt=\"Logo\" width=\"100\"\u003e\n  \u003cbr\u003e\n  \u003ca href=\"https://www.npmjs.org/package/@lamplightdev/compost\"\u003e\n     \u003cimg src=\"https://img.shields.io/npm/v/@lamplightdev/compost.svg?style=flat\" alt=\"npm\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n# compost\n\n\u003e A collection of small Web Component mixins to add commonly required functionality without the bloat\n\n- **No dependencies** - just mixin and go\n- **Plain JavaScript** - no build tools required, compatible by default, debug in the browser with standard dev tools\n- **No magic** - straight forward to understand\n- **Small \u0026 efficient** - low overhead to keep your components lean\n- **Include only what you need** - take it or leave it\n- **Cross browser** - works on all modern browsers and IE11\n\nExample Hacker News progressive web app - [https://github.com/lamplightdev/compost-hn](https://github.com/lamplightdev/compost-hn)\n\n## Table of Contents\n\n- [Install](#install)\n- [Mixins](#mixins)\n- [Usage](#usage)\n- [Browser support](#browser-support)\n- [Examples](#examples)\n- [License](#license)\n\n## Install\n\nInstall using [npm](https://npmjs.com):\n\n```sh\nnpm install --save @lamplightdev/compost\n```\n\n## Mixins\n\n### CompostShadowMixin\n\nAdds a shadow DOM to your component. Takes a string returned from a `render()` method to set up the shadow DOM, and adds [ShadyCSS](https://github.com/webcomponents/shadycss) support if the polyfill is included for browsers that don't natively support shadow DOM. Also adds some convenience accessors for querying shadow DOM elements.\n\n### CompostPropertiesMixin\n\nLets you specify your component's properties upfront, their types, whether their value should be reflected in an attribute, and an observer function to run when the property changes.\n\n### CompostEventsMixin\n\nLet's you add declarative event binding in your templates. Also adds some convenience methods for manual event binding and firing.\n\n### CompostRepeatMixin\n\nExperimental - Stamps out a template for each object in the `items` array property\n\n### CompostMixin\n\nA convenience mixin that includes `CompostShadowMixin`, `CompostPropertiesMixin`, and `CompostEventsMixin`:\n\n```js\nconst CompostMixin = (parent) =\u003e {\n  return class extends\n    CompostEventsMixin(\n      CompostPropertiesMixin(\n        CompostShadowMixin(parent))) {}\n};\n```\n\n### Where's the data binding?\n\nThere is none. For simple, well structured components you may not need data binding - turns out you can get a lot done with the standard DOM APIs (and it's the most efficient way to update the DOM too). But if data binding is your thing you can simply extend `CompostShadowBaseMixin` to include your data binding library of choice (I like [lit-html](https://github.com/Polymer/lit-html).)\n\n## Usage\n\n### CompostShadowMixin\n\n```html\n\u003cx-app\u003e\u003c/x-app\u003e\n\n\u003cscript\u003e\n  class App extends CompostShadowMixin(HTMLElement) {\n    render() {\n      return `\n        \u003cstyle\u003e\n          :host {\n            display: flex;\n            flex-direction: column;\n            max-width: 1280px;\n            margin: 0 auto;\n          }\n        \u003c/style\u003e\n\n        \u003cbutton class=\"button\" id=\"buttonEat\"\u003eEat me\u003c/button\u003e\n        \u003cbutton class=\"button\" id=\"buttonDrink\"\u003eDrink me\u003c/button\u003e\n        \u003cbutton class=\"button\" id=\"buttonClick\"\u003eClick me\u003c/button\u003e\n      `;\n    }\n  }\n\n  customElements.define('x-app', App);\n\u003c/script\u003e\n```\nImplement a `render` method that returns a string - your x-app component now has a shadow DOM with encapsulated CSS.\n\nIn addition the following convenience properties are added:\n\n`this.$s` is a reference to the shadow DOM\n\n`this.$` is equivalent to `app.shadowRoot.querySelector`\n\n`this.$$` is equivalent to `app.shadowRoot.querySelectorAll`\n\n`this.$id` is an object containing a mapping of all elements with their `id`\n\n### CompostPropertiesMixin\n\n```html\n\u003cx-app\u003e\u003c/x-app\u003e\n\n\u003cscript\u003e\n  class App extends CompostPropertiesMixin(HTMLElement) {\n    static get properties() {\n      return {\n        siteUsername: {\n          type: String,\n          value: 'lovelace',\n          reflectToAttribute: true,\n          observer: 'observeSiteUsername',\n        },\n      };\n    }\n\n    observeUser(oldValue, newValue) {\n      console.log(oldValue, newValue);\n    }\n  }\n\u003c/script\u003e\n\n```\n\nImplement a static getter for `properties`, returning an object with the property name as the key, and the value an object containing:\n\n|Key|Type|Default|Required|\n|:--:|:--:|:--:|:--:|\n|**`type`**|`{String\\|Number\\|Boolean\\|Array\\|Object}`|`String`|No|\n|**`value`**|`{String\\|Number\\|Boolean\\|Array\\|Object\\|null}`|`undefined`|No|\n|**`reflectToAttribute`**|`{true\\|false}`|`false`|No|\n|**`observer`**|`{String}`|`undefined`|No|\n\n`type`\n\nThe type of the property. Used when converting a property to an attribute, and vice-versa.\n\nFor boolean properties, `true` is converted to an empty attribute, while `false` removes the attribute. The presence of an attribute sets the property to `true`.\n\nFor array and object properties, the property is JSON stringified (`JSON.stringify`) when converting to an attribute, and JSON parsed (`JSON.parse`) when converting to a property.\n\n`value`\n\nThe default value of the property if the property has not been initialised from an attribute, or the property was not set before the element was added to the DOM. If an attribute with a name matching the property name (converted to kebab-case) is present on the element this will always override the default.\n\n`reflectToAttribute`\n\nWhether the property should be reflected to an attribute with the same name. Camel cased property names (e.g. `siteUsername`) are converted to kebab cased attribute names (e.g. `site-username`) automatically.\n\n`observer`\n\nIf defined, this is the name of a method in your class that will be called when the property changes with arguments containing the previous and new values. Strict equality is used when comparing old and new values, so changes in object sub properties or array elements won't trigger an observer - use of immutable data is advised.\n\nThe observer will also be called on initialisation - either from a matching attribute or a default `value`.\n\nObserver calls are batched and dispatched asynchronously. This means that when the observers are called all properties will have their most recent values - so the order in which the observers are called should not be important.\n\n### CompostEventsMixin\n\n```html\n\u003cx-app\u003e\u003c/x-app\u003e\n\n\u003cscript\u003e\n  class App extends CompostEventsMixin(CompostShadowMixin(HTMLElement)) {\n    render() {\n      return `\n        \u003cbutton on-click=\"test\"\u003eClick me\u003c/button\u003e\n      `;\n    }\n\n    test(event) {\n      console.log(this, event);\n    }\n  }\n\n  customElements.define('x-app', App);\n\u003c/script\u003e\n```\n\nAny attribute beginning `on-{eventName}` (where eventName is one of [these standard DOM events](https://github.com/lamplightdev/compost/blob/86a15dd693112e6a6433a6262270f29a0869b5a3/src/events-mixin.js#L4)) will bind that event to the method specified in the attribute value (`test` in the example above.) The method is automatically bound to the current class, so `this` in the method will always refer to the class itself.\n\nEvent listeners are added in `connectedCallback` and removed in `disconnectedCallback`.\n\nIn addition the following convenience methods are added:\n\n`this.on(el, type, func)` is equivalent to `el.addEventListener(type, func)`\n\n`this.off(el, type, func)` is equivalent to `el.removeEventListener(type, func)`\n\n`this.fire(type, detail, bubbles = true, composed = true)` is equivalent to\n```js\nthis.dispatchEvent(new CustomEvent(type, {\n  bubbles,\n  composed,\n  detail,\n}));\n```\n\n### CompostRepeatMixin\n\n```html\n\u003cx-app\u003e\u003c/x-app\u003e\n\n\u003cscript\u003e\n  class App extends CompostRepeatMixin(CompostShadowMixin(HTMLElement)) {\n    render() {\n      return super.render(`\n        \u003ch1\u003eRepeater\u003ch1\u003e\n      `);\n    }\n\n    getTemplateString(value, index) {\n      return `\n        \u003cbutton\u003e\u003c/button\u003e\n      `;\n    }\n\n    getKey(value, index) {\n      return value;\n    }\n\n    updateItem(el, value, index) {\n      el.textContent = value;\n    }\n  }\n\n  customElements.define('x-app', App);\n\n  const app = document.querySelector('x-app');\n  app.items = ['one', 'two', 'three'];\n\u003c/script\u003e\n```\n\nThe call to `render` returns a template string for anything that needs to appear above the repeated template and will be added to the element's shadow DOM. The call to `getTemplateString` returns a template string that contains the DOM  that needs to be repeated and is combined with the `items` array property to stamp out new elements in the light DOM. So the above will output a custom element with `\u003ch1\u003eRepeater\u003c/h1\u003e` in the shadow DOM, and the following in a `slot`:\n\n```html\n\u003cbutton\u003eone\u003c/button\u003e\n\u003cbutton\u003etwo\u003c/button\u003e\n\u003cbutton\u003ethree\u003c/button\u003e\n```\n\n`this.getKey(value, index)` must return a unique key for each `value` in `items` so that items can be efficiently added/removed/reordered when the items array changes.\n\n`updateItem(el, value, index)` is used to update the repeated element when the `value` in `items[index]` changes.\n\n## Browser support\n\nWorks with no polyfills or build step for browsers that support ES2015, Shadow DOM v1, Custom Elements v1 and HTML Templates. This is currently the latest versions of Chrome and Safari.\n\nWorks with other browsers down to IE11 when using the [web components polyfills](https://github.com/WebComponents/webcomponentsjs) and a transpilation step (e.g. Babel) as needed.\n\n## Examples\n\n[HackerNew Progressive Web App](https://github.com/lamplightdev/compost-hn) -  built using compost\n\nComing soon - an example using lit-html.\n\nComing soon - an example that works on all browsers down to IE11.\n\n## License\n\n[MIT License](https://oss.ninja/mit/lamplightdev) © [Chris Haynes](https://lamplightdev.com)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flamplightdev%2Fcompost","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flamplightdev%2Fcompost","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flamplightdev%2Fcompost/lists"}