A rich-text field that saves its content as an EditorValue.
import { Editor } from '@oztix/roadie-editor/editor'
Add the editor's CSS after the components' CSS. It places the toolbar and styles the placeholder and selection:
@import '@oztix/roadie-core/css';@import '@oztix/roadie-components/css';@import '@oztix/roadie-editor/css';
The toolbar at the top of the editor holds the preset's controls, and each control's tooltip shows its shortcut.
The toolbar is a bar at the top of the editor, a size smaller than the text. It scrolls away with a long description, and looks the same whether you're editing or not, so the text never moves. On a phone or touch screen its controls scroll sideways on one row when they don't fit.
defaultValue takes an EditorValue from @oztix/roadie-editor/schema. The
basic preset holds paragraphs, bulleted and numbered lists, bold, italic,
underline, and links.
Select text and press Mod+K, or use the Link control, to link it. Type a
web address such as oztix.com.au and the editor adds https://. With
nothing selected, saving inserts the address as linked text. Typing or pasting
a web address links it as you go.
The Link, Embed, Edit link, and Alt text popovers are one row: an input, a check button that saves, and Close. Enter saves and Escape closes. Editing a link adds Remove link between the two.
A link that could run script, such as a javascript: address, never reaches
the saved value. The link editor refuses it, pasted HTML keeps only its text,
and migrate drops it from a stored value.
preset='rich' adds headings 2 to 4, quotes, and dividers to the basic
preset, for an event page. The Block type menu leads the toolbar and names
the block at the caret, then come the basic controls, from Bold, Italic, and
Underline to the lists. Markdown works as you type: ## , ### , and
#### start a heading, > a quote, and --- a divider.
The page owns its h1, so pasted h1 becomes a heading 2, and h5 and
h6 become a heading 4.
The rich preset holds images. Pass onUploadImage to add the Image control:
it uploads a file the person picks, pastes, or drops, and resolves to where
the image is served. A placeholder shows while it uploads, and a rejection's
message shows in Field.ErrorText. Without onUploadImage there's no Image
control, and image files are ignored.
An image saves only its src and alt. The src must be https or a path
on the same site, such as /images/poster.webp; any other address is refused
when it's inserted, pasted, or loaded.
Click an image, or move onto it with the arrow keys, to edit it in place. A
toolbar by the image has Alt text, Replace image (with onUploadImage, which
uploads the new file the same way and keeps the alt text), and Remove image.
An image with no alt text shows a No alt text badge until it has some; Enter
on a selected image opens Alt text too.
The rich preset embeds videos and tracks from YouTube, Vimeo, Spotify, and
TikTok. Paste a share link alone on an empty line, or use the Embed control.
The editor shows each player at its own shape, under a cover, so a click
selects it rather than playing it, and Backspace or Delete removes it. A
player with a ratio is no taller than 36rem, so a portrait one, such as
TikTok's, stays narrow at the start of the line, in the editor and in
/render.
An embed saves only its provider, its id, and the params its provider reads,
never a URL, so /render builds the player's address from
defaultEmbedProviders and a provider can move its player without a
migration. Players run in a sandboxed iframe, and a look-alike host such as
youtube.com.evil.test is never embedded.
embedProviders sets the providers the editor reads links from, starting
from defaultEmbedProviders. Pass the same list to /render, or it drops
the embeds it doesn't know.resolveUrl turns a link no provider reads, such as a TikTok short link,
into one it can, when it's entered in the Embed control. Resolve it on
your server.Click an embed, or move onto it with the arrow keys, to edit it in place. A
toolbar by the embed has Edit link and Remove embed. Edit link starts from the
embed's share link, from its provider's optional shareUrl, and replaces the
embed with the new link's player. It refuses a link no provider reads, as the
Embed control does.
The rich preset holds tables. The Table control inserts one of three rows and three columns, with a header row. Tab moves to the next cell, adding a row after the last, and Shift+Tab moves back, leaving the editor from the first cell. While the caret is in a table, a toolbar at its start edge, above it, or below it when a table comes first, has its actions: a Row menu with Insert above, Insert below, and Delete row, a Column menu with Insert before, Insert after, and Delete column, the Header row toggle, and Delete table.
A cell holds paragraphs, with bold, italic, underline, and links, but no
headings, lists, images, embeds, or other tables, so a list pasted into a
cell, as from Word, keeps each item as a paragraph. A table saves no widths or
styles, only each cell's colspan and rowspan, and a wide one scrolls
sideways in the editor and in /render, so it never widens the page on a
phone.
emphasis sets the frame. The toolbar takes its own
emphasis, so it's the same in each.
| Emphasis | Frame | Use it for |
|---|---|---|
normal (default) | A field's border, fill, and focus ring | A form, beside other fields |
subtle | A tint with no border, and a focus ring | A form on a busy surface, where a border adds noise |
subtler | None: the content sits on the page | Content edited in place, such as an event page's sections |
emphasis='subtler' drops the border, fill, and padding, so the text lines up
with the page around it and editing looks like reading. The caret shows
where focus is.
Its skeleton is lines of text, not a box.
An invalid subtler editor has no border to turn red, so it draws a danger
edge on its start side, just outside the text, and Field.ErrorText says
what's wrong. Give it room at the start: a container that clips its overflow
hides the edge. A disabled one dims as a disabled field does.
Editor.Toolbar takes Toolbar's emphasis for its bar, the same on every screen and
whether you're editing or not. It's independent of the editor's own emphasis. To
change it, pass the parts yourself:
| Emphasis | Bar | Use it for |
|---|---|---|
normal (default) | A visible border on the normal surface | Most editors, in a form or on the page |
subtle | A tint with no border | A busy surface, where a border adds noise |
subtler | No surface, just the controls | A quiet bar, such as over a subtler editor |
raised | A translucent raised surface | An editor on a tinted surface |
floating | A translucent surface with a deep shadow | A bar that should read as above the content |
size sets the body and heading sizes to match Prose, so editing looks
like the published page.
onValueChange gets the whole EditorValue on each change. Save it as JSON,
never as HTML, and pass a stored value through migrate before it goes back in.
The editor takes its preset from the value. A preset prop that differs reads
the value into that preset, keeping the text of what it can't hold.
In a Field, the editor is named by Field.Label and described by
Field.HelperText, or by Field.ErrorText while the field is invalid. It
takes invalid, required, and disabled from the field.
name adds a hidden input that holds the EditorValue as JSON, so a native
form submits it. A disabled editor leaves it out, as a disabled input does.
With no children, Editor renders Editor.Toolbar then Editor.Content.
Pass them yourself to add controls: Editor.Toolbar renders the preset's
controls, then its children. A Toolbar.Button child joins the toolbar's
arrow order, and useEditorContext() gives it the editor to run commands on.
@oztix/roadie-editor/render turns a saved EditorValue into the page's
content, with none of the editor's code. It works in a server component and
on the client.
import { renderEditorHtml, renderEditorValue } from '@oztix/roadie-editor/render'
renderEditorValue(value, { size, className }) returns React elements in
Prose at size, so pass the editor's size. Links render through Link,
so internal hrefs route through the Link given to RoadieProvider.renderEditorHtml(value, { size, className }) returns the same content as
an HTML string in a .prose element, for a consumer that isn't React.
External links get target="_blank" and rel="noopener noreferrer", as
Link gives them.loading="lazy", and embeds as a lazy, sandboxed
iframe whose address they rebuild from the provider. Pass
embedProviders if the editor has its own, or embeds from any other
provider drop..prose-scroll, so a wide table scrolls on a
phone rather than widening the page, with a first row of header cells in a
thead, unless one spans down, as a thead would clip it.Both run migrate first, so a value from a newer package, or with a preset
this one doesn't know, throws. They render only the nodes, marks, and
attributes the value's preset defines, and keep only the text of the rest. A link to an unsafe
href, such as javascript:, keeps its text and loses its link. Neither
renders raw HTML, so the saved JSON stays the source of truth.
The same value in the editor, then rendered:
Editor shows a skeleton the
height of the empty editor until then, and always on the server. Call preloadEditor()
on the hover or focus that leads to an editor to start loading it sooner.Field with a Field.Label. Outside one, label it
with aria-label or aria-labelledby; a <label for> can't name editable
content.textbox with aria-multiline. In a Field it's
named by Field.Label through aria-labelledby, unless Editor has its
own aria-label or aria-labelledby, and described by the helper or error
text through aria-describedby.aria-invalid or aria-required. A
read-only editor sets aria-readonly, and a disabled one aria-disabled.subtler editor has no focus ring: the caret shows
focus. While invalid it shows a danger edge at its start, beside
aria-invalid and the error text.toolbar named Formatting, with aria-controls set to
the editable area. It takes one Tab stop, and the arrow keys move between its
controls, scrolling the one with focus into view on a phone. Bold, italic, underline,
link, and the lists are toggles that report
aria-pressed for the text at the caret. Undo and Redo are disabled when
there's nothing to undo or redo, and stay focusable.toolbar, named Image actions or
Embed actions, with the same keys as Formatting. It shows only while the
node is selected and the editor has focus, and never while read-only or
disabled. Each action is one step to undo.status, and an upload error
shows in Field.ErrorText, or under the editor outside a Field.toolbar named Table actions shows above
it, or below it when above would cover Formatting, with the same keys as
Formatting, and never while read-only or disabled. Row and Column open
menus whose items are menuitems, and a menu returns focus to the cell
when it closes. Header row is a toggle that reports aria-pressed, and
Delete row and Delete column are disabled when they'd delete the last one.
Alt+F10 in a table moves focus to its first action, and Escape back to the
cell. In a table, Tab and Shift+Tab move between cells instead of nesting.
Tab in the last cell adds a row, and Shift+Tab in the first leaves the
editor, as it does from any text.aria-keyshortcuts.| Keys | Action |
|---|---|
Mod+B | Bold |
Mod+I | Italic |
Mod+U | Underline |
Mod+K | Add, change, or remove a link |
Mod+Shift+8 | Bulleted list |
Mod+Shift+7 | Numbered list |
Mod+Alt+0 | Paragraph, in the rich preset |
Mod+Alt+2 to Mod+Alt+4 | Heading 2 to 4, in the rich preset |
Mod+Shift+B | Quote, in the rich preset |
Enter | On a selected image, edit its alt text |
Tab | In a table, move to the next cell, adding a row after the last |
Shift+Tab | In a table, move to the cell before, or leave the editor from the first |
Mod+Z | Undo |
Mod+Shift+Z | Redo |
Shift+Enter | Line break |
Alt+F10 | Move focus to the toolbar's first enabled control, a selected image's or embed's first action, or in a table its first table action |
Escape | In a toolbar, move focus back to the text, where the caret was, or to the selected image or embed |
Mod is ⌘ on Apple devices and Ctrl elsewhere, and Alt is ⌥ on
Apple devices.
function useEditorContext(): { editor: Editor | null }
Returns the Tiptap Editor of the nearest Editor, for a custom control in
Editor.Toolbar or elsewhere among its children. editor is null for a moment after the editor's code
loads, until it mounts. It throws outside an Editor.
The nodes and marks the document may hold. A value in another preset is read into this one, keeping the text of what it can't hold.
Defaults to the value's preset, or 'basic'.
The document, for a controlled editor.
The starting document, for an uncontrolled editor.
Called with the whole document on each change.
Shown while the document is empty.
Takes no input and dims, as does a disabled `Field` around it.
Submits the value as JSON with a native form, under this name.
Body size, matching `Prose`'s sizes.
Defaults to 'md'.
The frame. `normal` and `subtle` match `Textarea`'s; `subtler` has none, for content edited in place on the page.
Defaults to 'normal'.
Uploads an image the person picks, pastes, or drops, and resolves to where it's served: https, or a path on this site. Without it the editor has no image control and ignores image files. A rejection's message shows in `Field.ErrorText`.
The providers the editor embeds from, matching what renders the value.
Defaults to defaultEmbedProviders.
Resolves a link no provider reads, such as a short link, to one it can. Called when one is entered in the Embed control; run it on your server.
Replaces the default parts, `<Editor.Toolbar />` then `<Editor.Content />`.
The editable area: a multiline textbox styled as `.prose` at the editor's size.
No additional props. It forwards all standard HTML attributes to the underlying element.
The preset's formatting controls, then `children`, at the editor's size. Alt+F10 in the content moves focus here, and Escape moves it back.
Classes for the toolbar's surface, its outermost element.
The surface behind the toolbar. `subtler` has none, for a toolbar inside another surface. The buttons stay at `subtler` on every one.
Defaults to 'normal'.
Inherited from ToolbarRootProps
The orientation of the toolbar.
Defaults to 'horizontal'.
If `true`, using keyboard navigation will wrap focus to the other end of the toolbar once the end is reached.
Defaults to true.