@latentic/live-markdown 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,36 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.0] - 2026-08-29
4
+
5
+ ### Added
6
+
7
+ - **A host can contribute extension modules.** `extensions?: readonly
8
+ MarkdownExtension[]` on the editor, merged in one pass with the built-ins and
9
+ host-last, so a module can deliberately override a construct and the merger
10
+ still warns when a node rule is redefined. Applies in source mode as well as
11
+ wysiwyg — a keymap is not a rendering concern. Read when an editor state is
12
+ built, so building the array inline cannot remount the editor.
13
+
14
+ Toolbar contributions now reach the host through the `toolbar` slot's
15
+ context; the merger always collected them and the component dropped them.
16
+
17
+ - `composeExtensions` is renamed **`mergeExtensions`**, with the old name kept
18
+ as a deprecated alias. "compose" read as the name of an app rather than as the
19
+ verb, which is the wrong signal for a package meant for many hosts.
20
+
21
+ ### Fixed
22
+
23
+ - **A checked task box reads as checked in dark.** The tick used
24
+ `--cds-icon-on-color` — white in every theme — against a fill of
25
+ `--cds-icon-primary`, which inverts. In dark that was #ffffff on #f4f4f4, a
26
+ contrast ratio of 1.10. It now uses `--cds-background`, the inverse of the
27
+ fill by construction.
28
+
29
+ - **The task box is sized to the text.** 1rem beside 1rem text is as tall as the
30
+ whole em box; it now derives from its own `font-size: 0.875em`, with box, tick
31
+ and baseline offset all in `em` of that, and sits on the x-height instead of
32
+ hanging off the baseline.
33
+
3
34
  ## [0.2.0] - 2026-08-28
4
35
 
5
36
  ### Added
package/README.md CHANGED
@@ -186,7 +186,8 @@ The `toolbar` render prop receives the live `EditorView`, so host buttons can dr
186
186
  | `value` | `string` | — | The markdown content (controlled) |
187
187
  | `onChange` | `(value: string, changes: DocumentTextChange[]) => void` | — | Called after edits, debounced |
188
188
  | `mode` | `"wysiwyg" \| "source"` | `"wysiwyg"` | Rich rendering or raw markdown |
189
- | `toolbar` | `(ctx: { view: EditorView }) => ReactNode` | — | Host-rendered toolbar, given the live editor view |
189
+ | `extensions` | `readonly MarkdownExtension[]` | — | Host-contributed extension modules, merged after the built-ins |
190
+ | `toolbar` | `(ctx: { view, contributions }) => ReactNode` | — | Host-rendered toolbar, given the live editor view and any toolbar items the extensions contributed |
190
191
  | `selectionActions` | `(ctx: { selection, dismiss }) => ReactNode` | — | Host-rendered actions for the current selection (e.g. a comment bubble) |
191
192
  | `linkTargets` | `ReadonlySet<string>` | — | Known file paths for wikilink resolution |
192
193
  | `onNavigateToLink` | `(path: string) => void` | — | Called on Cmd/Ctrl-click of an internal link |
@@ -209,13 +210,32 @@ Built-in extensions:
209
210
  - `tableExtension` — GFM tables with cell navigation
210
211
  - `wikilinkExtension` — `[[wikilink]]` rendering and navigation
211
212
 
213
+ The built-ins load themselves. To add your own, hand the editor your modules:
214
+
215
+ ```tsx
216
+ <CodeMirrorMarkdownEditor value={md} onChange={setMd} extensions={[myExtension]} />
217
+ ```
218
+
219
+ They are merged **after** the built-ins, so a module can deliberately override a
220
+ construct — the merger warns when a node rule is redefined, so an accidental
221
+ shadow is not silent. Modules apply in source mode as well as wysiwyg: a keymap
222
+ or a plain CM6 extension is not a markdown-rendering concern, and node rules
223
+ simply go unread while the painter is off.
224
+
225
+ The prop is read when an editor state is built, not on every render, so building
226
+ the array inline costs nothing and cannot remount the editor. Changing it
227
+ mid-session takes effect at the next rebuild.
228
+
229
+ To merge modules yourself — composing a preset, or feeding a CodeMirror view you
230
+ own — `mergeExtensions` is the same function the editor uses:
231
+
212
232
  ```tsx
213
- import { composeExtensions, mathExtension, tableExtension } from "@latentic/live-markdown";
233
+ import { mergeExtensions, mathExtension, tableExtension } from "@latentic/live-markdown";
214
234
 
215
- const composed = composeExtensions([mathExtension, tableExtension]);
216
- // composed.extensions — CM6 Extension[] (each extension's node rules ride
217
- // along via a facet, so this is all the editor needs)
218
- // composed.toolbar — merged ToolbarContribution[]
235
+ const merged = mergeExtensions([mathExtension, tableExtension]);
236
+ // merged.extensions — CM6 Extension[] (each module's node rules ride along via
237
+ // a facet, so this is all a view needs)
238
+ // merged.toolbar — merged ToolbarContribution[]
219
239
  ```
