@latentic/live-markdown 0.4.0 → 0.4.2

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,40 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.4.2] - 2026-09-10
4
+
5
+ ### Fixed
6
+
7
+ - **Selected text was the least readable text on screen in a dark host.**
8
+ CodeMirror's base theme carries
9
+ `&light.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground`,
10
+ far more specific than the plain `.cm-selectionBackground` this package set —
11
+ and because the editor is themed through CSS custom properties rather than
12
+ registered as `{ dark: true }`, CodeMirror believes it is always `&light` and
13
+ painted its own `#d7d4f0` over the host's `--cds-highlight`. Light text on a
14
+ light band, exactly where a reader had just selected something.
15
+
16
+ The same trap the caret hit, in the one place where getting it wrong hides
17
+ what the reader is looking at. Both focused and unfocused states are now
18
+ stated at CodeMirror's own specificity, and pinned by a browser test that
19
+ reads the computed colour back.
20
+
21
+ ## [0.4.1] - 2026-09-10
22
+
23
+ ### Changed
24
+
25
+ - **KaTeX is fetched on the first expression, not at every launch.** It is the
26
+ largest single dependency this package pulls into a host's bundle — 293KB
27
+ measured in Compose, 13% of its eager chunk — and most documents contain no
28
+ `$` at all. Compose's eager chunk goes 2,061KB → 1,793KB and its `entry` mark
29
+ 167ms → 150ms.
30
+
31
+ The cost is one frame on the first expression in a session; every one after is
32
+ synchronous. Until KaTeX lands the source stands in, which is also the existing
33
+ failure mode, so a document that never loads it still shows what was written.
34
+
35
+ - `mathTypesettingSettled()` is exported for anything that MEASURES rendered
36
+ math rather than displaying it — a document export, a test.
37
+
3
38
  ## [0.4.0] - 2026-09-10
4
39
 
5
40
  ### Fixed
@@ -1,5 +1,7 @@
1
1
  import { WidgetType, EditorView } from '@codemirror/view';
2
2
 
3
+ /** Resolves once every math expression asked for so far has been typeset. */
4
+ declare function mathTypesettingSettled(): Promise<void>;
3
5
  /** Typeset `tex` into `el`, falling back to the source on a KaTeX failure so a
4
6
  * malformed expression shows what the author wrote rather than nothing. */
5
7
  declare function renderMathInto(el: HTMLElement, tex: string, displayMode: boolean): void;
@@ -12,4 +14,4 @@ declare class MathWidget extends WidgetType {
12
14
  ignoreEvent(): boolean;
13
15
  }
14
16
 
15
- export { MathWidget, renderMathInto };
17
+ export { MathWidget, mathTypesettingSettled, renderMathInto };
@@ -1,15 +1,36 @@
1
1
  import {
2
2
  __publicField
3
3
  } from "../../chunk-V6TY7KAL.js";
4
- import katex from "katex";
5
4
  import { WidgetType } from "@codemirror/view";
