{"id":15603401,"url":"https://github.com/codemeasandwich/hyper-element","last_synced_at":"2026-01-24T12:19:56.275Z","repository":{"id":71445831,"uuid":"114667294","full_name":"codemeasandwich/hyper-element","owner":"codemeasandwich","description":"Combining the best of hyperHTML and Custom Elements!","archived":false,"fork":false,"pushed_at":"2026-01-21T11:45:50.000Z","size":1993,"stargazers_count":5,"open_issues_count":0,"forks_count":4,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-01-21T23:14:09.304Z","etag":null,"topics":["hyperhtml","react","web-components"],"latest_commit_sha":null,"homepage":null,"language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/codemeasandwich.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":null,"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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2017-12-18T17:09:52.000Z","updated_at":"2026-01-21T11:45:40.000Z","dependencies_parsed_at":"2024-09-18T20:44:57.661Z","dependency_job_id":null,"html_url":"https://github.com/codemeasandwich/hyper-element","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/codemeasandwich/hyper-element","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codemeasandwich%2Fhyper-element","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codemeasandwich%2Fhyper-element/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codemeasandwich%2Fhyper-element/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codemeasandwich%2Fhyper-element/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/codemeasandwich","download_url":"https://codeload.github.com/codemeasandwich/hyper-element/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codemeasandwich%2Fhyper-element/sbom","scorecard":{"id":296755,"data":{"date":"2025-08-11","repo":{"name":"github.com/codemeasandwich/hyper-element","commit":"3dc0f9b90f9ed018328c6e91a46596a4d34f8e2b"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":1.3,"checks":[{"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":"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":"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 1/29 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":"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":"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":"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":"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":"License","score":0,"reason":"license file not detected","details":["Warn: project does not have a license file"],"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"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 2 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"30 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-67hx-6x53-jw92","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-cwfw-4gq5-mrqx","Warn: Project is vulnerable to: GHSA-g95f-p29q-9xw4","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-gxpj-cx7g-858c","Warn: Project is vulnerable to: GHSA-w573-4hg7-7wgq","Warn: Project is vulnerable to: GHSA-8r6j-v8pm-fqw3","Warn: Project is vulnerable to: MAL-2023-462","Warn: Project is vulnerable to: GHSA-qqgx-2p2h-9c37","Warn: Project is vulnerable to: GHSA-9c47-m6qq-7p4h","Warn: Project is vulnerable to: GHSA-6c8f-qphg-qjgp","Warn: Project is vulnerable to: GHSA-jf85-cpcp-j695","Warn: Project is vulnerable to: GHSA-p6mc-m468-83gw","Warn: Project is vulnerable to: GHSA-29mw-wpgm-hmr9","Warn: Project is vulnerable to: GHSA-35jh-r3h4-6jhm","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3","Warn: Project is vulnerable to: GHSA-vh95-rmgr-6w4m","Warn: Project is vulnerable to: GHSA-xvch-5gv4-984h","Warn: Project is vulnerable to: GHSA-fhjf-83wg-r2j9","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-4g88-fppr-53pp","Warn: Project is vulnerable to: GHSA-4jqc-8m5r-9rpr","Warn: Project is vulnerable to: GHSA-3jfq-g458-7qm9","Warn: Project is vulnerable to: GHSA-r628-mhmh-qjhw","Warn: Project is vulnerable to: GHSA-9r2w-394v-53qc","Warn: Project is vulnerable to: GHSA-5955-9wpr-37jh","Warn: Project is vulnerable to: GHSA-qq89-hq3f-393p","Warn: Project is vulnerable to: GHSA-f5x3-32g6-xq36"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-17T19:44:24.800Z","repository_id":71445831,"created_at":"2025-08-17T19:44:24.801Z","updated_at":"2025-08-17T19:44:24.801Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28727523,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-24T10:24:43.181Z","status":"ssl_error","status_checked_at":"2026-01-24T10:24:36.112Z","response_time":89,"last_error":"SSL_read: 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":["hyperhtml","react","web-components"],"created_at":"2024-10-03T03:03:07.262Z","updated_at":"2026-01-24T12:19:56.251Z","avatar_url":"https://github.com/codemeasandwich.png","language":"HTML","readme":"# hyper-element\n\n[![npm version](https://img.shields.io/npm/v/hyper-element.svg)](https://www.npmjs.com/package/hyper-element)\n[![npm package size](https://img.shields.io/bundlephobia/minzip/hyper-element)](https://bundlephobia.com/package/hyper-element)\n[![CI](https://github.com/codemeasandwich/hyper-element/actions/workflows/publish.yml/badge.svg)](https://github.com/codemeasandwich/hyper-element/actions/workflows/publish.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen.svg)](https://github.com/codemeasandwich/hyper-element)\n[![ES6+](https://img.shields.io/badge/ES6+-supported-blue.svg)](https://caniuse.com/es6)\n\nCombining the best of [hyperHTML] and [Custom Elements]! Your new custom-element will be rendered with the super fast **hyperHTML** and will react to tag attribute and store changes.\n\n### If you like it, please [★ it on github](https://github.com/codemeasandwich/hyper-element)\n\n# Installation\n\n## npm\n\n```bash\nnpm install hyper-element\n```\n\n### ES6 Modules\n\n```js\nimport hyperElement from 'hyper-element';\n\ncustomElements.define(\n  'my-elem',\n  class extends hyperElement {\n    render(Html) {\n      Html`Hello ${this.attrs.who}!`;\n    }\n  }\n);\n```\n\n### CommonJS\n\n```js\nconst hyperElement = require('hyper-element');\n\ncustomElements.define(\n  'my-elem',\n  class extends hyperElement {\n    render(Html) {\n      Html`Hello ${this.attrs.who}!`;\n    }\n  }\n);\n```\n\n## CDN (Browser)\n\nFor browser environments without a bundler, include both hyperHTML and hyper-element via CDN:\n\n```html\n\u003cscript src=\"https://cdn.jsdelivr.net/npm/hyperhtml@latest/index.js\"\u003e\u003c/script\u003e\n\u003cscript src=\"https://cdn.jsdelivr.net/npm/hyper-element@latest/build/hyperElement.min.js\"\u003e\u003c/script\u003e\n```\n\nThe `hyperElement` class will be available globally on `window.hyperElement`.\n\n## Browser Support\n\nhyper-element requires native ES6 class support and the Custom Elements v1 API:\n\n| Browser | Version |\n| ------- | ------- |\n| Chrome  | 67+     |\n| Firefox | 63+     |\n| Safari  | 10.1+   |\n| Edge    | 79+     |\n\nFor older browsers, a [Custom Elements polyfill](https://github.com/webcomponents/polyfills/tree/master/packages/custom-elements) may be required.\n\n## Why hyper-element\n\n- hyper-element is fast \u0026 small\n  - With only 1 dependency: [hyperHTML]\n- With a completely stateless approach, setting and reseting the view is trivial\n- Simple yet powerful [Interface](#interface)\n- Built in [template](#templates) system to customise the rendered output\n- Inline style objects supported (similar to React)\n- First class support for [data stores](#connecting-to-a-data-store)\n- Pass `function` to other custom hyper-elements via there tag attribute\n\n# [Live Demo](https://jsfiddle.net/codemeasandwich/k25e6ufv/)\n\n## Live Examples\n\n| Example              | Description                         | Link                                                       |\n| -------------------- | ----------------------------------- | ---------------------------------------------------------- |\n| Hello World          | Basic element creation              | [CodePen](https://codepen.io/codemeasandwich/pen/VOQpqz)   |\n| Attach a Store       | Store integration with setup()      | [CodePen](https://codepen.io/codemeasandwich/pen/VOQWeN)   |\n| Templates            | Using the template system           | [CodePen](https://codepen.io/codemeasandwich/pen/LoQLrK)   |\n| Child Element Events | Passing functions to child elements | [CodePen](https://codepen.io/codemeasandwich/pen/rgdvPX)   |\n| Async Fragments      | Loading content asynchronously      | [CodePen](https://codepen.io/codemeasandwich/pen/MdQrVd)   |\n| Styling              | React-style inline styles           | [CodePen](https://codepen.io/codemeasandwich/pen/RmQVKY)   |\n| Full Demo            | Complete feature demonstration      | [JSFiddle](https://jsfiddle.net/codemeasandwich/k25e6ufv/) |\n\n---\n\n- [Browser Support](#browser-support)\n- [Define a Custom Element](#define-a-custom-element)\n- [Lifecycle](#lifecycle)\n- [Interface](#interface)\n  - [render](#render)\n  - [Html](#html)\n  - [Html.wire](#htmlwire)\n  - [Html.lite](#htmllite)\n  - [setup](#setup)\n  - [this](#this)\n- [Advanced Attributes](#advanced-attributes)\n- [Templates](#templates)\n  - [Basic Syntax](#basic-template-syntax)\n  - [Conditionals](#conditionals-if)\n  - [Negation](#negation-unless)\n  - [Iteration](#iteration-each)\n- [Fragments](#fragments)\n- [Styling](#styling)\n- [Connecting to a Data Store](#connecting-to-a-data-store)\n  - [Backbone](#backbone)\n  - [MobX](#mobx)\n  - [Redux](#redux)\n- [Best Practices](#best-practices)\n- [Development](#development)\n\n---\n\n# Define a custom-element\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n  \u003chead\u003e\n    \u003cscript src=\"https://cdn.jsdelivr.net/npm/hyperhtml@latest/index.js\"\u003e\u003c/script\u003e\n    \u003cscript src=\"https://cdn.jsdelivr.net/npm/hyper-element@latest/build/hyperElement.min.js\"\u003e\u003c/script\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\n    \u003cmy-elem who=\"world\"\u003e\u003c/my-elem\u003e\n    \u003cscript\u003e\n      customElements.define(\n        'my-elem',\n        class extends hyperElement {\n          render(Html) {\n            Html`hello ${this.attrs.who}`;\n          }\n        }\n      );\n    \u003c/script\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\nOutput\n\n```html\n\u003cmy-elem who=\"world\"\u003e hello world \u003c/my-elem\u003e\n```\n\n**Live Example of [helloworld](https://codepen.io/codemeasandwich/pen/VOQpqz)**\n\n---\n\n# Lifecycle\n\nWhen a hyper-element is connected to the DOM, it goes through the following initialization sequence:\n\n1. Element connected to DOM\n2. Unique identifier created\n3. MutationObserver attached (watches for attribute/content changes)\n4. Fragment methods defined (methods starting with capital letters)\n5. Attributes and dataset attached to `this`\n6. `setup()` called (if defined)\n7. Initial `render()` called\n\nAfter initialization, the element will automatically re-render when:\n\n- Attributes change\n- Content mutations occur (innerHTML/textContent changes)\n- Store updates trigger `onStoreChange()`\n\n---\n\n# Interface\n\n## Define your element\n\nThere are 2 functions. `render` is _required_ and `setup` is _optional_\n\n## render\n\nThis is what will be displayed within your element. Use the `Html` to define your content.\n\n```js\nrender(Html, store) {\n  Html`\n    \u003ch1\u003e\n      Last updated at ${new Date().toLocaleTimeString()}\n    \u003c/h1\u003e\n  `;\n}\n```\n\nThe second argument `store` contains the value returned from your store function (if using `setup()`).\n\n---\n\n## Html\n\nThe primary operation is to describe the complete inner content of the element.\n\n```js\nrender(Html, store) {\n  Html`\n    \u003ch1\u003e\n      Last updated at ${new Date().toLocaleTimeString()}\n    \u003c/h1\u003e\n  `;\n}\n```\n\nThe `Html` has a primary operation and two utilities: `.wire` \u0026 `.lite`\n\n---\n\n## Html.wire\n\nCreate reusable sub-elements with object/id binding for efficient rendering.\n\nThe wire takes two arguments `Html.wire(obj, id)`:\n\n1. A reference object to match with the created node, allowing reuse of the existing node\n2. A string to identify the markup used, allowing the template to be generated only once\n\n### Example: Rendering a List\n\n```js\nHtml`\n  \u003cul\u003e\n    ${users.map((user) =\u003e Html.wire(user, ':user_list_item')`\u003cli\u003e${user.name}\u003c/li\u003e`)}\n  \u003c/ul\u003e\n`;\n```\n\n### Anti-pattern: Inlining Markup as Strings\n\n**BAD example:** ✗\n\n```js\nHtml`\n  \u003cul\u003e\n    ${users.map((user) =\u003e `\u003cli\u003e${user.name}\u003c/li\u003e`)}\n  \u003c/ul\u003e\n`;\n```\n\nThis creates a new node for every element on every render, causing:\n\n- **Negative impact on performance**\n- **Output will not be sanitized** - potential XSS vulnerability\n\n### Block Syntax\n\nThe Html function supports block syntax for iteration and conditionals directly in tagged template literals:\n\n| Syntax                                | Description           |\n| ------------------------------------- | --------------------- |\n| `{+each ${array}}...{-each}`          | Iterate over arrays   |\n| `{+if ${condition}}...{-if}`          | Conditional rendering |\n| `{+if ${condition}}...{else}...{-if}` | Conditional with else |\n| `{+unless ${condition}}...{-unless}`  | Negated conditional   |\n\n#### {+each} - Iteration\n\nFor cleaner list rendering, use the `{+each}...{-each}` syntax:\n\n```js\nHtml`\u003cul\u003e{+each ${users}}\u003cli\u003e{name}\u003c/li\u003e{-each}\u003c/ul\u003e`;\n```\n\nThis is equivalent to:\n\n```js\nHtml`\u003cul\u003e${users.map((user) =\u003e Html.wire(user, ':id')`\u003cli\u003e${user.name}\u003c/li\u003e`)}\u003c/ul\u003e`;\n```\n\nThe `{+each}` syntax automatically calls `Html.wire()` for each item, ensuring efficient DOM reuse.\n\n**Available variables inside {+each}:**\n\n| Syntax               | Description                               |\n| -------------------- | ----------------------------------------- |\n| `{name}`             | Access item property                      |\n| `{address.city}`     | Nested property access                    |\n| `{...}` or `{ ... }` | Current item value (see formatting below) |\n| `{@}`                | Current array index (0-based)             |\n\n**Formatting rules for `{...}` output:**\n\n| Type                                | Output                                                |\n| ----------------------------------- | ----------------------------------------------------- |\n| Primitive (string, number, boolean) | `toString()` and HTML escaped                         |\n| Array                               | `.join(\",\")`                                          |\n| Object                              | `JSON.stringify()`                                    |\n| Function                            | Called with no args, return value follows these rules |\n\n**Examples:**\n\n```js\n// Multiple properties\nHtml`\u003cul\u003e{+each ${users}}\u003cli\u003e{name} ({age})\u003c/li\u003e{-each}\u003c/ul\u003e`;\n\n// Using index\nHtml`\u003col\u003e{+each ${items}}\u003cli\u003e{@}: {title}\u003c/li\u003e{-each}\u003c/ol\u003e`;\n\n// Nested arrays with {+each {property}}\nconst categories = [\n  { name: 'Fruits', items: [{ title: 'Apple' }, { title: 'Banana' }] },\n  { name: 'Veggies', items: [{ title: 'Carrot' }] },\n];\nHtml`\n  {+each ${categories}}\n    \u003csection\u003e\n      \u003ch3\u003e{name}\u003c/h3\u003e\n      \u003cul\u003e{+each {items}}\u003cli\u003e{title}\u003c/li\u003e{-each}\u003c/ul\u003e\n    \u003c/section\u003e\n  {-each}\n`;\n```\n\n#### {+if} - Conditionals\n\nRender content based on a condition:\n\n```js\nHtml`{+if ${isLoggedIn}}\u003cp\u003eWelcome back!\u003c/p\u003e{-if}`;\n\n// With else\nHtml`{+if ${isLoggedIn}}\u003cp\u003eWelcome back!\u003c/p\u003e{else}\u003cp\u003ePlease log in\u003c/p\u003e{-if}`;\n```\n\n#### {+unless} - Negated Conditionals\n\nRender content when condition is falsy (opposite of {+if}):\n\n```js\nHtml`{+unless ${hasErrors}}\u003cp\u003eForm is valid\u003c/p\u003e{-unless}`;\n\n// With else\nHtml`{+unless ${isValid}}Invalid input!{else}Looking good!{-unless}`;\n```\n\n---\n\n## Html.lite\n\nCreate once-off sub-elements for integrating external libraries.\n\n### Example: Wrapping jQuery DatePicker\n\n```js\ncustomElements.define(\n  'date-picker',\n  class extends hyperElement {\n    onSelect(dateText, inst) {\n      console.log('selected time ' + dateText);\n    }\n\n    Date(lite) {\n      const inputElem = lite`\u003cinput type=\"text\"/\u003e`;\n      $(inputElem).datepicker({ onSelect: this.onSelect });\n      return {\n        any: inputElem,\n        once: true,\n      };\n    }\n\n    render(Html) {\n      Html`Pick a date ${{ Date: Html.lite }}`;\n    }\n  }\n);\n```\n\nThe `once: true` option ensures the fragment is only generated once, preventing the datepicker from being reinitialized on every render.\n\n---\n\n## setup\n\nThe `setup` function wires up an external data-source. This is done with the `attachStore` argument that binds a data source to your renderer.\n\n```js\nsetup(attachStore) {\n  // the getMouseValues function will be called before each render and passed to render\n  const onStoreChange = attachStore(getMouseValues);\n\n  // call onStoreChange on every mouse event\n  onMouseMove(onStoreChange);\n\n  // cleanup logic\n  return () =\u003e console.warn('On remove, do component cleanup here');\n}\n```\n\n**Live Example of [attach a store](https://codepen.io/codemeasandwich/pen/VOQWeN)**\n\n### Re-rendering Without a Data Source\n\nYou can trigger re-renders without any external data:\n\n```js\nsetup(attachStore) {\n  setInterval(attachStore(), 1000); // re-render every second\n}\n```\n\n### Set Initial Values\n\nPass static data to every render:\n\n```js\nsetup(attachStore) {\n  attachStore({ max_levels: 3 }); // passed to every render\n}\n```\n\n### Cleanup on Removal\n\nReturn a function from `setup` to run cleanup when the element is removed from the DOM:\n\n```js\nsetup(attachStore) {\n  let newSocketValue;\n  const onStoreChange = attachStore(() =\u003e newSocketValue);\n  const ws = new WebSocket('ws://127.0.0.1/data');\n\n  ws.onmessage = ({ data }) =\u003e {\n    newSocketValue = JSON.parse(data);\n    onStoreChange();\n  };\n\n  // Return cleanup function\n  return ws.close.bind(ws);\n}\n```\n\n### Multiple Subscriptions\n\nYou can trigger re-renders from multiple sources:\n\n```js\nsetup(attachStore) {\n  const onStoreChange = attachStore(user);\n\n  mobx.autorun(onStoreChange); // update when changed (real-time feedback)\n  setInterval(onStoreChange, 1000); // update every second (update \"the time is now ...\")\n}\n```\n\n---\n\n## this\n\nAvailable properties and methods on `this`:\n\n| Property              | Description                                                                 |\n| --------------------- | --------------------------------------------------------------------------- |\n| `this.attrs`          | Attributes on the tag. `\u003cmy-elem min=\"0\" max=\"10\" /\u003e` = `{ min:0, max:10 }` |\n| `this.store`          | Value returned from the store function. _Only updated before each render_   |\n| `this.wrappedContent` | Text content between your tags. `\u003cmy-elem\u003eHi!\u003c/my-elem\u003e` = `\"Hi!\"`          |\n| `this.element`        | Reference to your created DOM element                                       |\n| `this.dataset`        | Read/write access to all `data-*` attributes                                |\n\n### this.attrs\n\nAttributes are automatically type-coerced:\n\n| Input     | Output    | Type   |\n| --------- | --------- | ------ |\n| `\"42\"`    | `42`      | Number |\n| `\"3.14\"`  | `3.14`    | Number |\n| `\"hello\"` | `\"hello\"` | String |\n\n### this.dataset\n\nThe dataset provides proxied access to `data-*` attributes with automatic JSON parsing:\n\n| Attribute Value          | `this.dataset` Value | Type    |\n| ------------------------ | -------------------- | ------- |\n| `data-count=\"42\"`        | `42`                 | Number  |\n| `data-active=\"true\"`     | `true`               | Boolean |\n| `data-active=\"false\"`    | `false`              | Boolean |\n| `data-users='[\"a\",\"b\"]'` | `[\"a\", \"b\"]`         | Array   |\n| `data-config='{\"x\":1}'`  | `{ x: 1 }`           | Object  |\n\n**Example:**\n\n```html\n\u003cmy-elem data-users='[\"ann\",\"bob\"]'\u003e\u003c/my-elem\u003e\n```\n\n```js\nthis.dataset.users; // [\"ann\", \"bob\"]\n```\n\nThe `dataset` is a **live reflection**. Changes update the matching data attribute on the element:\n\n```js\nthis.dataset.user = { name: 'Alice' }; // Updates data-user attribute\n```\n\n---\n\n## Advanced Attributes\n\n### Dynamic Attributes with Custom-element Children\n\nBeing able to set attributes at run-time should be the same for dealing with a native element and ones defined by hyper-element.\n\n**⚠ To support dynamic attributes on custom elements YOU MUST USE `customElements.define` which requires native ES6 support! Use `/build/hyperElement.min.js`.**\n\nThis is what allows for the passing any dynamic attributes from parent to child custom element! You can also pass a `function` to a child element (that extends hyperElement).\n\n**Example:**\n\n```js\nwindow.customElements.define(\n  'a-user',\n  class extends hyperElement {\n    render(Html) {\n      const onClick = () =\u003e this.attrs.hi('Hello from ' + this.attrs.name);\n      Html`${this.attrs.name} \u003cbutton onclick=${onClick}\u003eSay hi!\u003c/button\u003e`;\n    }\n  }\n);\n\nwindow.customElements.define(\n  'users-elem',\n  class extends hyperElement {\n    onHi(val) {\n      console.log('hi was clicked', val);\n    }\n    render(Html) {\n      Html`\u003ca-user hi=${this.onHi} name=\"Beckett\" /\u003e`;\n    }\n  }\n);\n```\n\n**Live Example of passing an [onclick to a child element](https://codepen.io/codemeasandwich/pen/rgdvPX)**\n\n---\n\n# Templates\n\nUnlike standard Custom Elements which typically discard or replace their innerHTML, hyper-element's template system **preserves** the markup inside your element and uses it as a reusable template. This means your custom element primarily holds logic, while the template markup between the tags defines how data should be rendered.\n\nTo enable templates:\n\n1. Add a `template` attribute to your custom element\n2. Define the template markup within your element\n3. Call `Html.template(data)` in your render method to populate the template\n\n**Example:**\n\n```html\n\u003cmy-list template data-json='[{\"name\":\"ann\",\"url\":\"\"},{\"name\":\"bob\",\"url\":\"\"}]'\u003e\n  \u003cdiv\u003e\n    \u003ca href=\"{url}\"\u003e{name}\u003c/a\u003e\n  \u003c/div\u003e\n\u003c/my-list\u003e\n```\n\n```js\ncustomElements.define(\n  'my-list',\n  class extends hyperElement {\n    render(Html) {\n      Html`${this.dataset.json.map((user) =\u003e Html.template(user))}`;\n    }\n  }\n);\n```\n\nOutput:\n\n```html\n\u003cmy-list template data-json='[{\"name\":\"ann\",\"url\":\"\"},{\"name\":\"bob\",\"url\":\"\"}]'\u003e\n  \u003cdiv\u003e\n    \u003ca href=\"\"\u003eann\u003c/a\u003e\n  \u003c/div\u003e\n  \u003cdiv\u003e\n    \u003ca href=\"\"\u003ebob\u003c/a\u003e\n  \u003c/div\u003e\n\u003c/my-list\u003e\n```\n\n**Live Example of using [templates](https://codepen.io/codemeasandwich/pen/LoQLrK)**\n\n---\n\n## Basic Template Syntax\n\n| Syntax                             | Description                           |\n| ---------------------------------- | ------------------------------------- |\n| `{variable}`                       | Simple interpolation                  |\n| `{+if condition}...{-if}`          | Conditional rendering                 |\n| `{+if condition}...{else}...{-if}` | Conditional with else                 |\n| `{+unless condition}...{-unless}`  | Negative conditional (opposite of if) |\n| `{+each items}...{-each}`          | Iteration over arrays                 |\n| `{@}`                              | Current index in each loop (0-based)  |\n\n---\n\n## Conditionals: {+if}\n\nShow content based on a condition:\n\n```html\n\u003cstatus-elem template\u003e{+if active}Online{else}Offline{-if}\u003c/status-elem\u003e\n```\n\n```js\ncustomElements.define(\n  'status-elem',\n  class extends hyperElement {\n    render(Html) {\n      Html`${Html.template({ active: true })}`;\n    }\n  }\n);\n```\n\nOutput: `Online`\n\n---\n\n## Negation: {+unless}\n\nShow content when condition is falsy (opposite of +if):\n\n```html\n\u003cwarning-elem template\u003e{+unless valid}Invalid input!{-unless}\u003c/warning-elem\u003e\n```\n\n```js\ncustomElements.define(\n  'warning-elem',\n  class extends hyperElement {\n    render(Html) {\n      Html`${Html.template({ valid: false })}`;\n    }\n  }\n);\n```\n\nOutput: `Invalid input!`\n\n---\n\n## Iteration: {+each}\n\nLoop over arrays:\n\n```html\n\u003clist-elem template\u003e\n  \u003cul\u003e\n    {+each items}\n    \u003cli\u003e{name}\u003c/li\u003e\n    {-each}\n  \u003c/ul\u003e\n\u003c/list-elem\u003e\n```\n\n```js\ncustomElements.define(\n  'list-elem',\n  class extends hyperElement {\n    render(Html) {\n      Html`${Html.template({ items: [{ name: 'Ann' }, { name: 'Bob' }] })}`;\n    }\n  }\n);\n```\n\nOutput:\n\n```html\n\u003cul\u003e\n  \u003cli\u003eAnn\u003c/li\u003e\n  \u003cli\u003eBob\u003c/li\u003e\n\u003c/ul\u003e\n```\n\n### Special Variables in {+each}\n\n- `{@}` - The current index (0-based)\n\n```html\n\u003cnums-elem template\u003e{+each numbers}{@}: {number}, {-each}\u003c/nums-elem\u003e\n```\n\n```js\ncustomElements.define(\n  'nums-elem',\n  class extends hyperElement {\n    render(Html) {\n      Html`${Html.template({ numbers: ['a', 'b', 'c'] })}`;\n    }\n  }\n);\n```\n\nOutput: `0: a, 1: b, 2: c, `\n\n---\n\n# Fragments\n\nFragments are pieces of content that can be loaded _asynchronously_.\n\nYou define one with a class property starting with a **capital letter**.\n\nThe fragment function should return an object with:\n\n- **placeholder:** the placeholder to show while resolving\n- **once:** Only generate the fragment once (default: `false`)\n\nAnd **one** of the following as the result:\n\n- **text:** An escaped string to output\n- **any:** Any type of content\n- **html:** A html string to output **(not sanitised)**\n- **template:** A template string to use **(is sanitised)**\n\n**Example:**\n\n```js\ncustomElements.define(\n  'my-friends',\n  class extends hyperElement {\n    FriendCount(user) {\n      return {\n        once: true,\n        placeholder: 'loading your number of friends',\n        text: fetch('/user/' + user.userId + '/friends')\n          .then((b) =\u003e b.json())\n          .then((friends) =\u003e `you have ${friends.count} friends`)\n          .catch((err) =\u003e 'problem loading friends'),\n      };\n    }\n\n    render(Html) {\n      const userId = this.attrs.myId;\n      Html`\u003ch2\u003e ${{ FriendCount: userId }} \u003c/h2\u003e`;\n    }\n  }\n);\n```\n\n**Live Example of using an [asynchronous fragment](https://codepen.io/codemeasandwich/pen/MdQrVd)**\n\n---\n\n# Styling\n\nSupports an object as the style attribute. Compatible with React's implementation.\n\n**Example:** of centering an element\n\n```js\nrender(Html) {\n  const style = {\n    position: 'absolute',\n    top: '50%',\n    left: '50%',\n    marginRight: '-50%',\n    transform: 'translate(-50%, -50%)',\n  };\n  Html`\u003cdiv style=${style}\u003e center \u003c/div\u003e`;\n}\n```\n\n**Live Example of [styling](https://codepen.io/codemeasandwich/pen/RmQVKY)**\n\n---\n\n# Connecting to a Data Store\n\nhyper-element integrates with any state management library via `setup()`. The pattern is:\n\n1. Call `attachStore()` with a function that returns your state\n2. Subscribe to your store and call the returned function when state changes\n\n## Backbone\n\n```js\nvar user = new (Backbone.Model.extend({\n  defaults: {\n    name: 'Guest User',\n  },\n}))();\n\ncustomElements.define(\n  'my-profile',\n  class extends hyperElement {\n    setup(attachStore) {\n      user.on('change', attachStore(user.toJSON.bind(user)));\n      // OR user.on(\"change\", attachStore(() =\u003e user.toJSON()));\n    }\n\n    render(Html, { name }) {\n      Html`Profile: ${name}`;\n    }\n  }\n);\n```\n\n## MobX\n\n```js\nconst user = observable({\n  name: 'Guest User',\n});\n\ncustomElements.define(\n  'my-profile',\n  class extends hyperElement {\n    setup(attachStore) {\n      mobx.autorun(attachStore(user));\n    }\n\n    render(Html, { name }) {\n      Html`Profile: ${name}`;\n    }\n  }\n);\n```\n\n## Redux\n\n```js\ncustomElements.define(\n  'my-profile',\n  class extends hyperElement {\n    setup(attachStore) {\n      store.subscribe(attachStore(store.getState));\n    }\n\n    render(Html, { user }) {\n      Html`Profile: ${user.name}`;\n    }\n  }\n);\n```\n\n---\n\n# Best Practices\n\n## Always Use Html.wire for Lists\n\nWhen rendering lists, always use `Html.wire()` to ensure proper DOM reuse and prevent XSS vulnerabilities:\n\n```js\n// GOOD - Safe and efficient\nHtml`\u003cul\u003e${users.map((u) =\u003e Html.wire(u, ':item')`\u003cli\u003e${u.name}\u003c/li\u003e`)}\u003c/ul\u003e`;\n\n// BAD - XSS vulnerability and poor performance\nHtml`\u003cul\u003e${users.map((u) =\u003e `\u003cli\u003e${u.name}\u003c/li\u003e`)}\u003c/ul\u003e`;\n```\n\n## Dataset Updates Require Assignment\n\nThe `dataset` works by reference. To update an attribute you must use **assignment**:\n\n```js\n// BAD - mutation doesn't trigger attribute update\nthis.dataset.user.name = '';\n\n// GOOD - assignment triggers attribute update\nthis.dataset.user = { name: '' };\n```\n\n## Type Coercion Reference\n\n| Source         | Supported Types                |\n| -------------- | ------------------------------ |\n| `this.attrs`   | Number                         |\n| `this.dataset` | Object, Array, Number, Boolean |\n\n## Cleanup Resources in setup()\n\nAlways return a cleanup function when using resources that need disposal:\n\n```js\nsetup(attachStore) {\n  const interval = setInterval(attachStore(), 1000);\n  return () =\u003e clearInterval(interval); // Cleanup on removal\n}\n```\n\n---\n\n# Development\n\n## Prerequisites\n\n- **Node.js** 20 or higher\n- **npm** (comes with Node.js)\n\n## Setup\n\n1. Clone the repository:\n\n   ```bash\n   git clone https://github.com/codemeasandwich/hyper-element.git\n   cd hyper-element\n   ```\n\n2. Install dependencies:\n\n   ```bash\n   npm install\n   ```\n\n   This also installs the pre-commit hooks automatically via the `prepare` script.\n\n## Available Scripts\n\n| Command               | Description                                       |\n| --------------------- | ------------------------------------------------- |\n| `npm run build`       | Build minified production bundle with source maps |\n| `npm test`            | Run Playwright tests with coverage                |\n| `npm run test:ui`     | Run tests with Playwright UI for debugging        |\n| `npm run test:headed` | Run tests in headed browser mode                  |\n| `npm run kitchensink` | Start local dev server for examples               |\n| `npm run lint`        | Run ESLint to check for code issues               |\n| `npm run format`      | Check Prettier formatting                         |\n| `npm run format:fix`  | Auto-fix Prettier formatting issues               |\n| `npm run release`     | Run the release script (maintainers only)         |\n\n## Project Structure\n\n```\nhyper-element/\n├── src/                     # Source files (ES modules)\n│   ├── core/                # Core utilities\n│   ├── attributes/          # Attribute handling\n│   ├── template/            # Template processing\n│   ├── html/                # HTML rendering\n│   ├── lifecycle/           # Lifecycle hooks\n│   └── hyperElement.js      # Main export\n├── build/\n│   ├── hyperElement.min.js  # Minified production build\n│   └── hyperElement.min.js.map\n├── kitchensink/             # Test suite\n│   ├── kitchensink.spec.js  # Playwright test runner\n│   └── *.html               # Test case files\n├── example/                 # Example project\n├── docs/                    # Documentation\n├── .hooks/                  # Git hooks\n│   ├── pre-commit           # Main hook orchestrator\n│   ├── commit-msg           # Commit message validator\n│   └── pre-commit.d/        # Modular validation scripts\n└── scripts/\n    └── publish.sh           # Release script\n```\n\n## Building\n\nThe build process uses [esbuild](https://esbuild.github.io/) for fast, minimal output:\n\n```bash\nnpm run build\n```\n\nThis produces:\n\n- `build/hyperElement.min.js` - Minified bundle (~6.2 KB)\n- `build/hyperElement.min.js.map` - Source map for debugging\n\n## Pre-commit Hooks\n\nThe project uses a modular pre-commit hook system located in `.hooks/`. When you commit, the following checks run automatically:\n\n1. **ESLint** - Code quality checks\n2. **Prettier** - Code formatting\n3. **Build** - Ensures the build succeeds\n4. **Coverage** - Enforces 100% test coverage\n5. **JSDoc** - Documentation validation\n6. **Docs** - Documentation completeness\n\nIf any check fails, the commit is blocked until the issue is fixed.\n\n### Installing Hooks Manually\n\nIf hooks weren't installed automatically:\n\n```bash\nnpm run hooks:install\n```\n\n## Code Style\n\n- **Prettier** for formatting (2-space indent, single quotes, trailing commas)\n- **ESLint** for code quality\n- All files are automatically checked on commit\n\nRun formatting manually:\n\n```bash\nnpm run format:fix\n```\n\n## Testing\n\nSee [kitchensink/README.md](kitchensink/README.md) for the full testing guide.\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines.\n\n---\n\n[shadow-dom]: https://developers.google.com/web/fundamentals/web-components/shadowdom\n[innerHTML]: https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML\n[hyperHTML]: https://viperhtml.js.org/hyper.html\n[Custom Elements]: https://developer.mozilla.org/en-US/docs/Web/Web_Components/Custom_Elements\n[Test system]: https://jsfiddle.net/codemeasandwich/k25e6ufv/36/\n[promise]: https://scotch.io/tutorials/javascript-promises-for-dummies#understanding-promises\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodemeasandwich%2Fhyper-element","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcodemeasandwich%2Fhyper-element","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodemeasandwich%2Fhyper-element/lists"}