220
240
 
221
241
  ### A custom extension
@@ -223,7 +243,7 @@ const composed = composeExtensions([mathExtension, tableExtension]);
223
243
  A rule is one function per Lezer node name, returning how that node paints. The `mark` combinator covers the common "style this span" case. Rules merge last-wins, so an extension can introduce a construct from its own grammar or deliberately restyle a built-in one:
224
244
 
225
245
  ```tsx
226
- import { composeExtensions, mark, type MarkdownExtension } from "@latentic/live-markdown";
246
+ import { mark, type MarkdownExtension } from "@latentic/live-markdown";
227
247
 
228
248
  const fancyEmphasis: MarkdownExtension = {
229
249
  name: "fancy-emphasis",
@@ -234,7 +254,7 @@ const fancyEmphasis: MarkdownExtension = {
234
254
  },
235
255
  };
236
256
 
237
- const composed = composeExtensions([fancyEmphasis]);
257
+ <CodeMirrorMarkdownEditor value={md} onChange={setMd} extensions={[fancyEmphasis]} />
238
258
  ```
239
259
 
240
260
  `Paint` is a closed set — line class, span mark, hide, widget, or nothing — while node names grow, so styling a construct is always one rule in one place.
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
3
  import * as _codemirror_view from '@codemirror/view';
4
- import { Decoration, ViewPlugin, DecorationSet, ViewUpdate, EditorView, Command, KeyBinding } from '@codemirror/view';
4
+ import { Decoration, ViewPlugin, DecorationSet, ViewUpdate, KeyBinding, EditorView, Command } from '@codemirror/view';
5
5
  import * as _codemirror_state from '@codemirror/state';
6
6
  import { EditorState, Facet, Extension } from '@codemirror/state';
7
7
  import { syntaxTree } from '@codemirror/language';
@@ -384,6 +384,71 @@ type CommentOnExcerpt = (excerpt: {
384
384
  range: SourceRange;
385
385
  }, anchor: CommentAnchor) => void;
386
386
 
