26
loading...
This website collects cookies to deliver better user experience
custom-elements.json
to achieve this goal.You can read more about @custom-elements-manifest/analyzers rich plugin system here: Plugin Authoring Handbook, and be sure to check out the cem-plugin-template repository.
reactify
plugin here."customElement": true
flag, so we can loop through all the modules of our Manifest, and find any class declaration that has the customElement
flag:const elements = [];
customElementsManifest?.modules?.forEach(mod => {
mod?.declarations?.forEach(dec => {
if(dec.customElement) elements.push(dec);
})
});
children
. Which means... we can use children
to project any children of the Reactified component, straight to the Custom Element, which (if it supports slots), will correctly render them.function GenericSwitch({children}) {
return <generic-switch>{children}</generic-switch>
}
<GenericSwitch>Toggle me!</GenericSwitch>
disabled
attribute/property or a checked
attribute/property.fieldName
property:"attributes": [
{
"name": "checked",
"type": {
"text": "boolean"
},
"fieldName": "checked"
},
]
checked
attribute, but interface with the checked
property instead, and avoid having two props with the same name.ref
for our custom element, and set the property that way. Here's an example:function GenericSwitch({checked}) {
const ref = useRef(null);
useEffect(() => {
ref.current.checked = checked;
}, [checked]);
return <generic-switch ref={ref}></generic-switch>
}
<generic-skiplink for="someID"></generic-skiplink>
for
attribute is no problem. But since we're reactifying, and everything in React-land gets passed around as a JavaScript property, we now have a problem. Can you spot what the problem is in this code?function GenericSkiplink({for}) {
return <generic-skiplink for={for}></generic-skiplink>
}
for
is a reserved JavaScript keyword, so this will cause an error. In order to avoid this, we'll provide an attribute mapping to avoid these kinds of clashes:export default {
plugins: [
reactify({
// Provide an attribute mapping to avoid clashing with React or JS reserved keywords
attributeMapping: {
for: '_for',
},
}),
],
};
function GenericSkiplink({_for}) {
return <generic-skiplink for={_for}></generic-skiplink>
}
<button disabled></button>
<button disabled=""></button>
<button disabled="true"></button>
<button disabled="false"></button> <!-- Yes, even this is considered as `true`! -->
button.hasAttribute('disabled')
on any of these will result in true
.ref.current.setAttribute()
, but we need some special handling. Fortunately, the Custom Elements Manifest supports types, so we can easily make a distinction between 'regular' attributes, and boolean attributes:"attributes": [
{
"name": "checked",
"type": {
+ "text": "boolean"
},
"fieldName": "checked"
},
]
<button onClick={e => console.log(e)}/>
"events": [
{
"name": "checked-changed",
"type": {
"text": "CustomEvent"
}
}
],
on
, and capitalize, and camelize them; onCheckedChanged
.ref
to add an event listener:function GenericSwitch({onCheckedChanged}) {
const ref = useRef(null);
useEffect(() => {
ref.current.addEventListener("checked-changed", onCheckedChanged);
}, []);
return <generic-switch ref={ref}></generic-switch>
}
customElements.define()
call, it will be present in the Manifest. This means we can loop through the Manifest, find where our custom element gets defined, and stitch together some information from the package.json
to create a bare module specifier:switch.js
:import { GenericSwitch } from './generic-switch/GenericSwitch.js';
customElements.define('generic-switch', GenericSwitch);
custom-elements.json
:{
"kind": "javascript-module",
"path": "switch.js",
"declarations": [],
"exports": [
{
"kind": "custom-element-definition",
"name": "generic-switch",
"declaration": {
"name": "GenericSwitch",
"module": "/generic-switch/GenericSwitch.js"
}
}
]
},
name
property from the projects package.json
, and the path
from the module containing the custom element definition, we can construct a bare module specifier for the import:import '@generic-components/components/switch.js';
custom-elements-manifest.config.js
in the root of my project, import the plugin, and add it to the plugins
array:custom-elements-manifest.config.js
:import reactify from './cem-plugin-reactify.js';
export default {
plugins: [
reactify()
]
};
└── legacy
├── GenericAccordion.jsx
├── GenericAlert.jsx
├── GenericDialog.jsx
├── GenericDisclosure.jsx
├── GenericListbox.jsx
├── GenericRadio.jsx
├── GenericSkiplink.jsx
├── GenericSwitch.jsx
├── GenericTabs.jsx
└── GenericVisuallyHidden.jsx
<GenericSwitch
disabled={false} // boolean attribute
checked={true} // property
label={'foo'} // regular attribute
onCheckedChanged={e => console.log(e)} // event
>
Toggle me! // slot
</GenericSwitch>