6
- function renderMathInto(el, tex, displayMode) {
5
+ let katex = null;
6
+ let loadingKatex = null;
7
+ function loadKatex() {
8
+ loadingKatex ?? (loadingKatex = import("katex").then((module) => {
9
+ katex = module.default;
10
+ }));
11
+ return loadingKatex;
12
+ }
13
+ const pendingTypesets = /* @__PURE__ */ new Set();
14
+ function mathTypesettingSettled() {
15
+ return Promise.all([...pendingTypesets]).then(() => void 0);
16
+ }
17
+ function typeset(el, tex, displayMode) {
7
18
  try {
8
- katex.render(tex, el, { displayMode, throwOnError: false, output: "html" });
19
+ katex?.render(tex, el, { displayMode, throwOnError: false, output: "html" });
9
20
  } catch {
10
21
  el.textContent = tex;
11
22
  }
12
23
  }
24
+ function renderMathInto(el, tex, displayMode) {
25
+ if (katex) {
26
+ typeset(el, tex, displayMode);
27
+ return;
28
+ }
29
+ el.textContent = tex;
30
+ const settled = loadKatex().then(() => typeset(el, tex, displayMode));
31
+ pendingTypesets.add(settled);
32
+ void settled.finally(() => pendingTypesets.delete(settled));
33
+ }
13
34
  class MathWidget extends WidgetType {
14
35
  constructor(tex, displayMode) {
15
36
  super();
@@ -31,6 +52,7 @@ class MathWidget extends WidgetType {
31
52
  }
32
53
  export {
33
54
  MathWidget,
55
+ mathTypesettingSettled,
34
56
  renderMathInto
35
57
  };
36
58
  //# sourceMappingURL=mathWidget.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/codemirror/math/mathWidget.ts"],"sourcesContent":["import katex from \"katex\";\nimport { EditorView, WidgetType } from \"@codemirror/view\";\n\n/** Typeset `tex` into `el`, falling back to the source on a KaTeX failure so a\n * malformed expression shows what the author wrote rather than nothing. */\nexport function renderMathInto(el: HTMLElement, tex: string, displayMode: boolean): void {\n try {\n katex.render(tex, el, { displayMode, throwOnError: false, output: \"html\" });\n } catch {\n el.textContent = tex;\n }\n}\n\nexport class MathWidget extends WidgetType {\n constructor(\n readonly tex: string,\n readonly displayMode: boolean,\n ) {\n super();\n }\n\n override eq(other: MathWidget): boolean {\n return other.tex === this.tex && other.displayMode === this.displayMode;\n }\n\n override toDOM(_view: EditorView): HTMLElement {\n const span = document.createElement(this.displayMode ? \"div\" : \"span\");\n span.className = this.displayMode ? \"cm-math-block\" : \"cm-math-inline\";\n renderMathInto(span, this.tex, this.displayMode);\n return span;\n }\n\n override ignoreEvent(): boolean {\n return false;\n }\n}\n"],"mappings":";;;AAAA,OAAO,WAAW;AAClB,SAAqB,kBAAkB;AAIhC,SAAS,eAAe,IAAiB,KAAa,aAA4B;AACvF,MAAI;AACF,UAAM,OAAO,KAAK,IAAI,EAAE,aAAa,cAAc,OAAO,QAAQ,OAAO,CAAC;AAAA,EAC5E,QAAQ;AACN,OAAG,cAAc;AAAA,EACnB;AACF;AAEO,MAAM,mBAAmB,WAAW;AAAA,EACzC,YACW,KACA,aACT;AACA,UAAM;AAHG;AACA;AAAA,EAGX;AAAA,EAES,GAAG,OAA4B;AACtC,WAAO,MAAM,QAAQ,KAAK,OAAO,MAAM,gBAAgB,KAAK;AAAA,EAC9D;AAAA,EAES,MAAM,OAAgC;AAC7C,UAAM,OAAO,SAAS,cAAc,KAAK,cAAc,QAAQ,MAAM;AACrE,SAAK,YAAY,KAAK,cAAc,kBAAkB;AACtD,mBAAe,MAAM,KAAK,KAAK,KAAK,WAAW;AAC/C,WAAO;AAAA,EACT;AAAA,EAES,cAAuB;AAC9B,WAAO;AAAA,EACT;AACF;","names":[]}
1
+ {"version":3,"sources":["../../../src/codemirror/math/mathWidget.ts"],"sourcesContent":["import { EditorView, WidgetType } from \"@codemirror/view\";\n\n/**\n * KaTeX, fetched the first time a document actually contains math.\n *\n * It is the largest single dependency this package pulls — 293KB of a host's\n * launch bundle, measured in Compose — and most documents have no `$` in them\n * at all. A static import spends it on every launch regardless.\n *\n * The cost of deferring is one frame on the first expression in a session: the\n * element is in the DOM already, so it is typeset in place when KaTeX lands,\n * and every expression after that is synchronous.\n */\nlet katex: typeof import(\"katex\").default | null = null;\nlet loadingKatex: Promise<void> | null = null;\n\nfunction loadKatex(): Promise<void> {\n loadingKatex ??= import(\"katex\").then((module) => {\n katex = module.default;\n });\n return loadingKatex;\n}\n\n/** Typesets still waiting on KaTeX. Anything that measures rendered output —\n * a test, or a host exporting a document — has to wait for these. */\nconst pendingTypesets = new Set<Promise<void>>();\n\n/** Resolves once every math expression asked for so far has been typeset. */\nexport function mathTypesettingSettled(): Promise<void> {\n return Promise.all([...pendingTypesets]).then(() => undefined);\n}\n\nfunction typeset(el: HTMLElement, tex: string, displayMode: boolean): void {\n try {\n katex?.render(tex, el, { displayMode, throwOnError: false, output: \"html\" });\n } catch {\n el.textContent = tex;\n }\n}\n\n/** Typeset `tex` into `el`, falling back to the source on a KaTeX failure so a\n * malformed expression shows what the author wrote rather than nothing. */\nexport function renderMathInto(el: HTMLElement, tex: string, displayMode: boolean): void {\n if (katex) {\n typeset(el, tex, displayMode);\n return;\n }\n // Until it arrives, the source stands in — which is also the failure mode, so\n // a document that never loads KaTeX still shows what the author wrote.\n el.textContent = tex;\n const settled = loadKatex().then(() => typeset(el, tex, displayMode));\n pendingTypesets.add(settled);\n void settled.finally(() => pendingTypesets.delete(settled));\n}\n\nexport class MathWidget extends WidgetType {\n constructor(\n readonly tex: string,\n readonly displayMode: boolean,\n ) {\n super();\n }\n\n override eq(other: MathWidget): boolean {\n return other.tex === this.tex && other.displayMode === this.displayMode;\n }\n\n override toDOM(_view: EditorView): HTMLElement {\n const span = document.createElement(this.displayMode ? \"div\" : \"span\");\n span.className = this.displayMode ? \"cm-math-block\" : \"cm-math-inline\";\n renderMathInto(span, this.tex, this.displayMode);\n return span;\n }\n\n override ignoreEvent(): boolean {\n return false;\n }\n}\n"],"mappings":";;;AAAA,SAAqB,kBAAkB;AAavC,IAAI,QAA+C;AACnD,IAAI,eAAqC;AAEzC,SAAS,YAA2B;AAClC,kCAAiB,OAAO,OAAO,EAAE,KAAK,CAAC,WAAW;AAChD,YAAQ,OAAO;AAAA,EACjB,CAAC;AACD,SAAO;AACT;AAIA,MAAM,kBAAkB,oBAAI,IAAmB;AAGxC,SAAS,yBAAwC;AACtD,SAAO,QAAQ,IAAI,CAAC,GAAG,eAAe,CAAC,EAAE,KAAK,MAAM,MAAS;AAC/D;AAEA,SAAS,QAAQ,IAAiB,KAAa,aAA4B;AACzE,MAAI;AACF,WAAO,OAAO,KAAK,IAAI,EAAE,aAAa,cAAc,OAAO,QAAQ,OAAO,CAAC;AAAA,EAC7E,QAAQ;AACN,OAAG,cAAc;AAAA,EACnB;AACF;AAIO,SAAS,eAAe,IAAiB,KAAa,aAA4B;AACvF,MAAI,OAAO;AACT,YAAQ,IAAI,KAAK,WAAW;AAC5B;AAAA,EACF;AAGA,KAAG,cAAc;AACjB,QAAM,UAAU,UAAU,EAAE,KAAK,MAAM,QAAQ,IAAI,KAAK,WAAW,CAAC;AACpE,kBAAgB,IAAI,OAAO;AAC3B,OAAK,QAAQ,QAAQ,MAAM,gBAAgB,OAAO,OAAO,CAAC;AAC5D;AAEO,MAAM,mBAAmB,WAAW;AAAA,EACzC,YACW,KACA,aACT;AACA,UAAM;AAHG;AACA;AAAA,EAGX;AAAA,EAES,GAAG,OAA4B;AACtC,WAAO,MAAM,QAAQ,KAAK,OAAO,MAAM,gBAAgB,KAAK;AAAA,EAC9D;AAAA,EAES,MAAM,OAAgC;AAC7C,UAAM,OAAO,SAAS,cAAc,KAAK,cAAc,QAAQ,MAAM;AACrE,SAAK,YAAY,KAAK,cAAc,kBAAkB;AACtD,mBAAe,MAAM,KAAK,KAAK,KAAK,WAAW;AAC/C,WAAO;AAAA,EACT;AAAA,EAES,cAAuB;AAC9B,WAAO;AAAA,EACT;AACF;","names":[]}
@@ -128,7 +128,22 @@ const hideNativeSelection = Prec.highest(
128
128
  }
129
129
  })
130
130
  );
