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 ; }