387
+ interface ToolbarContribution {
388
+ readonly id: string;
389
+ readonly group: "heading" | "format" | "block" | "insert" | string;
390
+ readonly label: string;
391
+ readonly icon: ReactNode;
392
+ readonly shortcut?: string;
393
+ readonly isActive?: (caretContext: CaretContextSnapshot) => boolean;
394
+ readonly run: (view: _codemirror_view.EditorView) => void;
395
+ }
396
+ interface CaretContextSnapshot {
397
+ readonly bold: boolean;
398
+ readonly italic: boolean;
399
+ readonly code: boolean;
400
+ readonly link: boolean;
401
+ readonly heading: 1 | 2 | 3 | 4 | 5 | 6 | 0;
402
+ readonly bulletList: boolean;
403
+ readonly orderedList: boolean;
404
+ readonly blockquote: boolean;
405
+ }
406
+ interface MarkdownExtension {
407
+ readonly name: string;
408
+ readonly version: string;
409
+ readonly description?: string;
410
+ /** Node rules for constructs this extension's grammar introduces (or
411
+ * deliberately overrides) — merged into the decoration painter via
412
+ * `nodeRulesFacet`, so an extension never edits the base table. */
413
+ readonly rules?: NodeRules;
414
+ readonly extensions?: Extension[];
415
+ readonly keymap?: KeyBinding[];
416
+ readonly toolbar?: ToolbarContribution[];
417
+ }
418
+
419
+ interface ComposedExtension {
420
+ extensions: Extension[];
421
+ toolbar: ToolbarContribution[];
422
+ }
423
+ /**
424
+ * Merge extension modules into one set of CodeMirror extensions plus the
425
+ * toolbar items they contribute. Later modules win: a host's rules are applied
426
+ * after the built-ins, so it can deliberately override a construct.
427
+ */
428
+ declare function mergeExtensions(modules: readonly MarkdownExtension[]): ComposedExtension;
429
+ /**
430
+ * @deprecated Renamed to {@link mergeExtensions}. "compose" reads as the app
431
+ * this package was extracted from rather than as the verb; the editor is meant
432
+ * for more hosts than that one. Kept as an alias so the rename is not breaking.
433
+ */
434
+ declare const composeExtensions: typeof mergeExtensions;
435
+
436
+ declare const highlightExtension: MarkdownExtension;
437
+
438
+ declare const footnoteExtension: MarkdownExtension;
439
+
440
+ declare const mathExtension: MarkdownExtension;
441
+
442
+ declare const mermaidExtension: MarkdownExtension;
443
+
444
+ /**
445
+ * A FACTORY, not a const: each composition gets its own editing surface (the
446
+ * one-active-edit state), so two mounted editors can never share a cell edit.
447
+ */
448
+ declare function tableExtension(): MarkdownExtension;
449
+
450
+ declare const wikilinkExtension: MarkdownExtension;
451
+
387
452
  type CodeMirrorEditorMode = "wysiwyg" | "source";
388
453
  /** A non-empty editor selection, in document byte offsets. */
