Changelog
The same history ships in the package as CHANGELOG.md. Entries describe
the API as it was at that release; the current names are in the
API reference.
0.13.1 — Unreleased
Added
- Horizontal rules.
---(also***or___) on a line of its own is a thematic break: Lexical'sHorizontalRuleNode, rendered as<hr class="zui-hr">; click selects it, Backspace removes it. Exports as---, except a rule that opens the document, written as***so a later---cannot pair with it into a frontmatter block on re-import. Typing---and then a space or Enter converts the paragraph. Toolbar button,useMarkdownEditor().insertHorizontalRule()andINSERT_HORIZONTAL_RULE_COMMAND;HORIZONTAL_RULE,HorizontalRuleNode,$createHorizontalRuleNode,$isHorizontalRuleNodeon./lexical;hr/hrSelectedinnodeClassNames; labeltoolbar.insertHorizontalRule. - Footnotes (GFM).
[^id]in the text is a superscript cue, numbered in first-reference order (a cue inside a note counts after the notes the body cites, as GitHub numbers them);[^id]: textlines are notes, gathered into one section that is always the last block (an<ol>; further lines of a note are four-space continuation lines). Notes stay in numbering order; a note nothing cites is kept, unnumbered. The toolbar button,insertFootnote()andINSERT_FOOTNOTE_COMMANDinsert a cue and an empty note and move the caret into the note; clicking a cue (or Enter on a selected cue) goes to its note, Escape or the note's↩goes back (GOTO_FOOTNOTE_DEFINITION_COMMAND,GOTO_FOOTNOTE_REF_COMMAND); Enter in a note adds a line, Backspace on an empty note removes it. HTML export and paste use GitHub's markup (<sup><a data-footnote-ref>,<section data-footnotes>). On./lexical:FootnoteRefNode,FootnoteDefinitionNode,FootnoteSectionNode(+$create*/$is*),FOOTNOTES(FOOTNOTE_DEFINITION,FOOTNOTE_CONTINUATION,FOOTNOTE_REF),$insertFootnote,$normalizeFootnotes,$computeFootnoteNumbers,$collectFootnoteRefs,$selectFootnoteDefinition,$selectFootnoteRef,$getFootnoteSection,$findFootnoteDefinition,FootnotesPlugin(mounted by the root). ClassesfootnoteRef,footnote,footnotes; labelstoolbar.insertFootnote,footnotes.reference,footnotes.backToReference.$replaceMarkdown(every import) places the section last and sorts it.
0.13.0 — 2026-09-07
Breaking
- Lexical and
@zuilib/tokensare now regular dependencies, so consumers install only@zuilib/text-editor; React and React DOM remain peers. Lexical is pinned to 0.35.0. The newtailwind.cssentry owns token styles and source registration, whilestyles.cssincludes tokens and editor chrome for hosts without Tailwind.
Added
- Small screens and touch.
.zui-text-editoris now an inline-size container (and so a stacking context), and the layout follows the editor's own width: below 40rem the toolbar scrolls sideways with a hidden scrollbar and scroll snapping instead of overflowing, the outline becomes a collapsible strip above the document, each table scrolls horizontally inside its own box with a 6rem column minimum, headings step down, and any drawing wider than its pane scales to fit (aspect preserved). Link, mention and comment panels stay inside the pane (useFloatingClamp, internal) and the mention list caps at50dvh. On coarse pointers buttons, tools and rail handles get 44px targets, the hover-only fold chevrons and table rail handles are visible, and a tap on a table rail pins the+/−handle where the finger was (a second tap acts). A finger on an idle drawing scrolls the page; the canvas captures touches once tapped (focused). Editable text renders at 16px or more on phones so iOS does not zoom on focus. Desktop geometry is unchanged.
0.12.0 — 2026-09-01
This entry carries everything since 0.11.1: the library-wide naming overhaul (clean break; only the
persisted drawing format migrates), the extension API and first-party plugins,
and the controlled-value rewrite. Features new in this release are described
under their final names.
Breaking
- Value triad:
onChange→onValueChange;foldable→collapsible;defaultBlockWidth→newBlockWidth;measure→maxTextWidth; mode'edit-md'→'edit-rich'. - Lexical containment: all
$-helpers, node classes and presets moved to the new@zuilib/text-editor/lexicalentry; the root barrel is Lexical-free. - Drawing data v3: shape
w/h→width/height(v2 documents migrate on load);parseDrawingData→deserializeDrawingData;BoxType→NodeShapeType; geometry internals un-exported. - All module files kebab-cased.
- Optional peer
@zuilib/tokensis^0.2.0(token contract 2.0.0: the stylesheet reads--danger,--shadow-2, …;--zui-drawing-*is declared here now, not in the tokens package). - Peer dependencies added:
@lexical/selection(block types, max length) and, for the plugins,@lexical/mark,@lexical/html,@lexical/clipboard.tailwindcss(^4) is declared as an optional peer: the document typography classes need a Tailwind host that@sources this package.
Added
- Extension API.
nodes(appended to the built-in node classes),transformers(an array goes before the built-ins; a function receives them and returns the whole list),nodeClassNames(merged over the built-in class map one level deep) andonEditorReadyonMarkdownEditor/MarkdownEditor.Root. All three lists are read on mount (the composer cannot change nodes afterwards). Children of the root are plugins and share the editor.createMarkdownEditor(preset)builds the component around anEditorPreset - New entry points:
@zuilib/text-editor/markdown(the editor without the drawing canvas: noDrawingNode, transformers, button or canvas code),@zuilib/text-editor/drawing(the canvas on its own: node, transformers,DrawingPlugin, helpers) and@zuilib/text-editor/lexical(everything Lexical-shaped). Insertion goes throughINSERT_DRAWING_COMMANDso the markdown hook has no drawing dependency - Exported the production lists on
./lexical:MARKDOWN_NODES,MARKDOWN_TRANSFORMERS,MARKDOWN_PRESET,DRAWING_PRESET,DRAWING_PRESET_NODES,DRAWING_PRESET_TRANSFORMERS,LISTS,resolveTransformers,shortcutTransformers,editorNodeClassNames,mergeNodeClassNames - Localisation. One
EditorLabelstype covers every chrome string (toolbar, outline, collapsible headings, table rails and shortcuts, layout presets, drawing canvas, placeholder, character count); pass a deep partial aslabels.DEFAULT_LABELS,resolveLabels,useLabelsexported. TheCollapsibleHeadingsLabels,TableRowShortcutsLabels,TableColumnShortcutsLabelstypes are exported and their plugins default to the editor's labels - Toolbar. A new block group (heading 1 to 3, bullet / numbered /
check list, quote) and a link button.
ToolbarItemsgainsblock;BlockButtonsandMarkdownEditor.BlockButtonsexported.useMarkdownEditor()gainsblockType/setBlockType,link/setLinkandhasSelection. Toolbar dividers arerole="separator" - Link bubble. The link button opens an edit bubble (URL input, apply,
remove) rather than inserting a placeholder
https://link: it needs a non-empty selection (disabled for a bare caret, so it cannot link the whole text node around the caret) and nothing is written to the document until a URL is applied. Escape, moving the caret or focus leaving the bubble abandons it without anyonValueChange.setLink(url)is a no-op for a collapsed caret outside a link;EDIT_LINK_COMMANDopens the bubble - Link safety.
LinkPluginvalidates URLs withisSafeUrl:javascript:,data:and other script-capable schemes are refused for typed, pasted and bubble-entered links (SAFE_LINK_PROTOCOLS). HTML pastes keep only the text of links whosehreffailsisSafeUrl, in the paste normaliser and, via a paste-timeLinkNodetransform, on Lexical's built-in paste path, sojavascript:URLs never reach the emitted markdown - Form fields.
id,name,aria-label,aria-labelledby,aria-describedby,aria-invalid,aria-requiredare forwarded to the editable surface (namerenders a hidden input with the markdown); a visible:focus-visiblering on the surface (--ring/--primary) replaces the removed outline;maxLengthrejects input past the plain-text limit,onCharacterCountChangereports the length,showCharacterCount/CharacterCountrender it - Controlled and uncontrolled editing.
valueis truly controlled: whenever the prop differs from the last markdown the editor emitted throughonValueChange, the document is replaced in one update (a single undo step) and the caret is kept at the same position when it still exists.value=""clears the editor. Hosts no longer need tokey-remount the editor to push content in. A remounted sync plugin (e.g. switchingedit-raw→edit-rich) always applies the host value, so an emptied raw document comes back empty. NewdefaultValueprop for the uncontrolled path;edit-rawwithout avaluekeeps its keystrokes (they were discarded) - Sync.
debounceMsdelaysonValueChange(the document is never delayed); a change still pending when the editor unmounts is delivered instead of dropped, so the last keystrokes before a dialog closes or a route changes reach the host. Updates that dirty no leaf and mark no element on purpose skip serialisation, and a serialisation identical to the last emitted value is not reported. A controlledvalueequal to one of the last 8 emitted values from the last second is treated as a stale echo and ignored instead of stomping what has been typed since (EMITTED_HISTORY,EMITTED_STALE_MS); older values are re-imported so a deliberate reset still wins - SSR.
mode="view"renders a non-editable fallback (the document's block text, nocontenteditable, no placeholder) on the server and until hydration; the Lexical surface then mounts with the document already in its state so the placeholder never flashes.readOnlyrenderscontenteditable="false"from the first paint - New
onErrorprop onMarkdownEditor/MarkdownEditor.Root(defaultconsole.error), forwarded to Lexical - Round-trip escaping. Image markdown percent-encodes whitespace and
parentheses in the source (on the node itself, so an uploaded
"my photo (1).png" stays an image) and backslash-escapes
]in alt and"in titles; mention markdown escapes]/\in names and percent-encodes%,)and whitespace in ids. Values that used to degrade to literal text now re-import - Accessibility. An
aria-invalidsurface keeps the 2px focus ring when focused (the danger line sits inside it); the character counter is no longer a live region read on every keystroke: a hidden status announces only when the document passesmaxLengthor returns within it (labels.limitExceeded/labels.limitRestored) - Performance.
DrawingNode.getData()memoizes parsing on the payload string;normalizeDrawingDatatakes anonWarncallback (default: one console warning per session) instead of warning on every parse; the toolbar keyboard hook only sweeps elements markeddata-toolbar-item(nested widgets keep their own focus handling) and measures the writing direction once, not per keydown - Code highlighting. TypeScript / TSX is its own grammar (
ts,tsx,mts,cts; type-level keywords, primitive types, utility types and type arguments);javascriptno longer answers to those aliases. 16 grammars. The registry loads the built-ins on first lookup, so no module has import-time side effects apart from the stylesheet andsuppress-global-highlighter, which stops the Prism instance pulled in by@lexical/codefrom re-highlighting the host page's own code blocks (both are listed insideEffects; keep them insideEffects-sensitive bundler configs) - Tokens. The stylesheet no longer carries a foreign brand colour: the
outline marker, table selection and rail accent fall back to
--primary/currentColor; every radius reads--radius-sm|md|lg|full; the code palette reads--zui-code-<type>-light/-dark, the canvas surface and grid--zui-drawing-surface-color/--zui-drawing-grid-color, all with the previous literals as fallbacks - Plugins. Four opt-in first-party plugins, each on its own entry
point and on the default entry (not on
./markdown):./mentions(MentionNode,MENTION,MentionsPlugin: typeahead listbox witharia-activedescendant, several triggers,[@name](mention:id)markdown),./comments(CommentsPluginon@lexical/mark: controlledcomments, floating add button,Mod+Shift+M, caret bubble,<!-- zui:comment id -->markers),./images(ImageNode,IMAGE,ImagesPlugin,ImageButton: paste / drop upload with a progress placeholder,accept/maxSize, alt-text editing,markdown) and./paste(PastePlugin,normalizePastedHtml: Word, Google Docs, Confluence and Outlook HTML reduced to structure) - Tests: the round-trip script is a
node --testfile that imports the exported production transformer list; jsdom and axe-core are devDependencies, so mounted tests fail instead of skipping - Build: dist is minified and cleaned on every build (removes the stale
dist/index.css)
0.11.1 — 2026-08-29
- Fix: mounting an editor no longer steals focus and scrolls to its first
code block. The initial markdown import and the one-off re-highlight of
existing code blocks now run with Lexical's
skip-dom-selectiontag, so their selection stays internal until the user clicks in
0.11.0 — 2026-08-27
- Depends on
@zuilib/tokens(optional peer) instead of@zuilib/core: the editor only ever consumed the design tokens, never a component. Load@zuilib/tokens/styles.css(ortokens.csswithout Tailwind) where you previously loaded@zuilib/core/styles.css; without it the editor falls back to its built-in defaults - Documented that a Tailwind host must
@sourcethis package for the document-content classes (headings, lists, code) to be generated
0.10.1 — 2026-08-26
- Table columns get the same rail as rows: a strip along the table's
top edge with a bar on the caret's column and a
+handle after the last column that snaps to column boundaries while hovered - Deleting moves onto the rails. The handle follows the pointer: near a
boundary it is a
+(insert; hover previews the line), over the middle of a row / column it is a−that removes it (hover tints what goes). The three row buttons added to the floating toolbar in 0.10.0 are gone - The layout toolbar now floats above the table, stacked over the column rail at the top-right corner
useMarkdownEditor():tableRowis nowtableCell({ row, column, rows, columns, hasHeader }); addsinsertTableColumn(position)anddeleteTableColumn(). Node helpers:$getTableCellPosition(was$getTableRowPosition),$insertTableColumnAt,$deleteTableColumnAt,$insertTableColumnNear,$deleteSelectedTableColumn,insertableColumnIndices,canDeleteColumn
0.10.0 — 2026-08-26
- Table rows. Put the caret in a table and a row rail appears along
its left edge: a bar marks the caret's row, and a
+handle rests under the last row (click to append). Move over the rail and the handle snaps to the nearest row boundary; hovering it draws the insertion line across the table. The floating table toolbar gains insert row above / below and delete row. Keyboard:⌘↩/Ctrl+Enterinserts below,⌘⇧↩above,⌘⇧⌫deletes the row, and Tab in the last cell appends a row instead of leaving the table - Rows follow GFM's shape: nothing is inserted above the header row, and deleting the header promotes the next row so the markdown keeps its divider; the last remaining row can't be deleted
useMarkdownEditor()addstableRow(index, column, count, hasHeader),insertTableRow(position)anddeleteTableRow(). Node-level helpers$getTableRowPosition,$insertTableRowAt,$deleteTableRowAt,$insertTableRowNear,$deleteSelectedTableRow,insertableRowIndicesandcanDeleteRoware exported for custom toolbars- Rail and toolbar use the theme tokens (
--primary,--popover,--border,--shadow-medium); motion respectsprefers-reduced-motion
0.9.0 — 2026-08-26
- Ink drawing style:
drawingStyle="ink"on the editor renders shapes as one seeded, pressure-varying pen stroke with misregistered fills and tapered connectors, and switches the canvas face to Recursive (casual axes) when the host loads it. Renderer-only: the drawing format is unchanged. Geometry helpers are exported (inkStroke,seedFrom, …)
0.8.0 — 2026-08-26
- Syntax highlighting for fenced code blocks, driven by the package's
own grammars instead of Prism: JavaScript/TypeScript, Kotlin, Java, C/C++,
Python, Go, Rust, JSON, YAML, shell, SQL, CSS, HTML/XML, diff and
markdown. Colors are CSS custom properties (
--zui-code-<type>on.zui-code) with light and dark defaults; override them to retheme.registerCodeLanguage(grammar)adds a language from a data-only grammar;tokenizeCodeandcodeTokenizerare exported for reuse - Untagged code blocks are highlighted as
plainand export as a barefence (previously they came back asjavascript).CODE_BLOCKis the exported transformer that does this - Unknown languages get the same line structure as known ones, so cursor movement and indentation behave identically
0.7.2 — 2026-08-25
- Canvas properties are one color setting (outline plus its matching
fill on boxes: plain (no outline), black, gray, red, green, blue, orange,
purple) shown inline in the tools row, only while the canvas is focused;
the header keeps a single row. Text on dark or borderless cards picks a
readable color automatically. The same names work as
colorin```diagramskeletons - Table settings and the canvas share three layout presets: compact (shrinks to the content; tables also get compact rows), comfortable (the text column; comfortable rows), full (the editor container; comfortable rows). They replace the separate width and density buttons; the markdown markers are unchanged
- The floating table settings use the theme tokens (
--popover,--border,--shadow-medium) instead of fixed light/dark colors
0.7.1 — 2026-08-25
- Fix: free
textshapes could not be typed into. The text tool's editor opened on pointerdown and was blurred by the same click; it now opens on pointerup like the card editors - Fix: selecting a shape no longer shifts the drawing. The properties row is always rendered (showing the defaults for the next shape when nothing is selected) with a fixed minimum height, so click-to-edit lands where the pointer is
- Fix: keyboard shortcuts keep working after committing text (the canvas
regains focus);
Home/Endwork inside the inline text editor
0.7.0 — 2026-08-25
Themed drawing canvas. The canvas chrome now follows the host theme instead of shipping its own look.
- Tool row and contextual property row are a flat header in the editor's own toolbar idiom (in flow, no floating panels over the drawing)
- Every color, radius, shadow and font of the canvas is a
--zui-drawing-*custom property defaulting to the core theme tokens (--primary,--foreground,--border,--popover,--radius,--shadow-medium, …), so light and dark mode come from the tokens; override them on.zui-drawing-canvasor an ancestor. See README, "Theming the canvas" - Selection frame, handles, marquee, bind highlight and connector label
plates are drawn in
currentColor/theme variables; the table selection tint uses--primarytoo, so the editor shares one accent --zui-drawing-dark-filterexposes the dark-mode inversion of the drawing area (noneopts out)
0.6.0 — 2026-08-25
Text measure and text block width. Additive: markdown, JSON, class
names and props keep their meaning; with no configuration the layout is
unchanged.
- Measure (opt in):
measure="48rem"onMarkdownEditor/MarkdownEditor.Root, or--zui-text-editor-measure: 48remin a stylesheet — the CSS custom property is the contract, the prop is sugar. Text blocks (paragraphs, headings, lists, quotes, code, frontmatter) are capped at the measure and centred in the content pane; the placeholder and fold chevrons follow the column. The pane itself is never capped and full-width blocks stop short of the outline sidebar BlockWidthgains'text':'full' | 'text' | 'content'.textaligns a table or drawing with the text column; without a measure it is visually identical tofull(resolved in CSS through the variable's fallback).full(default, omitted) andcontent(shrink to fit) are unchanged- Persistence: tables
<!-- width: text -->(combinable withdensity); drawings and```diagramskeletons"width":"text".DRAWING_DATA_JSON_SCHEMA,DRAWING_SKELETON_JSON_SCHEMAandDRAWING_FORMAT.mdupdated. Unknown width values are ignored (treated asfull) instead of being rejected, so documents from newer versions still open —parseTableSettingsMarkernow returns{}(rather thannull) for a marker whose only known key carries an unknown value, and the transformer consumes that line - Insertion defaults:
defaultBlockWidth={{ table?, drawing? }}on Root applies when the toolbar /useMarkdownEditor()inserts a new table or drawing. The chosen width is written to the markdown explicitly, evenfull, so the document reads the same in an app with other defaults; a width named in the source (<!-- width: full -->,"width":"full") is preserved on round trip - UI / headless: the floating table toolbar and the drawing toolbar
offer all three widths with distinct icons (
BLOCK_WIDTH_OPTIONS).useMarkdownEditor()gainsblockWidth/setBlockWidthfor the block containing the selection — the focused drawing or the table the caret is in;insertTable/insertDrawingaccept awidthoption.tableWidth/setTableWidthkeep working and are deprecated - CSS: the new layout lives in zero-specificity
:where()rules. The content pane exposes--zui-text-editor-gutterand--zui-text-editor-bleed; every top-level block reads--zui-text-editor-block-bleed, so one class rule overrides any block kind. New block classes (added next to the existing utility classes):.zui-paragraph,.zui-heading/.zui-heading-1…6,.zui-list/.zui-list-ordered/.zui-list-unordered,.zui-quote,.zui-code;.zui-drawing-canvasgains.is-text-width. Nothing is renamed - New exports:
BLOCK_WIDTHS,isBlockWidth,BlockWidthDefaults,TableSettingsOptions,$isTableWidthExplicit;$setTableSettingsandformatTableSettingsMarkertake an optional{ explicitWidth } - Adopting: set
measure(or the CSS variable) and delete any externalmax-width/margin-inlineoverrides on.zui-text-editor-content > *
0.5.0 — 2026-08-25
Diagram builder rewrite. The drawing canvas, its payload format and the authoring story for LLMs were rebuilt.
- Format v2 (breaking):
```drawingpayloads are now{"version":2,"canvasHeight":<px>,"canvasWidth"?:<px>,"width"?:"content","shapes":[...]}. No migration from version 1 and no legacy spellings: anything that is not a version 2 payload renders as an empty canvas.canvasWidthis optional: when set the drawing scales to fit narrower layouts (SVG viewBox), when absent the canvas is fluid.triangleandpentagonare removed.DRAWING_DATA_JSON_SCHEMAandDRAWING_FORMAT.mdrewritten for v2 - Shapes: boxes
rect,ellipse,diamond,note(sticky note with a folded corner, default fill#ffec99),cylinder(database),cloud,queue(horizontal cylinder),actor(stick figure with a name under it,textslot only); connectorsarrow,line; freetext. Palette gains purple (#7048e8/#d0bfff) - Bindings are objects:
{ id, fixedPoint?: [fx, fy], mode?: "orbit" | "inside" }.fixedPointis a ratio in the box's bounding box; omitted, the endpoint auto-aims from the box center at the other end.orbit(default) projects the endpoint onto the outline with a 6px gap,insidepins it exactly. Theside: "top" | "right" | "bottom" | "left"shorthand exists only in the```diagramskeleton - Router:
routing: "elbow"is auto-routed. It leaves the box perpendicular to the attach side, avoids every box on the canvas and minimises bends.elbow(0 to 1) remains as an override of the middle segment when the path is a simple three-segment Z;waypointsstill take precedence - Editor UX: multi-select (shift-click, marquee on empty canvas; move, delete and recolor together), redesigned toolbar with a "more shapes" popover, contextual property bar, smoother drag and selection visuals, Copy as Mermaid button
```diagramskeleton: a fenced block with a coordinate-free JSON skeleton (boxes,connectors,texts,direction, palettecolornames) that the editor auto-lays out and expands into a```drawingblock on import (one-way). New exportsexpandSkeleton,parseDrawingSkeleton,DRAWING_SKELETON_JSON_SCHEMA,DIAGRAMtransformer, typesDrawingSkeleton,SkeletonBox,SkeletonConnector- Mermaid export:
drawingToMermaid(data, { direction? })returns aflowchartwith one node per box (shape-specific node syntax), edges for connectors bound at both ends (labels as|text|),stylelines for coloured boxes and free text as%% note:comments - Internal: the drawing code moved from
src/components/intosrc/drawing/(types.ts,schema.ts,geometry.ts,mermaid.ts,shapes/,canvas/); shape geometry is data-driven so the router, bindings, skeleton expansion and exporters run without a DOM
0.4.0 — 2026-08-25
- Table density, Slab-style: the floating table toolbar (caret in a table) gains compact / comfortable (default) / spacious row padding and font size, next to the width toggle
- The settings comment above a GFM table now lists every non-default
setting, e.g.
<!-- width: content; density: compact -->; the 0.3.1<!-- width: content -->form is unchanged for width-only tables useMarkdownEditor()gainstableDensity/setTableDensity- New exports:
TableDensity,TableSettings,DEFAULT_TABLE_SETTINGS,$getTableSettings/$setTableSettings,$getTableDensity/$setTableDensity,parseTableSettingsMarker/formatTableSettingsMarker.TABLE_WIDTH_MARKERis deprecated in favour offormatTableSettingsMarker
0.3.1 — 2026-08-25
- Block width for tables and drawings: switch any table or drawing
between full width (spans the editor, the previous behaviour) and
content width (shrinks to fit its columns / shapes, left-aligned with
the text), Slab-style
- Tables: a floating toggle docks to the top-right of the table the caret
is in; content width sizes columns by their content. Persisted as a
<!-- width: content -->line directly above the GFM table (hidden by other markdown renderers) - Drawings: toggle at the right end of the canvas toolbar; content width
fits the rightmost shape and grows as shapes move. Persisted as a
top-level
"width":"content"field in the ```drawing payload (DrawingData.width,DRAWING_DATA_JSON_SCHEMAupdated; omitted when full, so existing payloads are unchanged) useMarkdownEditor()gainstableWidth/setTableWidthfor custom toolbars- New exports:
BlockWidth,$getTableWidth,$setTableWidth,$getSelectedTable,TABLE_WIDTH_MARKER
- Tables: a floating toggle docks to the top-right of the table the caret
is in; content width sizes columns by their content. Persisted as a
0.3.0 — 2026-08-23
- Custom toolbars
toolbarprop accepts a render function receiving the default groups (format,insert,history) so you can add your own buttons- Compound components:
MarkdownEditor.Root/.Content/.Toolbar/.Outlineplus.FormatButtons/.InsertButtons/.HistoryButtons/.ToolbarButton/.ToolbarDivider— place the toolbar anywhere underRoot useMarkdownEditor()headless hook:editor,activeFormats,toggleFormat,insertTable,insertDrawing,undo/redo
- Undo/redo buttons (
HistoryButtons; in the default toolbar via the function form only — default toolbar layout unchanged) - Internal:
LexicalComposeris now mounted inedit-rawmode too, so hooks keep working across mode switches
0.2.1 — 2026-08-09
DRAWING_FORMAT.md: authoritative spec of the ```drawing JSON payload, written for programmatic/LLM consumption; ships in the npm package- New export
DRAWING_DATA_JSON_SCHEMA(JSON Schema draft-07) for validating or structured-output-generating drawing payloads - New exports
parseDrawingData/serializeDrawingData - This changelog; README overhaul
0.2.0 — 2026-08-09
- Outline (
outlineprop, default off): docked, collapsible table-of-contents sidebar — live heading list, click-to-scroll, current section highlighted - Section folding (
foldableprop, default on): collapse everything under a heading via a gutter chevron; view-layer only (markdown untouched); auto-expands if the cursor enters a folded section
0.1.0 — 2026-08-09
- Tables: GFM pipe tables round-trip through markdown; artifact-style
rendering; in-place editing via Lexical
TablePlugin; new peer deps@lexical/tableand@lexical/utils - Toolbar (
toolbarprop, default on): inline formatting, insert table, insert drawing - Drawing canvas: Excalidraw-style diagrams embedded as ```drawing
fenced JSON blocks — see
DRAWING_FORMAT.md- Shapes: rectangle, ellipse, triangle, pentagon, arrow, line, text
- Boxes are cards with three attached text slots (label / content / footer) that move, resize, and wrap with the shape
- Connectors bind to boxes and track them; one-way or two-way arrows; midpoint labels; straight, elbow, or waypoint-routed paths
- Stroke/fill palettes; dark mode via canvas color inversion
- Round-trip test suite (
pnpm test)
0.0.2 and earlier — 2026-04-17 to 2026-07-05
- Markdown editor wrapping Lexical:
edit-md/edit-raw/viewmodes, markdown shortcuts, checklists, fenced code highlighting, YAML frontmatter block, controlledvalue/onChange