type LinkProps = AnchorHTMLAttributes;
const Link = createBlock((props) => {
const { onSelectionChange, selectBlockContents } = useEditorState(props);
const popover = usePopoverState({ portal: true });
const [href, setHref] = useState(props.href);
useEffect(() => {
setHref(props.href);
}, [props.href]);
useEffect(() => {
return onSelectionChange((selection) => {
if (selection.startContainerId === props.id || selection.endContainerId === props.id) {
popover.show();
}
});
}, [onSelect, props.id, popover.show]);
return (
<>
{
props.onClick?.(event);
if (event.defaultPrevented) return;
event.preventDefault();
popover.selectBlockConents();
}}
>
{(disclosureProps) => (
)}
>
);
});
const ListItem = createBlock((props) => {
const { value, insertBlock } = useEditorState(props);
useEnter((defaultAction) => {
if (value.length) {
insertBlock("ListItem");
} else {
defaultAction();
}
}, [value, insertBlock]);
return ;
});
const ListItem = createEditorComponent((props) => {
const editor = useEditorState(props);
useEditorEnter(() => {
editor.insertElement("ListItem");
}, [editor.insertElement]);
return ;
});
type ListProps = {
inputs: {
type: "ordered" | "unordered";
};
};
const List = createEditorComponent((props) => {
const editor = useEditorState({
...props,
defaultValue: [{
type: "ListItem",
id: `${props.id}-1`,
}],
});
const type = useEditorInput(
ToggleGroup,
{
...props,
name: "type",
options: [
{ value: "ordered", label: "Ordered", icon: "Ordered" },
{ value: "unordered", label: "Unordered", icon: "Unordered" },
],
}
);
const component = type === "ordered" ? "ol" : "ul";
// if defaultValue doesn't work
useLayoutEffect(() => {
if (!editor.value.length) {
const selection = {
startContainerId: props.id,
startOffset: 0,
endContainerId: props.id,
endOffset: 0,
};
editor.insertElement("ListItem", null, selection);
}
}, [editor.insertElement, editor.value, props.id]);
return ;
});
type HeadingProps = TextProps & {
inputs: {
level?: number;
};
};
const Heading = createEditorComponent((props) => {
const level = useEditorInput(HeadingLevel, { ...props, name: "level", label: "Heading level" });
const component = `h${level}`;
useEditorReplace("Paragraph");
return ;
});
Heading.defaultProps = {
inputs: {
level: 1,
},
};
Heading.triggers = {
"#{1,6}": (text) => ({
inputs: {
level: text.length,
},
});
};
const HeadingLevel = createEditorInput((props) => {
useEditorInputValue(props, "value");
return ;
});
const defaultComponents = {
Paragraph,
Bold,
Heading,
...
};
function App() {
const editor = useEditorState({ defaultComponents });
editor.getSelection(); // { startContainerId: "id", startOffset: 0, endContainerId: "id", endOffset: 3, collapsed: false }
editor.insertElement("Paragraph", null, editor.getSelection());
editor.insertElement(Heading, { inputs: { level: 2 } });
editor.replaceElement(Paragraph);
return ;
}