131
- const drawnSelection = [selectionLayer, widgetTintLayer, hideNativeSelection];
131
+ const selectionColor = Prec.highest(
132
+ EditorView.theme({
133
+ "&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": {
134
+ background: "var(--cds-highlight, #d0e2ff)"
135
+ },
136
+ "&:not(.cm-focused) > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": {
137
+ background: "var(--cds-layer-accent-01, #e0e0e0)"
138
+ }
139
+ })
140
+ );
141
+ const drawnSelection = [
142
+ selectionLayer,
143
+ widgetTintLayer,
144
+ hideNativeSelection,
145
+ selectionColor
146
+ ];
132
147
  export {
133
148
  drawnSelection
134
149
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/codemirror/selectionLayer.ts"],"sourcesContent":["/**\n * Drawn selection over a virtualized viewport (#166).\n *\n * CodeMirror renders only the visible viewport, and the browser's native\n * `::selection` can only highlight DOM that exists — so with native painting,\n * a Cmd+A or Shift-extended selection shows highlight only on whatever\n * happened to be rendered, and scrolling away and back loses it entirely.\n * This layer paints selection ranges FROM LOGICAL STATE\n * (`state.selection` × the current viewport), so the highlight is correct at\n * every scroll position by construction.\n *\n * Why not CM6's own `drawSelection()`: its wrapped-line handling reverse-maps\n * the editor's far-left/right edge through `posAtCoords` per endpoint, and on\n * lines that start with hidden markers + widgets that probe jitters between\n * before/after the hidden prefix, flashing between one-piece and three-piece\n * painting per drag update (#90). The rects here derive only from FORWARD\n * geometry — `coordsAtPos` of the actual endpoints plus line-block boxes —\n * with no reverse edge probe anywhere:\n *\n * * endpoints in the same visual row → one rect between their coords;\n * * a wrapped or multi-line range → a partial rect for the first row, a\n * partial rect for the last row, and ONE full-width rect for everything\n * between (which also spans block widgets and hidden rows);\n * * a block widget wholly inside a range additionally gets a translucent\n * ABOVE-content tint (`cm-selectionWidgetTint`): widgets paint opaque\n * backgrounds over a below-content layer, so without it a selected\n * table/diagram would show no feedback at all.\n *\n * Known coarseness: partial rows of RTL text paint full-width (bidi span\n * splitting is deliberately not reimplemented here); LTR documents paint\n * glyph-accurately.\n *\n * The native `::selection` stays for the tablev2 cell editors (their own\n * contenteditable islands) and is made transparent everywhere else — the DOM\n * selection itself is untouched, so IME, copy, and focus behavior keep\n * working on the real selection.\n */\n\nimport { Prec, type Extension, type SelectionRange } from \"@codemirror/state\";\nimport {\n BlockType,\n Direction,\n EditorView,\n layer,\n RectangleMarker,\n type BlockInfo,\n} from \"@codemirror/view\";\n\n/** Client-rect → document-space base, mirroring CM6's own layer arithmetic. */\nfunction layerBase(view: EditorView): { left: number; top: number } {\n const rect = view.scrollDOM.getBoundingClientRect();\n const left =\n view.textDirection === Direction.LTR\n ? rect.left\n : rect.right - view.scrollDOM.clientWidth * view.scaleX;\n return {\n left: left - view.scrollDOM.scrollLeft * view.scaleX,\n top: rect.top - view.scrollDOM.scrollTop * view.scaleY,\n };\n}\n\ninterface ContentEdges {\n left: number;\n right: number;\n}\n\n/** The horizontal band text can occupy, in client coordinates. */\nfunction contentEdges(view: EditorView): ContentEdges {\n const rect = view.contentDOM.getBoundingClientRect();\n const line = view.contentDOM.querySelector(\".cm-line\");\n const style = line ? window.getComputedStyle(line) : null;\n const padLeft = style ? parseInt(style.paddingLeft) || 0 : 0;\n const padRight = style ? parseInt(style.paddingRight) || 0 : 0;\n return { left: rect.left + padLeft, right: rect.right - padRight };\n}\n\nfunction isTextBlock(block: BlockInfo): boolean {\n return block.type === BlockType.Text || Array.isArray(block.type);\n}\n\nclass Rect {\n constructor(\n readonly left: number,\n readonly top: number,\n readonly right: number,\n readonly bottom: number,\n ) {}\n}\n\n/**\n * Selection rectangles for one range, clamped to the viewport. Every rect is\n * derived from forward endpoint geometry; positions whose coords are\n * unavailable (jsdom, or an endpoint inside a replaced range) degrade to the\n * enclosing block's box rather than throwing.\n */\nfunction rectsForRange(view: EditorView, range: SelectionRange, edges: ContentEdges): Rect[] {\n if (range.to <= view.viewport.from || range.from >= view.viewport.to) return [];\n const from = Math.max(range.from, view.viewport.from);\n const to = Math.min(range.to, view.viewport.to);\n const startBlock = view.lineBlockAt(from);\n const endBlock = view.lineBlockAt(to);\n const contentTop = view.contentDOM.getBoundingClientRect().top;\n\n // A selection endpoint sitting on a widget block has no glyph coords; its\n // \"row\" is the widget's whole box.\n const startCoords = isTextBlock(startBlock) ? view.coordsAtPos(from, 1) : null;\n const endCoords = isTextBlock(endBlock) ? view.coordsAtPos(to, -1) : null;\n const startTop = startCoords ? startCoords.top : contentTop + startBlock.top;\n const startBottom = startCoords ? startCoords.bottom : contentTop + startBlock.bottom;\n const endTop = endCoords ? endCoords.top : contentTop + endBlock.top;\n const endBottom = endCoords ? endCoords.bottom : contentTop + endBlock.bottom;\n const startLeft = startCoords ? startCoords.left : edges.left;\n const endRight = endCoords ? endCoords.right : edges.right;\n\n // Same visual row: row boxes share their top edge, so the endpoint tops\n // are equal by construction (adjacent rows differ by a full row height).\n if (endTop - startTop < 1) {\n return [new Rect(startLeft, startTop, Math.max(endRight, startLeft), endBottom)];\n }\n\n const rects: Rect[] = [\n // Partial first row: selection start to the row's right edge.\n new Rect(startLeft, startTop, edges.right, startBottom),\n // Everything between the first and last rows — full width. Wrapped rows\n // of the endpoint lines, whole middle lines, and block widgets all fall\n // inside this band, which is what makes the shape probe-free.\n new Rect(edges.left, startBottom, edges.right, endTop),\n // Partial last row: the row's left edge to the selection end.\n new Rect(edges.left, endTop, endRight, endBottom),\n ];\n return rects.filter((r) => r.bottom > r.top || r.right > r.left);\n}\n\nconst selectionLayer = layer({\n above: false,\n class: \"cm-selectionLayer\",\n update: (update) =>\n update.docChanged || update.selectionSet || update.viewportChanged || update.geometryChanged,\n markers(view) {\n const base = layerBase(view);\n const edges = contentEdges(view);\n const markers: RectangleMarker[] = [];\n for (const range of view.state.selection.ranges) {\n if (range.empty) continue;\n for (const rect of rectsForRange(view, range, edges)) {\n markers.push(\n new RectangleMarker(\n \"cm-selectionBackground\",\n rect.left - base.left,\n rect.top - base.top,\n Math.max(0, rect.right - rect.left),\n rect.bottom - rect.top,\n ),\n );\n }\n }\n return markers;\n },\n});\n\n/** Translucent tint ABOVE block widgets wholly covered by a selection — the\n * below-content band is hidden behind their opaque backgrounds. */\nconst widgetTintLayer = layer({\n above: true,\n class: \"cm-selectionWidgetLayer\",\n update: (update) =>\n update.docChanged || update.selectionSet || update.viewportChanged || update.geometryChanged,\n markers(view) {\n const covering = view.state.selection.ranges.filter((r) => !r.empty);\n if (covering.length === 0) return [];\n const base = layerBase(view);\n const edges = contentEdges(view);\n const contentTop = view.contentDOM.getBoundingClientRect().top;\n const markers: RectangleMarker[] = [];\n for (const block of view.viewportLineBlocks) {\n if (isTextBlock(block)) continue;\n if (!covering.some((r) => r.from <= block.from && r.to >= block.to)) continue;\n markers.push(\n new RectangleMarker(\n \"cm-selectionWidgetTint\",\n edges.left - base.left,\n contentTop + block.top - base.top,\n Math.max(0, edges.right - edges.left),\n block.bottom - block.top,\n ),\n );\n }\n return markers;\n },\n});\n\n// The drawn layer replaces the native highlight; the DOM selection itself\n// stays (IME, copy, focus). The tablev2 cell editors are separate\n// contenteditable islands that keep the native paint.\nconst hideNativeSelection = Prec.highest(\n EditorView.theme({\n \".cm-content ::selection, .cm-content::selection\": {\n backgroundColor: \"transparent\",\n },\n '.cm-content [contenteditable=\"plaintext-only\"] ::selection, .cm-content [contenteditable=\"plaintext-only\"]::selection':\n {\n backgroundColor: \"var(--cds-highlight, #d0e2ff)\",\n },\n }),\n);\n\nexport const drawnSelection: Extension = [selectionLayer, widgetTintLayer, hideNativeSelection];\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/codemirror/selectionLayer.ts"],"sourcesContent":["/**\n * Drawn selection over a virtualized viewport (#166).\n *\n * CodeMirror renders only the visible viewport, and the browser's native\n * `::selection` can only highlight DOM that exists — so with native painting,\n * a Cmd+A or Shift-extended selection shows highlight only on whatever\n * happened to be rendered, and scrolling away and back loses it entirely.\n * This layer paints selection ranges FROM LOGICAL STATE\n * (`state.selection` × the current viewport), so the highlight is correct at\n * every scroll position by construction.\n *\n * Why not CM6's own `drawSelection()`: its wrapped-line handling reverse-maps\n * the editor's far-left/right edge through `posAtCoords` per endpoint, and on\n * lines that start with hidden markers + widgets that probe jitters between\n * before/after the hidden prefix, flashing between one-piece and three-piece\n * painting per drag update (#90). The rects here derive only from FORWARD\n * geometry — `coordsAtPos` of the actual endpoints plus line-block boxes —\n * with no reverse edge probe anywhere:\n *\n * * endpoints in the same visual row → one rect between their coords;\n * * a wrapped or multi-line range → a partial rect for the first row, a\n * partial rect for the last row, and ONE full-width rect for everything\n * between (which also spans block widgets and hidden rows);\n * * a block widget wholly inside a range additionally gets a translucent\n * ABOVE-content tint (`cm-selectionWidgetTint`): widgets paint opaque\n * backgrounds over a below-content layer, so without it a selected\n * table/diagram would show no feedback at all.\n *\n * Known coarseness: partial rows of RTL text paint full-width (bidi span\n * splitting is deliberately not reimplemented here); LTR documents paint\n * glyph-accurately.\n *\n * The native `::selection` stays for the tablev2 cell editors (their own\n * contenteditable islands) and is made transparent everywhere else — the DOM\n * selection itself is untouched, so IME, copy, and focus behavior keep\n * working on the real selection.\n */\n\nimport { Prec, type Extension, type SelectionRange } from \"@codemirror/state\";\nimport {\n BlockType,\n Direction,\n EditorView,\n layer,\n RectangleMarker,\n type BlockInfo,\n} from \"@codemirror/view\";\n\n/** Client-rect → document-space base, mirroring CM6's own layer arithmetic. */\nfunction layerBase(view: EditorView): { left: number; top: number } {\n const rect = view.scrollDOM.getBoundingClientRect();\n const left =\n view.textDirection === Direction.LTR\n ? rect.left\n : rect.right - view.scrollDOM.clientWidth * view.scaleX;\n return {\n left: left - view.scrollDOM.scrollLeft * view.scaleX,\n top: rect.top - view.scrollDOM.scrollTop * view.scaleY,\n };\n}\n\ninterface ContentEdges {\n left: number;\n right: number;\n}\n\n/** The horizontal band text can occupy, in client coordinates. */\nfunction contentEdges(view: EditorView): ContentEdges {\n const rect = view.contentDOM.getBoundingClientRect();\n const line = view.contentDOM.querySelector(\".cm-line\");\n const style = line ? window.getComputedStyle(line) : null;\n const padLeft = style ? parseInt(style.paddingLeft) || 0 : 0;\n const padRight = style ? parseInt(style.paddingRight) || 0 : 0;\n return { left: rect.left + padLeft, right: rect.right - padRight };\n}\n\nfunction isTextBlock(block: BlockInfo): boolean {\n return block.type === BlockType.Text || Array.isArray(block.type);\n}\n\nclass Rect {\n constructor(\n readonly left: number,\n readonly top: number,\n readonly right: number,\n readonly bottom: number,\n ) {}\n}\n\n/**\n * Selection rectangles for one range, clamped to the viewport. Every rect is\n * derived from forward endpoint geometry; positions whose coords are\n * unavailable (jsdom, or an endpoint inside a replaced range) degrade to the\n * enclosing block's box rather than throwing.\n */\nfunction rectsForRange(view: EditorView, range: SelectionRange, edges: ContentEdges): Rect[] {\n if (range.to <= view.viewport.from || range.from >= view.viewport.to) return [];\n const from = Math.max(range.from, view.viewport.from);\n const to = Math.min(range.to, view.viewport.to);\n const startBlock = view.lineBlockAt(from);\n const endBlock = view.lineBlockAt(to);\n const contentTop = view.contentDOM.getBoundingClientRect().top;\n\n // A selection endpoint sitting on a widget block has no glyph coords; its\n // \"row\" is the widget's whole box.\n const startCoords = isTextBlock(startBlock) ? view.coordsAtPos(from, 1) : null;\n const endCoords = isTextBlock(endBlock) ? view.coordsAtPos(to, -1) : null;\n const startTop = startCoords ? startCoords.top : contentTop + startBlock.top;\n const startBottom = startCoords ? startCoords.bottom : contentTop + startBlock.bottom;\n const endTop = endCoords ? endCoords.top : contentTop + endBlock.top;\n const endBottom = endCoords ? endCoords.bottom : contentTop + endBlock.bottom;\n const startLeft = startCoords ? startCoords.left : edges.left;\n const endRight = endCoords ? endCoords.right : edges.right;\n\n // Same visual row: row boxes share their top edge, so the endpoint tops\n // are equal by construction (adjacent rows differ by a full row height).\n if (endTop - startTop < 1) {\n return [new Rect(startLeft, startTop, Math.max(endRight, startLeft), endBottom)];\n }\n\n const rects: Rect[] = [\n // Partial first row: selection start to the row's right edge.\n new Rect(startLeft, startTop, edges.right, startBottom),\n // Everything between the first and last rows — full width. Wrapped rows\n // of the endpoint lines, whole middle lines, and block widgets all fall\n // inside this band, which is what makes the shape probe-free.\n new Rect(edges.left, startBottom, edges.right, endTop),\n // Partial last row: the row's left edge to the selection end.\n new Rect(edges.left, endTop, endRight, endBottom),\n ];\n return rects.filter((r) => r.bottom > r.top || r.right > r.left);\n}\n\nconst selectionLayer = layer({\n above: false,\n class: \"cm-selectionLayer\",\n update: (update) =>\n update.docChanged || update.selectionSet || update.viewportChanged || update.geometryChanged,\n markers(view) {\n const base = layerBase(view);\n const edges = contentEdges(view);\n const markers: RectangleMarker[] = [];\n for (const range of view.state.selection.ranges) {\n if (range.empty) continue;\n for (const rect of rectsForRange(view, range, edges)) {\n markers.push(\n new RectangleMarker(\n \"cm-selectionBackground\",\n rect.left - base.left,\n rect.top - base.top,\n Math.max(0, rect.right - rect.left),\n rect.bottom - rect.top,\n ),\n );\n }\n }\n return markers;\n },\n});\n\n/** Translucent tint ABOVE block widgets wholly covered by a selection — the\n * below-content band is hidden behind their opaque backgrounds. */\nconst widgetTintLayer = layer({\n above: true,\n class: \"cm-selectionWidgetLayer\",\n update: (update) =>\n update.docChanged || update.selectionSet || update.viewportChanged || update.geometryChanged,\n markers(view) {\n const covering = view.state.selection.ranges.filter((r) => !r.empty);\n if (covering.length === 0) return [];\n const base = layerBase(view);\n const edges = contentEdges(view);\n const contentTop = view.contentDOM.getBoundingClientRect().top;\n const markers: RectangleMarker[] = [];\n for (const block of view.viewportLineBlocks) {\n if (isTextBlock(block)) continue;\n if (!covering.some((r) => r.from <= block.from && r.to >= block.to)) continue;\n markers.push(\n new RectangleMarker(\n \"cm-selectionWidgetTint\",\n edges.left - base.left,\n contentTop + block.top - base.top,\n Math.max(0, edges.right - edges.left),\n block.bottom - block.top,\n ),\n );\n }\n return markers;\n },\n});\n\n// The drawn layer replaces the native highlight; the DOM selection itself\n// stays (IME, copy, focus). The tablev2 cell editors are separate\n// contenteditable islands that keep the native paint.\nconst hideNativeSelection = Prec.highest(\n EditorView.theme({\n \".cm-content ::selection, .cm-content::selection\": {\n backgroundColor: \"transparent\",\n },\n '.cm-content [contenteditable=\"plaintext-only\"] ::selection, .cm-content [contenteditable=\"plaintext-only\"]::selection':\n {\n backgroundColor: \"var(--cds-highlight, #d0e2ff)\",\n },\n }),\n);\n\n/**\n * The host's highlight token, stated at CodeMirror's own specificity.\n *\n * CM's base theme carries\n * `&light.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground`\n * — far more specific than a plain `.cm-selectionBackground` — and this editor\n * is themed through CSS custom properties rather than registered as\n * `{ dark: true }`, so CM believes it is always `&light` and paints its own\n * `#d7d4f0` over the token. On a dark host that is light-on-light: the selected\n * text becomes the least readable text on screen.\n *\n * The same trap the caret hit (see `editorTheme.ts`), in the one place where\n * getting it wrong hides what the reader just selected. Both focused and\n * unfocused are stated, because CM has a rule for each.\n */\nconst selectionColor = Prec.highest(\n EditorView.theme({\n \"&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground\": {\n background: \"var(--cds-highlight, #d0e2ff)\",\n },\n \"&:not(.cm-focused) > .cm-scroller > .cm-selectionLayer .cm-selectionBackground\": {\n background: \"var(--cds-layer-accent-01, #e0e0e0)\",\n },\n }),\n);\n\nexport const drawnSelection: Extension = [\n selectionLayer,\n widgetTintLayer,\n hideNativeSelection,\n selectionColor,\n];\n"],"mappings":"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","names":[]}
package/dist/index.d.ts CHANGED
@@ -18,6 +18,7 @@ export { mathExtension } from './codemirror/extensions/mathExtension.js';
18
18
  export { mermaidExtension } from './codemirror/extensions/mermaidExtension.js';
