Skip to main content

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's HorizontalRuleNode, 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() and INSERT_HORIZONTAL_RULE_COMMAND; HORIZONTAL_RULE, HorizontalRuleNode, $createHorizontalRuleNode, $isHorizontalRuleNode on ./lexical; hr / hrSelected in nodeClassNames; label toolbar.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]: text lines 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() and INSERT_FOOTNOTE_COMMAND insert 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). Classes footnoteRef, footnote, footnotes; labels toolbar.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/tokens are now regular dependencies, so consumers install only @zuilib/text-editor; React and React DOM remain peers. Lexical is pinned to 0.35.0. The new tailwind.css entry owns token styles and source registration, while styles.css includes tokens and editor chrome for hosts without Tailwind.

Added​

  • Small screens and touch. .zui-text-editor is 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 at 50dvh. 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/lexical entry; 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/tokens is ^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) and onEditorReady on MarkdownEditor / 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 an EditorPreset
  • New entry points: @zuilib/text-editor/markdown (the editor without the drawing canvas: no DrawingNode, 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 through INSERT_DRAWING_COMMAND so 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 EditorLabels type covers every chrome string (toolbar, outline, collapsible headings, table rails and shortcuts, layout presets, drawing canvas, placeholder, character count); pass a deep partial as labels. DEFAULT_LABELS, resolveLabels, useLabels exported. The CollapsibleHeadingsLabels, TableRowShortcutsLabels, TableColumnShortcutsLabels types 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. ToolbarItems gains block; BlockButtons and MarkdownEditor.BlockButtons exported. useMarkdownEditor() gains blockType / setBlockType, link / setLink and hasSelection. Toolbar dividers are role="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 any onValueChange. setLink(url) is a no-op for a collapsed caret outside a link; EDIT_LINK_COMMAND opens the bubble
  • Link safety. LinkPlugin validates URLs with isSafeUrl: 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 whose href fails isSafeUrl, in the paste normaliser and, via a paste-time LinkNode transform, on Lexical's built-in paste path, so javascript: URLs never reach the emitted markdown
  • Form fields. id, name, aria-label, aria-labelledby, aria-describedby, aria-invalid, aria-required are forwarded to the editable surface (name renders a hidden input with the markdown); a visible :focus-visible ring on the surface (--ring / --primary) replaces the removed outline; maxLength rejects input past the plain-text limit, onCharacterCountChange reports the length, showCharacterCount / CharacterCount render it
  • Controlled and uncontrolled editing. value is truly controlled: whenever the prop differs from the last markdown the editor emitted through onValueChange, 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 to key-remount the editor to push content in. A remounted sync plugin (e.g. switching edit-raw → edit-rich) always applies the host value, so an emptied raw document comes back empty. New defaultValue prop for the uncontrolled path; edit-raw without a value keeps its keystrokes (they were discarded)
  • Sync. debounceMs delays onValueChange (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 controlled value equal 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, no contenteditable, 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. readOnly renders contenteditable="false" from the first paint
  • New onError prop on MarkdownEditor / MarkdownEditor.Root (default console.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-invalid surface 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 passes maxLength or returns within it (labels.limitExceeded / labels.limitRestored)
  • Performance. DrawingNode.getData() memoizes parsing on the payload string; normalizeDrawingData takes an onWarn callback (default: one console warning per session) instead of warning on every parse; the toolbar keyboard hook only sweeps elements marked data-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); javascript no 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 and suppress-global-highlighter, which stops the Prism instance pulled in by @lexical/code from re-highlighting the host page's own code blocks (both are listed in sideEffects; keep them in sideEffects-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 with aria-activedescendant, several triggers, [@name](mention:id) markdown), ./comments (CommentsPlugin on @lexical/mark: controlled comments, 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, ![alt](src) markdown) and ./paste (PastePlugin, normalizePastedHtml: Word, Google Docs, Confluence and Outlook HTML reduced to structure)
  • Tests: the round-trip script is a node --test file 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-selection tag, 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 (or tokens.css without 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 @source this 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(): tableRow is now tableCell ({ row, column, rows, columns, hasHeader }); adds insertTableColumn(position) and deleteTableColumn(). 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+Enter inserts 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() adds tableRow (index, column, count, hasHeader), insertTableRow(position) and deleteTableRow(). Node-level helpers $getTableRowPosition, $insertTableRowAt, $deleteTableRowAt, $insertTableRowNear, $deleteSelectedTableRow, insertableRowIndices and canDeleteRow are exported for custom toolbars
  • Rail and toolbar use the theme tokens (--primary, --popover, --border, --shadow-medium); motion respects prefers-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; tokenizeCode and codeTokenizer are exported for reuse
  • Untagged code blocks are highlighted as plain and export as a bare fence (previously they came back asjavascript). CODE_BLOCK is 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 color in ```diagram skeletons
  • 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 text shapes 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/End work 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-canvas or 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 --primary too, so the editor shares one accent
  • --zui-drawing-dark-filter exposes the dark-mode inversion of the drawing area (none opts 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" on MarkdownEditor / MarkdownEditor.Root, or --zui-text-editor-measure: 48rem in 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
  • BlockWidth gains 'text': 'full' | 'text' | 'content'. text aligns a table or drawing with the text column; without a measure it is visually identical to full (resolved in CSS through the variable's fallback). full (default, omitted) and content (shrink to fit) are unchanged
  • Persistence: tables <!-- width: text --> (combinable with density); drawings and ```diagram skeletons "width":"text". DRAWING_DATA_JSON_SCHEMA, DRAWING_SKELETON_JSON_SCHEMA and DRAWING_FORMAT.md updated. Unknown width values are ignored (treated as full) instead of being rejected, so documents from newer versions still open — parseTableSettingsMarker now returns {} (rather than null) 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, even full, 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() gains blockWidth / setBlockWidth for the block containing the selection — the focused drawing or the table the caret is in; insertTable / insertDrawing accept a width option. tableWidth / setTableWidth keep working and are deprecated
  • CSS: the new layout lives in zero-specificity :where() rules. The content pane exposes --zui-text-editor-gutter and --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-canvas gains .is-text-width. Nothing is renamed
  • New exports: BLOCK_WIDTHS, isBlockWidth, BlockWidthDefaults, TableSettingsOptions, $isTableWidthExplicit; $setTableSettings and formatTableSettingsMarker take an optional { explicitWidth }
  • Adopting: set measure (or the CSS variable) and delete any external max-width / margin-inline overrides 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): ```drawing payloads 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. canvasWidth is optional: when set the drawing scales to fit narrower layouts (SVG viewBox), when absent the canvas is fluid. triangle and pentagon are removed. DRAWING_DATA_JSON_SCHEMA and DRAWING_FORMAT.md rewritten 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, text slot only); connectors arrow, line; free text. Palette gains purple (#7048e8 / #d0bfff)
  • Bindings are objects: { id, fixedPoint?: [fx, fy], mode?: "orbit" | "inside" }. fixedPoint is 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, inside pins it exactly. The side: "top" | "right" | "bottom" | "left" shorthand exists only in the ```diagram skeleton
  • 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; waypoints still 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
  • ```diagram skeleton: a fenced block with a coordinate-free JSON skeleton (boxes, connectors, texts, direction, palette color names) that the editor auto-lays out and expands into a ```drawing block on import (one-way). New exports expandSkeleton, parseDrawingSkeleton, DRAWING_SKELETON_JSON_SCHEMA, DIAGRAM transformer, types DrawingSkeleton, SkeletonBox, SkeletonConnector
  • Mermaid export: drawingToMermaid(data, { direction? }) returns a flowchart with one node per box (shape-specific node syntax), edges for connectors bound at both ends (labels as |text|), style lines for coloured boxes and free text as %% note: comments
  • Internal: the drawing code moved from src/components/ into src/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() gains tableDensity / setTableDensity
  • New exports: TableDensity, TableSettings, DEFAULT_TABLE_SETTINGS, $getTableSettings / $setTableSettings, $getTableDensity / $setTableDensity, parseTableSettingsMarker / formatTableSettingsMarker. TABLE_WIDTH_MARKER is deprecated in favour of formatTableSettingsMarker

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_SCHEMA updated; omitted when full, so existing payloads are unchanged)
    • useMarkdownEditor() gains tableWidth / setTableWidth for custom toolbars
    • New exports: BlockWidth, $getTableWidth, $setTableWidth, $getSelectedTable, TABLE_WIDTH_MARKER

0.3.0 — 2026-08-23​

  • Custom toolbars
    • toolbar prop accepts a render function receiving the default groups (format, insert, history) so you can add your own buttons
    • Compound components: MarkdownEditor.Root / .Content / .Toolbar / .Outline plus .FormatButtons / .InsertButtons / .HistoryButtons / .ToolbarButton / .ToolbarDivider — place the toolbar anywhere under Root
    • 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: LexicalComposer is now mounted in edit-raw mode 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 (outline prop, default off): docked, collapsible table-of-contents sidebar — live heading list, click-to-scroll, current section highlighted
  • Section folding (foldable prop, 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/table and @lexical/utils
  • Toolbar (toolbar prop, 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 / view modes, markdown shortcuts, checklists, fenced code highlighting, YAML frontmatter block, controlled value/onChange