389
454
  interface EditorSelectionSnapshot {
@@ -399,13 +464,27 @@ interface CodeMirrorMarkdownEditorProps {
399
464
  linkTargets?: ReadonlySet<string>;
400
465
  onNavigateToLink?: (path: string) => void;
401
466
  /**
402
- * Host-rendered toolbar. The editor owns the live `EditorView` and hands it to
403
- * the slot; the host builds whatever toolbar UI it wants (formatting buttons,
404
- * file actions, ) around it. Omit for a chromeless editor. Return a STABLE
405
- * element shape so the host's own memoisation can hold across keystrokes.
467
+ * Extension modules contributed by the host, merged AFTER the built-ins so a
468
+ * host can deliberately override a construct (the merger warns when a node
469
+ * rule is redefined).
470
+ *
471
+ * Read when an editor state is built — on mount, and on the swaps that
472
+ * rebuild one — not on every render, so passing a fresh array each time costs
473
+ * nothing and cannot remount the editor. Changing it mid-session therefore
474
+ * takes effect at the next rebuild.
475
+ *
476
+ * Applies in BOTH modes: a module's node rules simply go unread while the
477
+ * decoration painter is off, but its keymaps and plain CodeMirror extensions
478
+ * are not markdown-rendering concerns and should not be silently dropped in
479
+ * source mode.
406
480
  */
481
+ extensions?: readonly MarkdownExtension[];
482
+ /** Host-rendered toolbar. `contributions` carries the toolbar items the
483
+ * extension modules asked for; the host decides whether and how to render
484
+ * them alongside its own. */
407
485
  toolbar?: (ctx: {
408
486
  view: EditorView;
487
+ contributions: readonly ToolbarContribution[];
409
488
  }) => ReactNode;
410
489
  /**
411
490
  * Host-rendered actions for the current text selection (e.g. a comment / ask
@@ -456,7 +535,7 @@ interface CodeMirrorMarkdownEditorProps {
456
535
  */
457
536
  onFlushReady?: (flush: (() => void) | null) => void;
458
537
  }
459
- declare function CodeMirrorMarkdownEditorInner({ mode, onChange, value, workspaceRoot, filePath, linkTargets, onNavigateToLink, toolbar, selectionActions, resolveImageSrc, saveImageBytes, onOpenExternalUrl, onCommentOnExcerpt, renderClipboardHtml, onAfterContentSwap, onFlushReady, }: CodeMirrorMarkdownEditorProps): react.JSX.Element;
538
+ declare function CodeMirrorMarkdownEditorInner({ mode, onChange, value, workspaceRoot, filePath, linkTargets, onNavigateToLink, extensions: hostExtensions, toolbar, selectionActions, resolveImageSrc, saveImageBytes, onOpenExternalUrl, onCommentOnExcerpt, renderClipboardHtml, onAfterContentSwap, onFlushReady, }: CodeMirrorMarkdownEditorProps): react.JSX.Element;
460
539
  /**
461
540
  * Memoised export — same reason as the Tiptap editor. AppShell
462
541
  * re-renders on every chat-thread token; without memoisation each
@@ -504,60 +583,6 @@ declare const blockCommands: {
504
583
  */
505
584
  declare function onEditorUpdate(view: EditorView, fn: (update: ViewUpdate) => void): () => void;
506
585
 
507
- interface ToolbarContribution {
508
- readonly id: string;
509
- readonly group: "heading" | "format" | "block" | "insert" | string;
510
- readonly label: string;
511
- readonly icon: ReactNode;
512
- readonly shortcut?: string;
513
- readonly isActive?: (caretContext: CaretContextSnapshot) => boolean;
514
- readonly run: (view: _codemirror_view.EditorView) => void;
515
- }
516
- interface CaretContextSnapshot {
517
- readonly bold: boolean;
518
- readonly italic: boolean;
519
- readonly code: boolean;
520
- readonly link: boolean;
521
- readonly heading: 1 | 2 | 3 | 4 | 5 | 6 | 0;
522
- readonly bulletList: boolean;
523
- readonly orderedList: boolean;
524
- readonly blockquote: boolean;
525
- }
526
- interface MarkdownExtension {
527
- readonly name: string;
528
- readonly version: string;
529
- readonly description?: string;
530
- /** Node rules for constructs this extension's grammar introduces (or
531
- * deliberately overrides) — merged into the decoration painter via
532
- * `nodeRulesFacet`, so an extension never edits the base table. */
533
- readonly rules?: NodeRules;
534
- readonly extensions?: Extension[];
535
- readonly keymap?: KeyBinding[];
536
- readonly toolbar?: ToolbarContribution[];
537
- }
538
-
539
- interface ComposedExtension {
540
- extensions: Extension[];
541
- toolbar: ToolbarContribution[];
542
- }
543
- declare function composeExtensions(modules: readonly MarkdownExtension[]): ComposedExtension;
544
-
545
- declare const highlightExtension: MarkdownExtension;
546
-
547
- declare const footnoteExtension: MarkdownExtension;
548
-
549
- declare const mathExtension: MarkdownExtension;
550
-
551
- declare const mermaidExtension: MarkdownExtension;
552
-
553
- /**
554
- * A FACTORY, not a const: each composition gets its own editing surface (the
555
- * one-active-edit state), so two mounted editors can never share a cell edit.
556
- */
557
- declare function tableExtension(): MarkdownExtension;
558
-
559
- declare const wikilinkExtension: MarkdownExtension;
560
-
561
586
  /**
562
587
  * Mermaid rendering, decoupled from CodeMirror — shared by the editor widget,
563
588
  * the document export (which ships the SVG to the backend), and the clipboard
@@ -852,4 +877,4 @@ interface ResolveWorkspaceLinkOptions {
852
877
  }
853
878
  declare function resolveWorkspaceLink(href: string, options: ResolveWorkspaceLinkOptions): ResolvedWorkspaceLink | null;
854
879
 
855
- export { type CaretContextSnapshot, type CodeMirrorEditorMode, CodeMirrorMarkdownEditor, type CodeMirrorMarkdownEditorProps, type ComposedExtension, type DocumentTextChange, type EditorSelectionSnapshot, type Frontmatter, type FrontmatterValue, type HighlightedSpan, IMAGE_EDIT_ALT_EVENT, type ImageEditAltEventDetail, type ImageInsertOptions, type ImageInsertResult, type ImageResolveContext, type InlineScanMatch, type InlineScanRule, type MarkdownDocument, type MarkdownExtension, type MermaidRenderResult, type NodeContext, type NodeRule, type NodeRules, type OpenExternalUrl, type Paint, type ResolveImageSrc, type ResolveWorkspaceLinkOptions, type ResolvedWorkspaceLink, type SaveImageBytes, type SourceRange, type ToolbarContribution, blockCommands, buildImageMarkdown, composeExtensions, computeFileDir, defaultResolveImageSrc, dirnamePath, editorBaseTheme, escapeAttr, escapeText, extractImageBlobs, extractImageFiles, footnoteExtension, formatCommands, getCachedMermaidPng, hasUriScheme, headingLine, hideAlways, highlightExtension, highlightFenceSpans, imageInsertHandlers, inlineScanRulesFacet, insertImageBlob, isAbsolutePath, isMermaidFenceInfo, joinPath, line, mark, markdownDecorationsPlugin, mathExtension, mermaidExtension, nodeRulesFacet, onEditorUpdate, parseFrontmatter, parseWikilinkBody, pickImageFileForCaret, raw, renderMermaidToSvg, resolveWikilinkTarget, resolveWorkspaceLink, scanInline, serializeMarkdown, setFrontmatterField, showImageActionMenu, structural, tableExtension, treeAt, warmMermaidPng, wikilinkExtension };
880
+ export { type CaretContextSnapshot, type CodeMirrorEditorMode, CodeMirrorMarkdownEditor, type CodeMirrorMarkdownEditorProps, type ComposedExtension, type DocumentTextChange, type EditorSelectionSnapshot, type Frontmatter, type FrontmatterValue, type HighlightedSpan, IMAGE_EDIT_ALT_EVENT, type ImageEditAltEventDetail, type ImageInsertOptions, type ImageInsertResult, type ImageResolveContext, type InlineScanMatch, type InlineScanRule, type MarkdownDocument, type MarkdownExtension, type MermaidRenderResult, type NodeContext, type NodeRule, type NodeRules, type OpenExternalUrl, type Paint, type ResolveImageSrc, type ResolveWorkspaceLinkOptions, type ResolvedWorkspaceLink, type SaveImageBytes, type SourceRange, type ToolbarContribution, blockCommands, buildImageMarkdown, composeExtensions, computeFileDir, defaultResolveImageSrc, dirnamePath, editorBaseTheme, escapeAttr, escapeText, extractImageBlobs, extractImageFiles, footnoteExtension, formatCommands, getCachedMermaidPng, hasUriScheme, headingLine, hideAlways, highlightExtension, highlightFenceSpans, imageInsertHandlers, inlineScanRulesFacet, insertImageBlob, isAbsolutePath, isMermaidFenceInfo, joinPath, line, mark, markdownDecorationsPlugin, mathExtension, mergeExtensions, mermaidExtension, nodeRulesFacet, onEditorUpdate, parseFrontmatter, parseWikilinkBody, pickImageFileForCaret, raw, renderMermaidToSvg, resolveWikilinkTarget, resolveWorkspaceLink, scanInline, serializeMarkdown, setFrontmatterField, showImageActionMenu, structural, tableExtension, treeAt, warmMermaidPng, wikilinkExtension };
package/dist/index.js CHANGED
@@ -2615,23 +2615,30 @@ var editorBaseTheme = EditorView.theme({
2615
2615
  marginRight: "0.3em",
2616
2616
  fontWeight: "normal"
2617
2617
  },
2618
- // Task list checkbox — drawn as a Carbon checkbox, not the native control: a
2619
- // 1rem square that fills with the icon token and shows a white tick when
2620
- // checked. `appearance: none` is what replaces WebKit's small rounded default;
2621
- // the box then matches the design system rather than approximating it with an
2622
- // accent colour over the native shape.
2618
+ // Task list checkbox — drawn as a Carbon checkbox, not the native control.
2619
+ // `appearance: none` is what replaces WebKit's small rounded default; the box
2620
+ // then matches the design system rather than approximating it with an accent
2621
+ // colour over the native shape.
2622
+ //
2623
+ // Sized in `em`, not `rem`: a 1rem box beside 1rem text is as tall as the
2624
+ // whole em box, so it towered over lowercase letters, whose cap height is
2625
+ // nearer 0.7em. One knob — the checkbox's own `font-size` — drives the box,
2626
+ // the tick and the baseline offset, so the parts cannot drift apart, and the
2627
+ // control tracks the text if a list ever renders at another size.
2623
2628
  ".cm-task-checkbox": {
2624
2629
  appearance: "none",
2625
2630
  WebkitAppearance: "none",
2626
2631
  boxSizing: "border-box",
2627
2632
  position: "relative",
2628
- width: "1rem",
2629
- height: "1rem",
2630
- margin: "0 0.4em 0 0",
2633
+ fontSize: "0.875em",
2634
+ width: "1em",
2635
+ height: "1em",
2636
+ margin: "0 0.45em 0 0",
2631
2637
  cursor: "pointer",
2632
- verticalAlign: "-0.15em",
2633
- border: "0.0625rem solid var(--cds-icon-primary, #161616)",
2634
- borderRadius: "0.0625rem",
2638
+ // Centres the box on the x-height rather than hanging it off the baseline.
2639
+ verticalAlign: "-0.2em",
2640
+ border: "0.0625em solid var(--cds-icon-primary, #161616)",
2641
+ borderRadius: "0.0625em",
2635
2642
  background: "transparent"
2636
2643
  },
2637
2644
  ".cm-task-checkbox:checked": {
@@ -2639,15 +2646,20 @@ var editorBaseTheme = EditorView.theme({
2639
2646
  borderColor: "var(--cds-icon-primary, #161616)"
2640
2647
  },
2641
2648
  // The tick: an L (right + bottom border) rotated 45° into a check.
2649
+ //
2650
+ // Drawn in `--cds-background`, NOT `--cds-icon-on-color`. "On color" means an
2651
+ // icon on a branded fill and is white in every theme, while the box here is
2652
+ // filled with `--cds-icon-primary` — which inverts. In dark that was a white
2653
+ // tick on a near-white box: a checked item read as unchecked.
2642
2654
  ".cm-task-checkbox:checked::after": {
2643
2655
  content: "''",
2644
2656
  position: "absolute",
2645
- left: "0.3125rem",
2646
- top: "0.0625rem",
2647
- width: "0.25rem",
2648
- height: "0.5rem",
2649
- border: "solid var(--cds-icon-on-color, #ffffff)",
2650
- borderWidth: "0 0.125rem 0.125rem 0",
2657
+ left: "0.3125em",
2658
+ top: "0.0625em",
2659
+ width: "0.25em",
2660
+ height: "0.5em",
2661
+ border: "solid var(--cds-background, #ffffff)",
2662
+ borderWidth: "0 0.125em 0.125em 0",
2651
2663
  transform: "rotate(45deg)"
2652
2664
  },
2653
2665
  ".cm-task-checkbox:focus-visible": {
@@ -4350,7 +4362,7 @@ function pickImageFileForCaret(view) {
4350
4362
  };
4351
4363
  input.click();
4352
4364
  }
4353
- function composeExtensions(modules) {
4365
+ function mergeExtensions(modules) {
4354
4366
  const extensions = [];
4355
4367
  const toolbar = [];
4356
4368
  const allKeyBindings = [];
@@ -4374,6 +4386,7 @@ function composeExtensions(modules) {
4374
4386
  if (allKeyBindings.length) extensions.push(keymap.of(allKeyBindings));
4375
4387
  return { extensions, toolbar };
4376
4388
  }
4389
+ var composeExtensions = mergeExtensions;
4377
4390
  var HIGHLIGHT_RE = /==([^=\n]+?)==/g;
4378
4391
  var HIDE2 = Decoration.replace({});
4379
4392
  var highlightMark = Decoration.mark({ class: "cm-highlight" });
@@ -5576,6 +5589,7 @@ function CodeMirrorMarkdownEditorInner({
5576
5589
  filePath,
5577
5590
  linkTargets,
5578
5591
  onNavigateToLink,
5592
+ extensions: hostExtensions,
5579
5593
  toolbar,
5580
5594
  selectionActions,
5581
5595
  resolveImageSrc,
@@ -5624,6 +5638,9 @@ function CodeMirrorMarkdownEditorInner({
5624
5638
  const syncedHashRef = useRef(/* @__PURE__ */ new Map());
