{"id":15389697,"url":"https://github.com/joshgillies/hypercomponent","last_synced_at":"2025-04-15T21:16:36.386Z","repository":{"id":140003907,"uuid":"86532152","full_name":"joshgillies/hypercomponent","owner":"joshgillies","description":":zap: Fast and light component system, backed by hyperHTML","archived":false,"fork":false,"pushed_at":"2017-08-16T10:28:01.000Z","size":145,"stargazers_count":44,"open_issues_count":1,"forks_count":3,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-15T21:16:30.066Z","etag":null,"topics":["component","dom","html","hyperhtml"],"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/joshgillies.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-03-29T03:00:10.000Z","updated_at":"2023-09-08T17:22:55.000Z","dependencies_parsed_at":"2023-05-13T07:15:45.106Z","dependency_job_id":null,"html_url":"https://github.com/joshgillies/hypercomponent","commit_stats":{"total_commits":18,"total_committers":1,"mean_commits":18.0,"dds":0.0,"last_synced_commit":"7a6840e4240236843cf2bc7f3fbb7a074efd2265"},"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joshgillies%2Fhypercomponent","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joshgillies%2Fhypercomponent/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joshgillies%2Fhypercomponent/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joshgillies%2Fhypercomponent/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/joshgillies","download_url":"https://codeload.github.com/joshgillies/hypercomponent/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249153951,"owners_count":21221330,"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":["component","dom","html","hyperhtml"],"created_at":"2024-10-01T15:03:02.737Z","updated_at":"2025-04-15T21:16:36.368Z","avatar_url":"https://github.com/joshgillies.png","language":"JavaScript","readme":"# hypercomponent\n\n[![Build Status][0]][1]\n[![Standard - JavaScript Style Guide][2]][3]\n\n\u003e :zap: Fast and light component system, backed by [hyperHTML][hyper]\n\n`HyperComponent` is an abstract component system designed to be fast, and light - weighing in at ~4kb.\n\n```js\nconst HyperComponent = require('hypercomponent')\n\nclass HelloMessage extends HyperComponent {\n  renderCallback (wire) {\n    return wire`\n      \u003cdiv\u003eHello ${this.props.name}\u003c/div\u003e\n    `\n  }\n}\n\nconst greeting = new HelloMessage({ name: 'Jane'})\n\ngreeting.render(document.body)\n```\n\n## Install\n\n### npm\n\n`npm install hypercomponent --save`\n\n### cdn\n\n`\u003cscript src=\"https://unpkg.com/hypercomponent@latest/dist/hypercomponent.min.js\"\u003e\u003c/scrpt\u003e`\n\n## API\n\n### `HyperComponent`\n\n`HyperComponent` is a base class for creating generic front-end components.\n\n```js\nclass Component extends HyperComponent {\n  constructor (props) {\n    super(props)\n  }\n}\n```\n\nInstances of `HyperComponent` have the following internal properties:\n\n- `this.el`: The DOM node a component has rendered to. Defaults to `null`.\n- `this.props`: The initial properties passed to the `HyperComponent` constructor. Defaults to `Component.defaultProps` or `{}`\n- `this.state`: The initial state of the `HyperComponent` constructor. Defaults to `Component.defaultState` or `{}`\n\n### `HyperComponent.prototype.renderCallback(wire, component)`\n\nYou'll always want to implement a render function. This forms the public interface for your component. Your `renderCallback` method should always return DOM nodes, and the output of your `renderCallback` is automatically assigned to `this.el`.\n\nThe following arguments are available:\n\n#### `wire`\n\nThe `wire` argument is a tagged template literal for turning your template into DOM.\n\nInternally your template is cached against the instance of a component, and as such additional calls to `wire` with different templates will result in errors.\n\nFor cases where you want sub templates, you can simply pass a optional `type` argument, eg.\n\n```js\nclass Component extends HyperComponent {\n  constructor (props) {\n    super(props)\n    this.winning = true\n  }\n  renderCallback (wire) {\n    return wire`\n      \u003cdiv\u003e${this.winning\n        ? wire(':winning')`\u003cspan\u003eWinning!\u003c/span\u003e`\n        : wire(':not-winning')`\u003cspan\u003eNot winning!\u003c/span\u003e`\n      }\u003c/div\u003e\n    `\n  }\n```\n\nFor those familiar with `hyperHTML` a `wire` in this case is literally a facade around [`hyperHTML.wire([obj[, type]])`][wire], and can be used in the same way, eg.\n\n```js\nclass Component extends HyperComponent {\n  constructor (props) {\n    super(props)\n    this.items = [\n      { text: 'Foo' },\n      { text: 'Bar' }\n    ]\n  }\n  renderCallback (wire) {\n    return wire`\n      \u003cdiv\u003e\n        \u003cul\u003e${this.items.map((item) =\u003e wire(item, ':unordered')`\n          \u003cli\u003e ${item.text} \u003c/li\u003e`\n        )}\u003c/ul\u003e\n        \u003col\u003e${this.items.map((item) =\u003e wire(item, ':ordered')`\n          \u003cli\u003e ${item.text} \u003c/li\u003e`\n        )}\u003c/ol\u003e\n      \u003c/div\u003e`\n  }\n}\n```\n\n#### `component(Component, props, children)`\n\nThe `component` argument is useful for managing component composition, and the returned value of `component` is the result of calling `Component.prototype.renderCallback()`. The following arguments are available.\n\n##### `Component`\n\nThe `Component` argument is a component class you wish to compose within your parent component. It's expected that `Component` is an instance of `HyperComponent`.\n\n##### `props`\n\nInternally this will effectively create a new instance of your child component by passing props to the constructor eg. `new Component(props)`\n\nFor managing multiple instances of the same component class, you can additionally assign a `key` property via `props.key`, which is used to ensure component instances are reused on subsequent calls to `renderCallback()`.\n\n##### `children`\n\nIt's expected that the `children` argument is a [valid type for children][types] within hyperHTML eg. a String of text or markup, DOM nodes, a Promise, or an Array of the previous types. Internally the `children` argument is assigned to `this.props.children`.\n\nAs an example, the following demonstrates all of the above.\n\n```js\nclass Parent extends HyperComponent {\n  renderCallback (wire, component) {\n    return wire`\u003cdiv id=\"parent\"\u003e${\n      component(Child, { key: 'child1' },\n      wire(':child')`\u003cdiv\u003e${[\n        component(Child, { key: 'subchild1' }, `\u003cdiv\u003ewoah!\u003c/div\u003e`),\n        component(Child, { key: 'subchild2' }, `\u003cdiv\u003edude!\u003c/div\u003e`)\n      ]}\u003c/div\u003e`)\n    }\u003c/div\u003e`\n  }\n}\n\nclass Child extends HyperComponent {\n  renderCallback (wire) {\n    return wire`\u003cdiv id=\"${ this.props.key }\"\u003e${\n      this.props.children\n    }\u003c/div\u003e`\n  }\n}\n```\n\n### `HyperComponent.prototype.render(node)`\n\nRenders a component returning it's rendered DOM tree. When the optional argument `node` is provided the contents of the target DOM node will be replaced by your rendered component.\n\n### `HyperComponent.prototype.handleEvent(event)`\n\nBy default `handleEvent` is preconfigured to delegate any component method whose name matches a valid DOM event. eg. `onclick`.\n\nThe benefit being that instead of binding event handlers individually to your components, you can simply pass your `HyperComponent` instance to your event handler, and delegate all event handling logic through the `handleEvent` API.\n\nAs an example:\n\n```js\n// instead of this... 👎\nclass BoundButton extends HyperComponent {\n  constructor (props) {\n    super(props)\n    this.onclick = this.onclick.bind(this)\n  }\n  onclick (event) {\n    console.log(event.target, \" has been clicked!\")\n  }\n  renderCallback (wire) {\n    return wire`\n      \u003cbutton onclick=\"${this.onclick}\"\u003eClick me\u003c/button\u003e\n    `\n  }\n}\n\n// you can simply do this! 🎉\nclass DelegatedButton extends HyperComponent {\n  onclick (event) {\n    console.log(event.target, \" has been clicked!\")\n  }\n  renderCallback (wire) {\n    return wire`\n      \u003cbutton onclick=\"${this}\"\u003eClick me\u003c/button\u003e\n    `\n  }\n}\n```\n\nOf course the `HyperComponent.prototype.handleEvent` method is simply a helper. You can always override it to create your own event delegation logic.\n\nAs an example, if `camelCase` handlers are more your style:\n\n```js\nclass Button extends HyperComponent {\n  handleEvent (event) {\n    const type = event.type\n    this[`on${type.substr(0, 1).toUpperCase() + type.substr(1)}`](event)\n  }\n  onClick (event) {\n    console.log(event.target, \" has been clicked!\")\n  }\n  renderCallback (wire) {\n    return wire`\n      \u003cbutton onclick=\"${this}\"\u003eClick me\u003c/button\u003e\n    `\n  }\n}\n```\n\nFor more information on the benefits of `handleEvent` checkout this post by [@WebReflection][WebReflection]: [DOM handleEvent: a cross-platform standard since year 2000][handleEvent].\n\n### `HyperComponent.prototype.connectedCallback()`\n\nWhen assigned, the `connectedCallback` handler will be called once your component has been inserted into the DOM.\n\n### `HyperComponent.prototype.disconnectedCallback()`\n\nWhen assigned, the `disconnectedCallback` handler will be called once your component has been removed from the DOM.\n\n### `HyperComponent.prototype.setState(obj)`\n\nSets the internal state of a component eg. `this.state` by extending previous state with next state. After the state is updated your components `renderCallback()` method will be called.\n\n## Examples\n\n### A Basic Component\n\n```js\nconst HyperComponent = require('hypercomponent')\n\nclass HelloMessage extends HyperComponent {\n  renderCallback (wire) {\n    return wire`\n      \u003cdiv\u003eHello ${this.props.name}\u003c/div\u003e\n    `\n  }\n}\n\nconst greeting = new HelloMessage({ name: 'Jane'})\n\ngreeting.render(document.body)\n```\n\n### A Stateful Component\n\n```js\nconst HyperComponent = require('hypercomponent')\n\nclass Timer extends HyperComponent {\n  constructor (props) {\n    super(props)\n    this.state = {\n      secondsElapsed: 0\n    }\n  }\n  tick () {\n    this.setState({\n      secondsElapsed: this.state.secondsElapsed + 1\n    })\n  }\n  connectedCallback () {\n    this.interval = setInterval(() =\u003e this.tick(), 1000)\n  }\n  disconnectedCallback () {\n    clearInterval(this.interval)\n  }\n  renderCallback (wire) {\n    return wire`\n      \u003cdiv\u003eSeconds Elapsed: ${this.state.secondsElapsed}\u003c/div\u003e\n    `\n  }\n}\n\nconst myTimer = new Timer()\n\nmyTimer.render(document.body)\n```\n\n### An Application\n\n```js\nclass TodoApp extends HyperComponent {\n  constructor (props) {\n    super(props)\n    this.state = {items: [], text: ''}\n  }\n  renderCallback (wire, component) {\n    return wire`\n      \u003cdiv\u003e\n        \u003ch3\u003eTODO\u003c/h3\u003e${\n        component(TodoList, {items: this.state.items})\n        }\u003cform onsubmit=\"${this}\"\u003e\n          \u003cinput onchange=\"${this}\" value=\"${this.state.text}\" /\u003e\n          \u003cbutton\u003eAdd #${this.state.items.length + 1}\u003c/button\u003e\n        \u003c/form\u003e\n      \u003c/div\u003e\n    `\n  }\n  onchange (event) {\n    this.setState({text: event.target.value})\n  }\n  onsubmit (event) {\n    event.preventDefault()\n    var newItem = {\n      text: this.state.text,\n      id: Date.now()\n    }\n    this.setState({\n      items: this.state.items.concat(newItem),\n      text: ''\n    })\n  }\n}\n\nclass TodoList extends HyperComponent {\n  renderCallback (wire) {\n    return wire`\n      \u003cul\u003e${this.props.items.map(item =\u003e wire(item)`\n        \u003cli\u003e\n          ${item.text}\n        \u003c/li\u003e`)\n      }\u003c/ul\u003e`\n  }\n}\n\nconst app = new TodoApp()\n\napp.render(document.body)\n```\n\n## License\n\nMIT\n\n[0]: https://travis-ci.org/joshgillies/hypercomponent.svg?branch=master\n[1]: https://travis-ci.org/joshgillies/hypercomponent\n[2]: https://img.shields.io/badge/code_style-standard-brightgreen.svg\n[3]: http://standardjs.com/\n[WebReflection]: https://twitter.com/WebReflection\n[handleEvent]: https://medium.com/@WebReflection/dom-handleevent-a-cross-platform-standard-since-year-2000-5bf17287fd38\n[hyper]: https://github.com/WebReflection/hyperHTML\n[types]: https://github.com/WebReflection/hyperHTML/blob/master/DEEPDIVE.md#good\n[wire]: https://github.com/WebReflection/hyperHTML#wait--there-is-a-wire--in-the-code\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoshgillies%2Fhypercomponent","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjoshgillies%2Fhypercomponent","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoshgillies%2Fhypercomponent/lists"}