{"id":20165727,"url":"https://github.com/functionalland/functional-component","last_synced_at":"2026-06-01T01:31:00.236Z","repository":{"id":38232377,"uuid":"474372486","full_name":"functionalland/functional-component","owner":"functionalland","description":null,"archived":false,"fork":false,"pushed_at":"2022-06-08T18:53:12.000Z","size":57,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-09-18T18:48:08.129Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","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/functionalland.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":"2022-03-26T14:31:38.000Z","updated_at":"2022-06-06T15:34:56.000Z","dependencies_parsed_at":"2022-09-04T21:52:14.460Z","dependency_job_id":null,"html_url":"https://github.com/functionalland/functional-component","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/functionalland/functional-component","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/functionalland%2Ffunctional-component","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/functionalland%2Ffunctional-component/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/functionalland%2Ffunctional-component/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/functionalland%2Ffunctional-component/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/functionalland","download_url":"https://codeload.github.com/functionalland/functional-component/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/functionalland%2Ffunctional-component/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33756575,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-31T02:00:06.040Z","response_time":95,"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":[],"created_at":"2024-11-14T00:38:54.413Z","updated_at":"2026-06-01T01:31:00.220Z","avatar_url":"https://github.com/functionalland.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Functional Component\n\nThis is an experiemental project. The goal is to build a rendering framework\nleveraging the\n[web components API](https://developer.mozilla.org/en-US/docs/Web/Web_Components).\nIf you don't know what to do with it... it's okay.\n\n## API\n\n### `factorizeComponent`\n\nFactorizes a component given a render function, a state and an arbitrary number\nof composable functions.\n\nThe first argument is a render function. The function is called once when the\ncomponent is connected to the DOM. The render function should accept two\nparameters, the first one is the element that is being rendered. The second\nparameter is the current state that should be used to render the component.\n\nThe second argument to the `factorizeComponent` function is an object that will\nbe used as the inital state.\n\n`State :: Object`\\\n`R :: (DOMElement, State) -\u003e void`\\\n`F :: (((Component, R, State) -\u003e C) -\u003e void, ((DOMElement) -\u003e E) -\u003e void) -\u003e void`\\\n`factorizeComponent :: ((DOMElement, State) -\u003e void, State, [...F]) -\u003e Component`\n\n```js\nwindow.customElements.define(\n  \"iy-demo\",\n  factorizeComponent(\n    (e, { title }) =\u003e {\n      const h1 = window.document.createElement(\"h1\");\n      h1.textContent = title;\n      e.appendChild(e);\n    },\n    { title: \"Bluebird\" },\n  ),\n);\n```\n\n#### Higher-order-function\n\nThe `factorizeComponent` also accepts an arbitrary amount of functions as\narguments. Those higher-order-functions should accept 2 parameters; the first\none is named `factorize`, the second is named `construct`.\n\nBoth HOFs accepts a function that will be called, respectively, at the factory\nphase, before the component is initialize, and, at the construction phase, when\nthe component is being instantiated.\n\nThe factorize function has three parameters; the first parameter is a Component\nconstructor; the second parameter is the render function which can be called to\nqueue a render request; the third parameter is the initial state.\n\n```js\nwindow.customElements.define(\n  \"iy-demo\",\n  factorizeComponent(\n    render,\n    state,\n    (factorize, construct) =\u003e {\n      factorize((Component, render) =\u003e {\n        Object.defineProperty(\n          Component.prototype,\n          \"forceRender\",\n          {\n            configurable: false,\n            enumerable: false,\n            value() {\n              render(this);\n            },\n          },\n        );\n\n        return Component;\n      });\n      construct((element) =\u003e {\n        element.dataset.forceRender = true;\n      });\n    },\n  ),\n);\n```\n\n### `fetchTemplate`\n\nFetches a HTML file from a server and returns the Promise of a `\u003ctemplate\u003e`.\n\n```js\nconst element = window.document.querySelector(\"div\");\nfetchTemplate(\"/demo.html\")()\n  .then((template) =\u003e {\n    element.appendChild(template.content.cloneNode(true));\n  });\n```\n\n### `useAttributes`\n\nCreates a reactive lifecycle with a simple state reducer. When a user or a\nprogram sets an attribute of the component, the validation function is called\nwhich decides if the component should be rendered again.\n\nThe `useAttributes` function accepts a function to validate an observed\nattributes value and create a new state. The validation function should have\nthree parameters. The first one is an object representing the attribute that was\nchanged, the second is the element that is affected and the last is the current\nstate of the element. The validation function shoudl return a state fragment or\nfalse to cancel the render.\n\nThe hook function also takes a map object for all of the attributes to\nobserve.The value is a function to transform the value before the validation\nfunction called. If not transformation is needed, just pass the identity\nfunction. `(x) =\u003e x`\n\n```js\nwindow.customElements.define(\n  \"iy-demo\",\n  factorizeComponent(\n    (element, { value }) =\u003e {\n      const span = element.querySelector(\"span\");\n      span.textContent = String(value);\n    },\n    { value: 0 },\n    useAttributes(\n      ({ oldValue, value }) =\u003e\n        (oldValue !== value \u0026\u0026 value \u003e= 0) ? ({ value }) : false,\n      {\n        value: Number,\n      },\n    ),\n    (factorize) =\u003e {\n      factorize((Component) =\u003e {\n        Object.defineProperty(\n          Component.prototype,\n          \"connectedCallback\",\n          {\n            enumerable: true,\n            value() {\n              console.log(\"hello\");\n              const span = window.document.createElement(\"span\");\n              const button = window.document.createElement(\"button\");\n              button.textContent = \"Add\";\n              this.appendChild(span);\n              this.appendChild(button);\n              button.addEventListener(\"click\", () =\u003e {\n                const v = this.getAttribute(\"value\");\n                this.setAttribute(\"value\", String(Number(v) + 1));\n              });\n            },\n          },\n        );\n        return Component;\n      });\n    },\n  ),\n);\n```\n\n### `useCallbacks`\n\nHooks into the component's lifecycle. Learn more about\n[lifecycle callbacks on MDN](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements#using_the_lifecycle_callbacks)\n\nThe function accepts as argument an object of function to hook into one of the\nfollowing callback: `connectedCallback`, `disconnectedCallback`,\n`attributeChangedCallback` and `adoptedCallback`.\n\nEach callback function will be called when appropriate with the element, the\nrelevant options and a `asyncRender` function as arguments. The `asyncRender`\nfunction can be called at any moment with a \"state setter\" function. This\nreturns a thunk function that may accept an argument. When the thunk function is\ncalled, the \"state setter\" function is called with the current element and state\nas argument. This function should return a state fragment or false. The state\nfragment is then merged with the current state and set the relevant component\nattributes. See `useAttribute`.\n\n```js\nwindow.customElements.define(\n  \"iy-demo\",\n  factorizeComponent(\n    (element, { value }) =\u003e {\n      const span = element.querySelector(\"span\");\n      span.textContent = String(value);\n    },\n    { value: 0 },\n    useCallbacks({\n      connectedCallback: (element, render) =\u003e {\n        const span = window.document.createElement(\"span\");\n        const button = window.document.createElement(\"button\");\n        button.textContent = \"Add\";\n        element.appendChild(span);\n        element.appendChild(button);\n        button.addEventListener(\n          \"click\",\n          render((e, { value }) =\u003e ({ value: ++value })),\n        );\n      },\n    }),\n    (factorize) =\u003e {\n      factorize((Component, render) =\u003e {\n        Object.defineProperty(\n          Component,\n          \"observedAttributes\",\n          {\n            enumerable: true,\n            value: [\"value\"],\n          },\n        );\n\n        Object.defineProperty(\n          Component.prototype,\n          \"attributeChangedCallback\",\n          {\n            enumerable: true,\n            value(name, oldValue, value) {\n              this[StateSymbol][name] = value;\n              render(this, Object.assign({}, this[StateSymbol]));\n            },\n          },\n        );\n\n        return Component;\n      });\n    },\n  ),\n);\n```\n\n### `useShadow`\n\nAttaches a shadow root to every instance of the Component.\n\n```js\nwindow.customElements.define(\n  \"iy-demo\",\n  factorizeComponent(\n    (element, { value }) =\u003e {\n      const span = element.querySelector(\"span\");\n      span.textContent = String(value);\n    },\n    { value: 0 },\n    useShadow({ mode: \"open\" }),\n  ),\n);\n```\n\n### `useTemplate`\n\nAutomatically appends a clone of a template to the element or the element's\nshadow root.\n\nThe function accepts a function that must return a template instance or a\nPromise of a template instance. Optionally, the function can also be passed an\nobject as the second argument that is used to define children that would be\noften queried during the render phase. The object's values must be a function\nthat will accept the component instance as the first parameter and return a\nchild element or node list. The elements will be accessible as the `elements`\nproperty of the Component instance element.\n\n```js\nwindow.customElements.define(\n  \"iy-demo\",\n  factorizeComponent(\n    (e, { value }) =\u003e {\n      e.elements.number.textContent = String(value);\n    },\n    { value: 0 },\n    useShadow({ mode: \"open\" }),\n    useTemplate(\n      () =\u003e {\n        const t = window.document.createElement(\"template\");\n        t.innerHTML = `\u003cspan\u003e0\u003c/span\u003e\u003cbutton\u003eAdd\u003c/button\u003e`;\n\n        return t;\n      },\n      {\n        number: (e) =\u003e e.shadowRoot.querySelector(\"span\"),\n        addButton: (e) =\u003e e.shadowRoot.querySelector(\"button\"),\n      },\n    ),\n  ),\n);\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffunctionalland%2Ffunctional-component","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffunctionalland%2Ffunctional-component","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffunctionalland%2Ffunctional-component/lists"}