{"id":13806608,"url":"https://github.com/adobe/react-webcomponent","last_synced_at":"2025-05-13T22:30:30.715Z","repository":{"id":66034916,"uuid":"158740920","full_name":"adobe/react-webcomponent","owner":"adobe","description":"This projects automates the wrapping of a React component in a CustomElement.","archived":false,"fork":false,"pushed_at":"2024-04-10T00:39:44.000Z","size":186,"stargazers_count":105,"open_issues_count":12,"forks_count":19,"subscribers_count":17,"default_branch":"master","last_synced_at":"2025-05-08T23:36:19.140Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/adobe.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":"CODE_OF_CONDUCT.md","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":"2018-11-22T19:06:17.000Z","updated_at":"2024-12-16T21:22:55.000Z","dependencies_parsed_at":null,"dependency_job_id":"aa5ca279-d19e-49cf-9152-7cb5401f562d","html_url":"https://github.com/adobe/react-webcomponent","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adobe%2Freact-webcomponent","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adobe%2Freact-webcomponent/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adobe%2Freact-webcomponent/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adobe%2Freact-webcomponent/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/adobe","download_url":"https://codeload.github.com/adobe/react-webcomponent/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254036812,"owners_count":22003655,"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-04T01:01:13.822Z","updated_at":"2025-05-13T22:30:30.428Z","avatar_url":"https://github.com/adobe.png","language":"JavaScript","funding_links":[],"categories":["Libraries"],"sub_categories":["Integrations"],"readme":"# React Web Component\n\nThis project aims to bridge the connection between a React Component and a CustomElement.\nThe strategy the library takes is to create a model that defines how the DOM is translated into React props.\n\nSince the goal is support React components as CustomElements we are not supporting extending from builting elements such as Paragraph, Select etc. \n\nIt supports:\n* Updating the React component automatically when attributes get changed\n* Supports automatic registration and triggering of events\n* Supports multiple render targets, the custom element itself, a container div or shadow root.\n* Allows parsing and updating of nested structures of DOM, for example:\n```js\n    \u003cselect\u003e\n        \u003coption value=\"something\"\u003eElse\u003c/option\u003e\n        \u003coption value=\"certain\"\u003eValue\u003c/option\u003e\n    \u003c/select\u003e\n```\nThis DOM structure can be transformed into a model and automatically injected into a React Component.\nYou can transform *any* DOM structure into a model that will be passed to the React Component.\nThis allows you to make use of most of the DOM api and encapsulate React as an implementation detail.\n\n### Installing the library\n\n```\nnpm install @adobe/react-webcomponent\n```\n\nIf you are using Babel 6:\nBecause we are targeting CustomElements V1 and we are using Babel to transpile our code there will be a problem with instantiating the CustomElement.\nSee [this issue](https://github.com/w3c/webcomponents/issues/587) for the discussion. \nInclude the [custom-elements-es5-adapter](https://github.com/webcomponents/webcomponentsjs#custom-elements-es5-adapterjs) before you load this librabry to fix this issue.\n\nIs you are using Babel 7: the issues is fixed so you shouldn't need anything else.\n\nWe are using class properties and decorators so make sure you include the appropiat babel plugins to use this.  \n\n### Defining a Custom Element\nThe first thing which is need is a React Component to expose as a Custom Element\n\n```jsx\nimport React, { Component } from 'react';\n\nimport { createCustomElement, DOMModel, byContentVal, byAttrVal, registerEvent } from \"@adobe/react-webcomponent\";\n\nclass ReactButton extends Component {\n    constructor(props) {\n        super(props);\n    }\n    render() {\n        return (\u003cdiv\u003e\n            \u003cbutton weight={ this.props.weight }\u003e{ this.props.label }\u003c/button\u003e\n            \u003cp\u003eText\u003c/p\u003e\n        \u003c/div\u003e)\n    }\n}\n```\nThen you need to create a model which defines how the DOM is parsed into React properties.\n\n```jsx\nclass ButtonModel extends DOMModel {\n    @byContentVal text = \"something\";\n    @byAttrVal weight;\n    @registerEvent(\"change\") change;\n}\n```\nYou create the custom element\n```jsx\nconst ButtonCustomElement = createCustomElement(ReactButton, ButtonModel, \"container\");\n```\nAnd then register it\n\n```js\nwindow.customElements.define(\"test-button\", ButtonCustomElement);\n```\n\n#### Defining where the React component will be rendered\n\nWhen defining the CustomElement you have the posibility to specify where the React component will be rendered by specifying the `renderRoot` property. \nThe possible values are:  \n* container  \nThis will generate an extra div inside the custom element and the React Component will be rendered there.  \nThis is useful because React will remove all the children of the container element it renders in.  \nSo if you would like to parse values from the provided markup of the custom element and modify them, the elements will be lost after the initial rendering.  \nFor example:\n```js\n\u003cmy-button\u003e\n  \u003cmy-button-label\u003eMy Button\u003c/my-button-label\u003e\n\u003c/my-button\u003e\n```\n\u003e If we wouldn't render in a container the `my-button-label` element would be removed by React when rendering.\n\n* shadowRoot   \nThis will determine the creation of the custom element shadowRoot and the React component will be rendered in it\n\n* element   \nThe React component will be rendered directly in the custom element.\n\n#### Extending the custom element\nBy default we provide the utility to create a custom element `createCustomElement`. This encapsulates the default behaviour but doesn't allow extension of the element.  \nThis can be bypassed and the customElement can be extended with new capabilities.\n\n```js\nimport { CustomElement } from \"@adobe/react-webcomponent\";\n\nclass ButtonCustomElement extends CustomElement {\n    constructor() {\n        super();\n        this._custom = 3;\n    }\n    get custom() {\n        return this._custom;\n    }\n\n    set custom(value) {\n        this._custom = value;\n    }\n};\nButtonCustomElement.observedAttributes = Model.prototype.attributes;\nButtonCustomElement.domModel = Model;\nButtonCustomElement.ReactComponent = ReactComponent;\nButtonCustomElement.renderRoot = \"container\"; // optional, defaults to \"element\"\nwindow.customElements.define(\"test-button\", ButtonCustomElement);\n```\n\n### DOMModel\nThis utility is reponsible from converting a DOM node to a model. The model is decorated with a series of specialize decorators. Each decorator will parse the dom and construct the model:\n* [byAttrVal](#byattrval)\n* [byBooleanAttrVal](#bybooleanattrval)\n* [byJsonAttrVal](#byjsonattrval)\n* [byContentVal](#bycontentval)\n* [byContent](#bycontent)\n* [byChildContentVal](#bychildcontentval)\n* [byChildRef](#bychildref)\n* [byModel](#bymodel)\n* [byChildModelVal](#bychildmodelval)\n* [byChildrenRefArray](#bychildrenrefarray)\n* [byChildrenTypeArray](#bychildrentypearray)\n* [registerEvent](#registerevent)\n\n#### byAttrVal . \n\nParses the element and sets the value corresponding to the attribute value of element\n```js\n@byAttrVal(attrName:string) - defaults to the name of the property that it decorates.\n```\n```js\nclass Model extends DOMModel {\n    @byAttrVal() weight;\n    @byAttrVal(\"custom-attribute-name\") reactPropName;\n}\n```\nUsage:\n```js\n\u003cdiv id=\"elem\" weight=\"3\" custom-attribute-name=\"some value\"/\u003e\nconst model = new Model().fromDOM(document.getElementById(\"elem\"));\nmodel ~ {\n    weight: \"3\",\n    reactPropName: \"some value\"\n}\n```\n\n#### byBooleanAttrVal  \n\nParses the element and sets the value corresponding to the presence of the attribute on the element.  \nThe value of the attribute is ignored, only the presence of the attribute determines the value\n```js\n@byBooleanAttrVal(attrName:string) - defaults to the name of the property it decorates\n```\n```js\nclass Model extends DOMModel {\n    @byBooleanAttrVal() checked;\n    @byBooleanAttrVal(\"is-required\") required;\n}\n```\nUsage:\n```js\n\u003cdiv id=\"elem\" checked/\u003e\nconst model = new Model().fromDOM(document.getElementById(\"elem\"));\nmodel ~ {\n    checked: true,\n    required: undefined\n}\n\u003cdiv id=\"elem\" checked is-required/\u003e\nmodel.fromDOM(document.getElementById(\"elem\"));\nmodel ~ {\n    checked: true,\n    required: true\n}\n```\n\n#### byJsonAttrVal\n\nParses the element and sets the value by parsing the value using `JSON.parse`.\n```js\nclass Model extends DOMModel {\n    @byJsonAttrVal() obj;\n    @byJsonAttrVal(\"alias-attr\") anotherObj;\n}\n\n\u003cdiv id=\"elem\" obj='[{\"example\":1},{\"test\":2}]' alias-attr='[{\"other\":\"example},{\"test\":3}]'/\u003e\nconst model = new Model().fromDOM(document.getElementById(\"elem\"));\nmodel ~ {\n    obj: [{\"example\":1},{\"test\":2}],\n    anotherObj: [{\"other\":\"example},{\"test\":3}]\n}\n```\n\n#### byContentVal\nParse the element and sets the value to the `innerText` of the element.\n```js\nclass Model extends DOMModel {\n    @byContentVal() label;\n}\n\u003cdiv id=\"elem\"\u003eMy Label\u003c/div\u003e\nconst model = new Model().fromDOM(document.getElementById(\"elem\"));\nmodel ~ {\n    label: \"My Label\"\n}\n```\n\n#### byContent\nThis decorator allows you to capture a DOM node that is matched by a CSS selector.\nThis can be used to reparent arbitrary child DOM content, which may not have been\nrendered with React, into your web component. Once parsing has occurred, the field\nin the model will contain a React component that represents the DOM content that\nwill be reparented.\n\nThe DOM content will be moved when the React component is mounted. And, the content\nwill be put back in its original location if the React component is later unmounted.\n```js\n@byContent(attrName:selector) - the CSS selector that will match the child node.\n```\n```js\nclass Model extends DOMModel {\n    @byContent('.content') content;\n}\n\u003cdiv id=\"elem\"\u003e\n    \u003cdiv class=\"content\"\u003e\n        This will be reparented\n    \u003c/div\u003e\n\u003c/div\u003e\n\u003cdiv id=\"mount-point\"/\u003e\n\nconst model = new Model().fromDOM(document.getElementById(\"elem\"));\nReactDOM.render(\u003cdiv\u003e{ model.content }\u003c/div\u003e, document.getElementById(\"mount-point\"))\n\n// Once React has rendered the above component, the DOM will look like this\n\n\u003cdiv id=\"elem\"\u003e\n    \u003c!-- placeholder for DIV --\u003e\n\u003c/div\u003e\n\u003cdiv id=\"mount-point\"\u003e\n    \u003cdiv class=\"content\"\u003e\n        This will be reparented\n    \u003c/div\u003e\n\u003c/div\u003e\n```\n\n#### byChildContentVal\nParse the element looking for an element that matches the given selector and sets value to the `innerText` of that element\n```js\nclass Model extends DOMModel {\n    @byChildContentVal(\"custom-label\") label;\n}\n\u003cdiv id=\"elem\"\u003e\u003ccustom-label\u003eMy Label\u003c/custom-label\u003e\u003c/div\u003e\nconst model = new Model().fromDOM(document.getElementById(\"elem\"));\nmodel ~ {\n    label: \"My Label\"\n}\n```\n\n#### byChildRef\nParses the element and looks for an child element that matches the given selector and sets the value to result of parsing the child element with the given model\n\n```js\nclass CustomLabelModel extends DOMModel {\n    @byContentVal() value;\n    @byAttrVal() required;\n}\nclass Model extends DOMModel {\n    @byChildContentVal(\"custom-label\", CustomLabelModel) label;\n}\n\n\u003cdiv id=\"elem\"\u003e\u003ccustom-label required\u003eMy Label\u003c/custom-label\u003e\u003c/div\u003e\nconst model = new Model().fromDOM(document.getElementById(\"elem\"));\nmodel ~ {\n    label: {\n        value: \"My Label\",\n        required: true\n    }\n}\n```\n\n#### byModel\nAssigns the value of running a given model over the element.\nThis allows the element model to be saved on a different property than directly on the model.\n\n```js\nclass CustomModel extends DOMModel {\n    @byContentVal() value;\n    @byAttrVal() required;\n}\n\nclass Model extends DOMModel {\n    @byModelVal() item;\n}\n\n\u003cdiv id=\"elem\" required\u003eContent\u003c/div\u003e\nconst model = new Model().fromDOM(document.getElementById(\"elem\"));\nmodel ~ {\n    item: {\n        value: \"Content\",\n        required: true\n    }\n}\n```\n\n#### byChildModelVal\nParse the element and sets the value by getting the model value from the custom elem.\nThis attribute only returns something if there is a custom element parsed.\n\nUsing the Button defined at the beginning:\n```js\nwindow.customElements.define(\"test-button\", ButtonCustomElement);\n```\nWe define a model\n```js\nclass Model extends DOMModel {\n    @byChildModelVal(\"test-button\") button;\n}\n\n\u003cdiv\u003e\u003ctest-button weight=\"3\"\u003eClick me\u003c/test-button\u003e\u003c/div\u003e\nconst model = new Model().fromDOM(document.getElementById(\"elem\"));\nmodel ~ {\n    button: {\n        weight: 3,\n        text: \"Click me\"\n    }\n}\n```\nThe fundamental difference here is that the model is defined in another custom element and it is reused in this model.\nSo it doesn't get redefined.\n\n#### byChildrenRefArray\nParse the element children and selects all the elements that match the provided selector.   \nFor each element it uses the referenced model to parse the value of the element.   \nAll the resulting array of values is stored as the value on the decorated property.   \n\n```js\nclass OptionModel extends DOMModel {\n    @byContentVal() content;\n    @byAttrVal() value;\n    @byBooleanAttrVal() selected;\n}\n\nclass SelectModel extends DOMModel {\n    @byChildrenRefArray(\"option\", OptionModel) options;\n}\n\n\u003cselect id=\"elem\"\u003e\n    \u003coption value=\"1\"\u003eAmsterdam\u003c/option\u003e\n    \u003coption value=\"2\"\u003eBerlin\u003c/option\u003e\n    \u003coption value=\"3\"\u003eLondon\u003c/option\u003e\n\u003c/select\u003e\nconst model = new Model().fromDOM(document.getElementById(\"elem\"));\nmodel ~ {\n    options: [{value: 1, content: \"Amsterdam\"}, {value: 2, content: \"Berlin\"}, {value: 3, content: \"London\"}]\n}\n```\n\n#### byChildrenTypeArray\nParses the element children and for each child if the nodeName matches one from the provided map it will parse that child with the corresponding model.\n\n```js\nclass Child1Model extends DOMModel {\n    @byAttrVal() checked;\n}\n\nclass Child2Model extends DOMModel {\n    @byAttrVal() selected;\n}\n\nclass Model extends DOMModel {\n    @byChildrenTypeArray({\n        \"child-one\": Child1Model,\n        \"child-two\": Child2Model\n    }) items;\n}\n\n\u003cdiv id=\"elem\"\u003e\n    \u003cchild-one checked/\u003e\n    \u003cchild-two selected/\u003e\n\u003c/div\u003e\nconst model = new Model().fromDOM(document.getElementById(\"elem\"));\nmodel ~ {\n    items: [{checked: true}, {selected: true}]\n}\n```\n\n#### registerEvent\nRegisters an event to be registered on the React component and when it is called it a CustomEvent will be triggered on the custom element.\nThe event name is automatically transformed into camelCase and prefixed with `on`\n*This behaviours happens on the CustomElement not the DOMModel, the DOMModel only registers the event*\n```js\nclass Model extends DOMModel {\n    @registerEvent(\"change\") change;\n}\n```\nEventually this will be converted the CustomElement in a `onChange` property on the React component. \n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadobe%2Freact-webcomponent","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fadobe%2Freact-webcomponent","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadobe%2Freact-webcomponent/lists"}