5625
5639
  const currentFileRef = useRef(filePath);
5626
5640
  const modeInitializedRef = useRef(decorationsEnabled);
5641
+ const hostExtensionsRef = useRef(hostExtensions);
5642
+ hostExtensionsRef.current = hostExtensions;
5643
+ const toolbarContributionsRef = useRef([]);
5627
5644
  function buildExtensions() {
5628
5645
  const base = [
5629
5646
  history(),
@@ -5783,16 +5800,20 @@ function CodeMirrorMarkdownEditorInner({
5783
5800
  ];
5784
5801
  if (decorationsEnabled) {
5785
5802
  base.push(markdownDecorationsPlugin);
5786
- const composed = composeExtensions([
5803
+ }
5804
+ const merged = mergeExtensions([
5805
+ ...decorationsEnabled ? [
5787
5806
  wikilinkExtension,
5788
5807
  highlightExtension,
5789
5808
  footnoteExtension,
5790
5809
  mathExtension,
5791
5810
  mermaidExtension,
5792
5811
  tableExtension()
5793
- ]);
5794
- base.push(...composed.extensions);
5795
- }
5812
+ ] : [],
5813
+ ...hostExtensionsRef.current ?? []
5814
+ ]);
5815
+ base.push(...merged.extensions);
5816
+ toolbarContributionsRef.current = merged.toolbar;
5796
5817
  return base;
5797
5818
  }
5798
5819
  const buildExtensionsRef = useRef(buildExtensions);
@@ -5950,7 +5971,7 @@ function CodeMirrorMarkdownEditorInner({
5950
5971
  view.dispatch({ selection: EditorSelection.cursor(head) });
5951
5972
  }, []);
5952
5973
  const toolbarNode = useMemo(
5953
- () => viewForToolbar && toolbar ? toolbar({ view: viewForToolbar }) : null,
5974
+ () => viewForToolbar && toolbar ? toolbar({ view: viewForToolbar, contributions: toolbarContributionsRef.current }) : null,
5954
5975
  [viewForToolbar, toolbar]
5955
5976
  );
5956
5977
  const selectionNode = useMemo(
@@ -5965,6 +5986,6 @@ function CodeMirrorMarkdownEditorInner({
5965
5986
  }
5966
5987
  var CodeMirrorMarkdownEditor = memo(CodeMirrorMarkdownEditorInner);
5967
5988
 
5968
- export { CodeMirrorMarkdownEditor, IMAGE_EDIT_ALT_EVENT, blockCommands, buildImageMarkdown, composeExtensions, computeFileDir, defaultResolveImageSrc, dirnamePath, editorBaseTheme, escapeAttr, escapeText, extractImageBlobs, extractImageFiles, footnoteExtension, formatCommands, getCachedMermaidPng, hasUriScheme, headingLine, hideAlways, highlightExtension, highlightFenceSpans, imageInsertHandlers, inlineScanRulesFacet, insertImageBlob, isAbsolutePath, isMermaidFenceInfo, joinPath, line, mark, markdownDecorationsPlugin, mathExtension, mermaidExtension, nodeRulesFacet, onEditorUpdate, parseFrontmatter, parseWikilinkBody, pickImageFileForCaret, raw, renderMermaidToSvg, resolveWikilinkTarget, resolveWorkspaceLink, scanInline, serializeMarkdown, setFrontmatterField, showImageActionMenu, structural, tableExtension, treeAt, warmMermaidPng, wikilinkExtension };
5989
+ export { CodeMirrorMarkdownEditor, IMAGE_EDIT_ALT_EVENT, blockCommands, buildImageMarkdown, composeExtensions, computeFileDir, defaultResolveImageSrc, dirnamePath, editorBaseTheme, escapeAttr, escapeText, extractImageBlobs, extractImageFiles, footnoteExtension, formatCommands, getCachedMermaidPng, hasUriScheme, headingLine, hideAlways, highlightExtension, highlightFenceSpans, imageInsertHandlers, inlineScanRulesFacet, insertImageBlob, isAbsolutePath, isMermaidFenceInfo, joinPath, line, mark, markdownDecorationsPlugin, mathExtension, mergeExtensions, mermaidExtension, nodeRulesFacet, onEditorUpdate, parseFrontmatter, parseWikilinkBody, pickImageFileForCaret, raw, renderMermaidToSvg, resolveWikilinkTarget, resolveWorkspaceLink, scanInline, serializeMarkdown, setFrontmatterField, showImageActionMenu, structural, tableExtension, treeAt, warmMermaidPng, wikilinkExtension };
5969
5990
  //# sourceMappingURL=index.js.map
5970
5991
  //# sourceMappingURL=index.js.map