19
19
  export { tableExtension } from './codemirror/extensions/tableExtension.js';
20
20
  export { wikilinkExtension } from './codemirror/extensions/wikilinkExtension.js';
21
+ export { mathTypesettingSettled } from './codemirror/math/mathWidget.js';
21
22
  export { MermaidRenderResult, getCachedMermaidPng, isMermaidFenceInfo, renderMermaidToSvg, warmMermaidPng } from './codemirror/mermaid/mermaidRender.js';
22
23
  export { HighlightedSpan, highlightFenceSpans } from './codemirror/code/highlightFence.js';
23
24
  export { ImageInsertOptions, ImageInsertResult, buildImageMarkdown, extractImageBlobs, extractImageFiles, insertImageBlob } from './imageInsert.js';
package/dist/index.js CHANGED
@@ -31,6 +31,7 @@ import {
31
31
  tableExtension,
32
32
  wikilinkExtension
33
33
  } from "./codemirror/extensions/index.js";
34
+ import { mathTypesettingSettled } from "./codemirror/math/mathWidget.js";
34
35
  import {
35
36
  renderMermaidToSvg,
36
37
  getCachedMermaidPng,
@@ -106,6 +107,7 @@ export {
106
107
  mark,
107
108
  markdownDecorationsPlugin,
108
109
  mathExtension,
110
+ mathTypesettingSettled,
109
111
  mergeExtensions,
110
112
  mermaidExtension,
111
113
  nodeRulesFacet,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @latentic/live-markdown — a rich markdown editor on CodeMirror 6.\n *\n * Headings, lists, tables, images, math, and footnotes render inline while the\n * file on disk stays plain markdown. `value` in / `onChange` out is always a\n * markdown string — no AST translation, no format lock-in.\n *\n * Host-environment concerns (image storage, asset resolution, link opening,\n * toolbar, selection UI) are injected through props/slots, each with a\n * browser-friendly default, so the editor runs unmodified in a plain browser or\n * a desktop shell.\n */\n\n// ── Editor component ─────────────────────────────────────────────────────────\nexport {\n CodeMirrorMarkdownEditor,\n type CodeMirrorMarkdownEditorProps,\n type CodeMirrorEditorMode,\n type EditorSelectionSnapshot,\n} from \"./codemirror/CodeMirrorMarkdownEditor\";\n\n// ── Core value types ─────────────────────────────────────────────────────────\nexport type { SourceRange, DocumentTextChange } from \"./types\";\n\n// ── Host-environment seam types ──────────────────────────────────────────────\nexport type {\n ResolveImageSrc,\n SaveImageBytes,\n OpenExternalUrl,\n} from \"./codemirror/core\";\n\n// ── Editing commands (for building custom toolbars) ──────────────────────────\nexport { formatCommands, blockCommands } from \"./codemirror/format\";\nexport { onEditorUpdate } from \"./codemirror/updateBus\";\n// Structural position queries resolve from this, never from a bare\n// `syntaxTree` — which only covers what the viewport has driven the parser\n// through (see the doc on `treeAt`).\nexport { treeAt } from \"./codemirror/core\";\n\n// ── Decoration engine + theme ────────────────────────────────────────────────\nexport { markdownDecorationsPlugin, editorBaseTheme } from \"./codemirror/core\";\n// The rendering contract: one rule per node name, combinators for the common\n// shapes, a facet for extension-contributed rules.\nexport {\n nodeRulesFacet,\n mark,\n line,\n headingLine,\n hideAlways,\n raw,\n structural,\n type NodeRule,\n type NodeRules,\n type NodeContext,\n type Paint,\n} from \"./codemirror/core\";\n// The other half of the rendering contract, for the constructs Lezer does NOT\n// parse. Wikilinks, `==highlight==`, footnote refs and `$math$` are found by\n// scanning text, so a renderer that walks the tree cannot see them; a feature\n// contributes its pattern and its markup here instead. `escapeText` is what a\n// rule's `render` needs to neutralise markup in document content.\nexport {\n inlineScanRulesFacet,\n scanInline,\n escapeText,\n escapeAttr,\n type InlineScanRule,\n type InlineScanMatch,\n} from \"./codemirror/core\";\n\n// ── Extension system ─────────────────────────────────────────────────────────\nexport {\n type MarkdownExtension,\n type ToolbarContribution,\n type CaretContextSnapshot,\n mergeExtensions,\n // Deprecated alias; see the note on `mergeExtensions`.\n composeExtensions,\n type ComposedExtension,\n highlightExtension,\n footnoteExtension,\n mathExtension,\n mermaidExtension,\n tableExtension,\n wikilinkExtension,\n} from \"./codemirror/extensions\";\n\n// Mermaid rendering, reusable outside the editor: the document export ships\n// the same SVGs the editor shows; the clipboard embeds pre-rasterised PNGs.\n// Concrete module path on purpose — mermaidRender is CodeMirror-free, and the\n// mermaid barrel star-exports the CM StateField alongside it.\nexport {\n renderMermaidToSvg,\n getCachedMermaidPng,\n warmMermaidPng,\n isMermaidFenceInfo,\n type MermaidRenderResult,\n} from \"./codemirror/mermaid/mermaidRender\";\n\n// Synchronous fence highlighting for the clipboard's HTML flavor.\nexport {\n highlightFenceSpans,\n type HighlightedSpan,\n} from \"./codemirror/code\";\n\n// ── Image pipeline ───────────────────────────────────────────────────────────\nexport {\n insertImageBlob,\n buildImageMarkdown,\n extractImageBlobs,\n extractImageFiles,\n type ImageInsertOptions,\n type ImageInsertResult,\n} from \"./imageInsert\";\nexport {\n imageInsertHandlers,\n pickImageFileForCaret,\n showImageActionMenu,\n} from \"./codemirror/image\";\n// The event contract is CodeMirror-free (see imageEditEvent) so a host can\n// listen for it without pulling the editor into its initial bundle — hence\n// the concrete module path, not the image barrel.\nexport {\n IMAGE_EDIT_ALT_EVENT,\n type ImageEditAltEventDetail,\n} from \"./codemirror/image/imageEditEvent\";\nexport {\n type ImageResolveContext,\n defaultResolveImageSrc,\n hasUriScheme,\n computeFileDir,\n isAbsolutePath,\n dirnamePath,\n joinPath,\n} from \"./imageSrcResolver\";\n\n// ── Frontmatter ──────────────────────────────────────────────────────────────\nexport {\n parseFrontmatter,\n serializeMarkdown,\n setFrontmatterField,\n type Frontmatter,\n type FrontmatterValue,\n type MarkdownDocument,\n} from \"./frontmatter\";\n\n// ── Link resolution (wiki + workspace links) ─────────────────────────────────\nexport {\n parseWikilinkBody,\n resolveWikilinkTarget,\n} from \"./links/wikilink\";\nexport {\n resolveWorkspaceLink,\n type ResolvedWorkspaceLink,\n type ResolveWorkspaceLinkOptions,\n} from \"./links/workspaceLink\";\n"],"mappings":";AAcA;AAAA,EACE;AAAA,OAIK;AAaP,SAAS,gBAAgB,qBAAqB;AAC9C,SAAS,sBAAsB;AAI/B,SAAS,cAAc;AAGvB,SAAS,2BAA2B,uBAAuB;AAG3D;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAKK;AAMP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAGP;AAAA,EAIE;AAAA,EAEA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAMP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAGP;AAAA,EACE;AAAA,OAEK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAIP;AAAA,EACE;AAAA,OAEK;AACP;AAAA,EAEE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,OAGK;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @latentic/live-markdown — a rich markdown editor on CodeMirror 6.\n *\n * Headings, lists, tables, images, math, and footnotes render inline while the\n * file on disk stays plain markdown. `value` in / `onChange` out is always a\n * markdown string — no AST translation, no format lock-in.\n *\n * Host-environment concerns (image storage, asset resolution, link opening,\n * toolbar, selection UI) are injected through props/slots, each with a\n * browser-friendly default, so the editor runs unmodified in a plain browser or\n * a desktop shell.\n */\n\n// ── Editor component ─────────────────────────────────────────────────────────\nexport {\n CodeMirrorMarkdownEditor,\n type CodeMirrorMarkdownEditorProps,\n type CodeMirrorEditorMode,\n type EditorSelectionSnapshot,\n} from \"./codemirror/CodeMirrorMarkdownEditor\";\n\n// ── Core value types ─────────────────────────────────────────────────────────\nexport type { SourceRange, DocumentTextChange } from \"./types\";\n\n// ── Host-environment seam types ──────────────────────────────────────────────\nexport type {\n ResolveImageSrc,\n SaveImageBytes,\n OpenExternalUrl,\n} from \"./codemirror/core\";\n\n// ── Editing commands (for building custom toolbars) ──────────────────────────\nexport { formatCommands, blockCommands } from \"./codemirror/format\";\nexport { onEditorUpdate } from \"./codemirror/updateBus\";\n// Structural position queries resolve from this, never from a bare\n// `syntaxTree` — which only covers what the viewport has driven the parser\n// through (see the doc on `treeAt`).\nexport { treeAt } from \"./codemirror/core\";\n\n// ── Decoration engine + theme ────────────────────────────────────────────────\nexport { markdownDecorationsPlugin, editorBaseTheme } from \"./codemirror/core\";\n// The rendering contract: one rule per node name, combinators for the common\n// shapes, a facet for extension-contributed rules.\nexport {\n nodeRulesFacet,\n mark,\n line,\n headingLine,\n hideAlways,\n raw,\n structural,\n type NodeRule,\n type NodeRules,\n type NodeContext,\n type Paint,\n} from \"./codemirror/core\";\n// The other half of the rendering contract, for the constructs Lezer does NOT\n// parse. Wikilinks, `==highlight==`, footnote refs and `$math$` are found by\n// scanning text, so a renderer that walks the tree cannot see them; a feature\n// contributes its pattern and its markup here instead. `escapeText` is what a\n// rule's `render` needs to neutralise markup in document content.\nexport {\n inlineScanRulesFacet,\n scanInline,\n escapeText,\n escapeAttr,\n type InlineScanRule,\n type InlineScanMatch,\n} from \"./codemirror/core\";\n\n// ── Extension system ─────────────────────────────────────────────────────────\nexport {\n type MarkdownExtension,\n type ToolbarContribution,\n type CaretContextSnapshot,\n mergeExtensions,\n // Deprecated alias; see the note on `mergeExtensions`.\n composeExtensions,\n type ComposedExtension,\n highlightExtension,\n footnoteExtension,\n mathExtension,\n mermaidExtension,\n tableExtension,\n wikilinkExtension,\n} from \"./codemirror/extensions\";\n// KaTeX is fetched on the first expression, so anything that MEASURES rendered\n// math — an export, a test — has to wait for it.\nexport { mathTypesettingSettled } from \"./codemirror/math/mathWidget\";\n\n// Mermaid rendering, reusable outside the editor: the document export ships\n// the same SVGs the editor shows; the clipboard embeds pre-rasterised PNGs.\n// Concrete module path on purpose — mermaidRender is CodeMirror-free, and the\n// mermaid barrel star-exports the CM StateField alongside it.\nexport {\n renderMermaidToSvg,\n getCachedMermaidPng,\n warmMermaidPng,\n isMermaidFenceInfo,\n type MermaidRenderResult,\n} from \"./codemirror/mermaid/mermaidRender\";\n\n// Synchronous fence highlighting for the clipboard's HTML flavor.\nexport {\n highlightFenceSpans,\n type HighlightedSpan,\n} from \"./codemirror/code\";\n\n// ── Image pipeline ───────────────────────────────────────────────────────────\nexport {\n insertImageBlob,\n buildImageMarkdown,\n extractImageBlobs,\n extractImageFiles,\n type ImageInsertOptions,\n type ImageInsertResult,\n} from \"./imageInsert\";\nexport {\n imageInsertHandlers,\n pickImageFileForCaret,\n showImageActionMenu,\n} from \"./codemirror/image\";\n// The event contract is CodeMirror-free (see imageEditEvent) so a host can\n// listen for it without pulling the editor into its initial bundle — hence\n// the concrete module path, not the image barrel.\nexport {\n IMAGE_EDIT_ALT_EVENT,\n type ImageEditAltEventDetail,\n} from \"./codemirror/image/imageEditEvent\";\nexport {\n type ImageResolveContext,\n defaultResolveImageSrc,\n hasUriScheme,\n computeFileDir,\n isAbsolutePath,\n dirnamePath,\n joinPath,\n} from \"./imageSrcResolver\";\n\n// ── Frontmatter ──────────────────────────────────────────────────────────────\nexport {\n parseFrontmatter,\n serializeMarkdown,\n setFrontmatterField,\n type Frontmatter,\n type FrontmatterValue,\n type MarkdownDocument,\n} from \"./frontmatter\";\n\n// ── Link resolution (wiki + workspace links) ─────────────────────────────────\nexport {\n parseWikilinkBody,\n resolveWikilinkTarget,\n} from \"./links/wikilink\";\nexport {\n resolveWorkspaceLink,\n type ResolvedWorkspaceLink,\n type ResolveWorkspaceLinkOptions,\n} from \"./links/workspaceLink\";\n"],"mappings":";AAcA;AAAA,EACE;AAAA,OAIK;AAaP,SAAS,gBAAgB,qBAAqB;AAC9C,SAAS,sBAAsB;AAI/B,SAAS,cAAc;AAGvB,SAAS,2BAA2B,uBAAuB;AAG3D;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAKK;AAMP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAGP;AAAA,EAIE;AAAA,EAEA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP,SAAS,8BAA8B;AAMvC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAGP;AAAA,EACE;AAAA,OAEK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAIP;AAAA,EACE;AAAA,OAEK;AACP;AAAA,EAEE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,OAGK;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@latentic/live-markdown",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "WYSIWYG markdown editor for React, built on CodeMirror 6. Headings, tables, LaTeX math and Mermaid diagrams render inline as you type \u2014 no preview pane and no AST: value in and onChange out is the same markdown string, frontmatter included.",