From 10a4c80f8c0dc61c513f95b3a8f3a3034c9cf3e2 Mon Sep 17 00:00:00 2001 From: saxumcordis Date: Thu, 9 Oct 2025 18:07:37 +0300 Subject: [PATCH 1/2] feat: ? --- .../examples/markup-line-numbers/Editor.tsx | 200 ++++++++++++++++++ .../MarkupLineNumbers.stories.tsx | 61 ++++++ packages/editor/src/bundle/Editor.ts | 10 + .../src/bundle/MarkupEditorComponent.tsx | 8 + packages/editor/src/bundle/types.ts | 24 ++- packages/editor/src/index.ts | 10 +- .../editor/src/markup/codemirror/create.ts | 17 ++ .../editor/src/markup/codemirror/gravity.ts | 9 + .../editor/src/markup/codemirror/index.ts | 2 + .../codemirror/line-highlight/extension.ts | 132 ++++++++++++ .../markup/codemirror/line-highlight/index.ts | 2 + 11 files changed, 473 insertions(+), 2 deletions(-) create mode 100644 demo/src/stories/examples/markup-line-numbers/Editor.tsx create mode 100644 demo/src/stories/examples/markup-line-numbers/MarkupLineNumbers.stories.tsx create mode 100644 packages/editor/src/markup/codemirror/line-highlight/extension.ts create mode 100644 packages/editor/src/markup/codemirror/line-highlight/index.ts diff --git a/demo/src/stories/examples/markup-line-numbers/Editor.tsx b/demo/src/stories/examples/markup-line-numbers/Editor.tsx new file mode 100644 index 000000000..d2ceebc8c --- /dev/null +++ b/demo/src/stories/examples/markup-line-numbers/Editor.tsx @@ -0,0 +1,200 @@ +import {memo, useState} from 'react'; + +import { + MarkdownEditorView, + NumberInput, + setHighlightedLine, + useMarkdownEditor, +} from '@gravity-ui/markdown-editor'; +import type {MarkupLineNumbersConfig} from '@gravity-ui/markdown-editor'; +import {Button, Flex} from '@gravity-ui/uikit'; + +import {PlaygroundLayout} from '../../../components/PlaygroundLayout'; + +const longMarkup = [ + '# Markup Line Numbers Demo', + '', + 'This document demonstrates the new markup-mode features:', + 'line numbers, line highlighting, and scroll-to-line.', + '', + '## Getting Started', + '', + 'The editor below is running in **markup mode** (CodeMirror 6).', + 'You can see line numbers in the gutter on the left side.', + '', + '### Line Numbers', + '', + 'When `markupConfig.lineNumbers` is enabled, the editor', + 'displays line numbers in the left gutter. This is useful', + 'for referencing specific lines in documentation or code reviews.', + '', + '### Line Highlighting', + '', + 'When `lineHighlight()` extension is passed via `markupConfig.extensions`,', + 'clicking on a line number in the gutter will highlight that entire line.', + 'This extension automatically includes line numbers as well.', + '', + 'You can also programmatically highlight a line using the', + '`setHighlightedLine` StateEffect exported from the package.', + '', + '### Scroll to Line', + '', + 'The `markupConfig.lineNumbers.scrollToLine` option allows you to scroll the', + 'editor to a specific line (0-based) on mount. This is useful', + 'when you want to draw attention to a particular section of', + 'a long document.', + '', + '## Example Content', + '', + 'Here is some additional content to make the document long', + 'enough to demonstrate scrolling behavior.', + '', + '### Lists', + '', + '- Item one', + '- Item two', + '- Item three', + '- Item four', + '- Item five', + '', + '### Code Block', + '', + '```typescript', + "import {lineHighlight, useMarkdownEditor} from '@gravity-ui/markdown-editor';", + '', + 'const editor = useMarkdownEditor({', + " initial: {mode: 'markup'},", + ' markupConfig: {', + ' extensions: [lineHighlight()],', + ' },', + '});', + '```', + '', + '### Table', + '', + '| Feature | Option | Default |', + '|---------|--------|---------|', + '| Line numbers | `markupConfig.lineNumbers` | `false` |', + '| Highlight line | `markupConfig.extensions: [lineHighlight()]` | — |', + '| Scroll to line | `markupConfig.lineNumbers.scrollToLine` | `undefined` |', + '', + '### More Text', + '', + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + 'Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + 'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.', + '', + 'Duis aute irure dolor in reprehenderit in voluptate velit esse', + 'cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat', + 'cupidatat non proident, sunt in culpa qui officia deserunt mollit.', + '', + '### Final Section', + '', + 'This is the end of the demo document. If `scrollToLine` is set', + 'to a value like 20, the editor should scroll to approximately', + 'this area of the document on initialization.', + '', + '> **Tip:** Try clicking on line numbers in the gutter to highlight lines!', +].join('\n'); + +export type MarkupLineNumbersEditorProps = { + lineNumbers?: MarkupLineNumbersConfig; +}; + +export const MarkupLineNumbersEditor = memo( + function MarkupLineNumbersEditor({lineNumbers}) { + const [fromLine, setFromLine] = useState( + lineNumbers?.initialSelectedLines?.from ?? 0, + ); + const [toLine, setToLine] = useState( + lineNumbers?.initialSelectedLines?.to ?? 0, + ); + const [lastClickedLine, setLastClickedLine] = useState(null); + + const markupLineNumbers: MarkupLineNumbersConfig | undefined = lineNumbers + ? { + ...lineNumbers, + onLineClick: (line) => setLastClickedLine(line), + } + : undefined; + + const editor = useMarkdownEditor( + { + initial: { + mode: 'markup', + markup: longMarkup, + }, + markupConfig: { + lineNumbers: markupLineNumbers, + }, + }, + [], + ); + + const handleHighlightLine = () => { + if (typeof fromLine !== 'number' || Number.isNaN(fromLine)) return; + if (typeof toLine !== 'number' || Number.isNaN(toLine)) return; + + const cm = (editor as any).cm; + if (cm) { + cm.dispatch({effects: setHighlightedLine.of({from: fromLine, to: toLine})}); + } + }; + + return ( + + lineNumbers?.highlightLines ? ( + + From line: + + To line: + + + + {lastClickedLine !== null && ( + Last clicked: line {lastClickedLine + 1} + )} + + ) : null + } + view={({className}) => ( + + )} + /> + ); + }, +); diff --git a/demo/src/stories/examples/markup-line-numbers/MarkupLineNumbers.stories.tsx b/demo/src/stories/examples/markup-line-numbers/MarkupLineNumbers.stories.tsx new file mode 100644 index 000000000..e8379ed5c --- /dev/null +++ b/demo/src/stories/examples/markup-line-numbers/MarkupLineNumbers.stories.tsx @@ -0,0 +1,61 @@ +import type {Meta, StoryObj} from '@storybook/react'; + +import {MarkupLineNumbersEditor} from './Editor'; + +const meta: Meta = { + title: 'Examples / Markup Line Numbers', + component: MarkupLineNumbersEditor, +}; + +export default meta; + +type Story = StoryObj; + +export const LineNumbersOnly: Story = { + args: { + lineNumbers: {enabled: true}, + }, +}; +LineNumbersOnly.storyName = 'Line Numbers Only'; + +export const HighlightLine: Story = { + args: { + lineNumbers: {enabled: true, highlightLines: true}, + }, +}; +HighlightLine.storyName = 'Highlight Line'; + +export const HighlightMultipleLines: Story = { + args: { + lineNumbers: { + enabled: true, + highlightLines: true, + initialSelectedLines: {from: 5, to: 10}, + }, + }, +}; +HighlightMultipleLines.storyName = 'Highlight Multiple Lines'; + +export const ScrollToLine: Story = { + args: { + lineNumbers: { + enabled: true, + scrollToLine: 20, + initialSelectedLines: {from: 20, to: 20}, + highlightLines: true, + }, + }, +}; +ScrollToLine.storyName = 'Scroll to Line'; + +export const AllFeatures: Story = { + args: { + lineNumbers: { + enabled: true, + highlightLines: true, + initialSelectedLines: {from: 20, to: 25}, + scrollToLine: 20, + }, + }, +}; +AllFeatures.storyName = 'All Features'; diff --git a/packages/editor/src/bundle/Editor.ts b/packages/editor/src/bundle/Editor.ts index d2d3b5f4c..894042ba3 100644 --- a/packages/editor/src/bundle/Editor.ts +++ b/packages/editor/src/bundle/Editor.ts @@ -62,6 +62,7 @@ export interface EditorInt readonly mdOptions: Readonly; readonly directiveSyntax: DirectiveSyntaxContext; readonly mobile: boolean; + readonly markupConfig: MarkupConfig; /** @internal used in demo for dev-tools */ readonly _wysiwygView?: PMEditorView; @@ -280,6 +281,7 @@ export class EditorImpl extends SafeEventEmitter implements EditorI directiveSyntax: this.directiveSyntax, receiver: this, searchPanel: this.#markupConfig.searchPanel, + lineNumbers: this.#markupConfig.lineNumbers, }), ); } @@ -310,6 +312,14 @@ export class EditorImpl extends SafeEventEmitter implements EditorI return this.#mobile; } + get initialScrollToLine(): number | undefined { + return this.#markupConfig.lineNumbers?.scrollToLine; + } + + get markupConfig(): MarkupConfig { + return this.#markupConfig; + } + constructor(opts: EditorOptions) { const {logger} = opts; diff --git a/packages/editor/src/bundle/MarkupEditorComponent.tsx b/packages/editor/src/bundle/MarkupEditorComponent.tsx index 420bef24a..8e40da86e 100644 --- a/packages/editor/src/bundle/MarkupEditorComponent.tsx +++ b/packages/editor/src/bundle/MarkupEditorComponent.tsx @@ -34,6 +34,14 @@ export const MarkupEditorComponent: React.FC = } }, [editor.markupEditor]); + // scroll to line on mount + useEffect(() => { + const scrollToLine = editor.markupConfig.lineNumbers?.scrollToLine; + if (editor.markupConfig.lineNumbers?.enabled && scrollToLine !== undefined) { + editor.moveCursor({line: scrollToLine}); + } + }, []); + return ( // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
void; + /** 0-based line number to scroll to on mount in markup mode */ + scrollToLine?: number; +} + export type MarkdownEditorMarkupConfig = { /** * Pass the rendering function to preview the markdown content. @@ -164,6 +177,15 @@ export type MarkdownEditorMarkupConfig = { * @default true */ searchPanel?: boolean; + /** + * Line numbers configuration for the markup editor. + * + * When `enabled` is true, line numbers gutter is shown. + * When `highlightLines` is true, line highlighting extension is enabled + * (includes clickable line numbers and highlight decoration). + * @default undefined + */ + lineNumbers?: MarkupLineNumbersConfig; }; // do not export this type diff --git a/packages/editor/src/index.ts b/packages/editor/src/index.ts index a7f503f91..93fed85d6 100644 --- a/packages/editor/src/index.ts +++ b/packages/editor/src/index.ts @@ -13,7 +13,15 @@ export * from './view'; export * from './utils'; export * from './bundle'; -export {DirectiveSyntaxFacet, ReactRendererFacet, getImageDimensions} from './markup'; +export type {LineRange, LineHighlightOptions} from './markup'; +export type {MarkupLineNumbersConfig} from './bundle/types'; +export { + DirectiveSyntaxFacet, + ReactRendererFacet, + getImageDimensions, + lineHighlight, + setHighlightedLine, +} from './markup'; export * as MarkupCommands from './markup/commands'; export * as MarkupHelpers from './markup/commands/helpers'; diff --git a/packages/editor/src/markup/codemirror/create.ts b/packages/editor/src/markup/codemirror/create.ts index 3055a0414..a5bd5a6ce 100644 --- a/packages/editor/src/markup/codemirror/create.ts +++ b/packages/editor/src/markup/codemirror/create.ts @@ -14,6 +14,7 @@ import { type EditorViewConfig, type KeyBinding, keymap, + lineNumbers, placeholder, tooltips, } from '@codemirror/view'; @@ -22,6 +23,7 @@ import {InputState} from 'src/utils/input-state'; import {ActionName} from '../../bundle/config/action-names'; import type {EventMap} from '../../bundle/events'; +import type {MarkupLineNumbersConfig} from '../../bundle/types'; import type {ReactRenderStorage} from '../../extensions'; import {type Logger2, globalLogger} from '../../logger'; import {Action as A, formatter as f} from '../../shortcuts'; @@ -54,6 +56,7 @@ import {type FileUploadHandler, FileUploadHandlerFacet} from './files-upload-fac import {FilesUploadPlugin} from './files-upload-plugin'; import {gravityHighlightStyle, gravityTheme} from './gravity'; import {MarkdownConverter} from './html-to-markdown/converters'; +import {lineHighlight} from './line-highlight'; import {LoggerFacet} from './logger-facet'; import {PairingCharactersExtension} from './pairing-chars'; import {ReactRendererFacet} from './react-facet'; @@ -95,6 +98,7 @@ export type CreateCodemirrorParams = { directiveSyntax: DirectiveSyntaxContext; preserveEmptyRows: boolean; searchPanel?: boolean; + lineNumbers?: MarkupLineNumbersConfig; }; export function createCodemirror(params: CreateCodemirrorParams) { @@ -328,6 +332,19 @@ export function createCodemirror(params: CreateCodemirrorParams) { extensions.push(tooltips(tooltipsConfig)); } + if (params.lineNumbers?.enabled) { + if (params.lineNumbers.highlightLines) { + extensions.push( + lineHighlight({ + initialRange: params.lineNumbers.initialSelectedLines, + onLineClick: params.lineNumbers.onLineClick, + }), + ); + } else { + extensions.push(lineNumbers()); + } + } + if (extraExtensions) { extensions.push(...extraExtensions); } diff --git a/packages/editor/src/markup/codemirror/gravity.ts b/packages/editor/src/markup/codemirror/gravity.ts index 16c871f4a..57aaaaeff 100644 --- a/packages/editor/src/markup/codemirror/gravity.ts +++ b/packages/editor/src/markup/codemirror/gravity.ts @@ -30,6 +30,15 @@ export const gravityTheme = EditorView.baseTheme({ '&.cm-focused': { outline: 'none', }, + '.cm-gutters': { + backgroundColor: 'var(--g-color-base-background)', + borderRight: '1px solid var(--g-color-line-generic)', + color: 'var(--g-color-text-hint)', + }, + '.cm-lineNumbers .cm-gutterElement': { + padding: '0 8px 0 4px', + minWidth: '20px', + }, '.cm-placeholder': { color: 'var(--g-color-text-secondary)', }, diff --git a/packages/editor/src/markup/codemirror/index.ts b/packages/editor/src/markup/codemirror/index.ts index 5726b1d1b..5f21db113 100644 --- a/packages/editor/src/markup/codemirror/index.ts +++ b/packages/editor/src/markup/codemirror/index.ts @@ -3,4 +3,6 @@ export {createCodemirror} from './create'; export {ReactRendererFacet} from './react-facet'; export {DirectiveSyntaxFacet} from './directive-facet'; export {getImageDimensions, IMG_MAX_HEIGHT} from './files-upload-plugin'; +export type {LineRange, LineHighlightOptions} from './line-highlight'; +export {lineHighlight, setHighlightedLine} from './line-highlight'; export type {YfmLangOptions} from './yfm'; diff --git a/packages/editor/src/markup/codemirror/line-highlight/extension.ts b/packages/editor/src/markup/codemirror/line-highlight/extension.ts new file mode 100644 index 000000000..4c94d938a --- /dev/null +++ b/packages/editor/src/markup/codemirror/line-highlight/extension.ts @@ -0,0 +1,132 @@ +import { + type Extension, + type Range, + RangeSet, + RangeSetBuilder, + StateEffect, + StateField, +} from '@codemirror/state'; +import { + Decoration, + type DecorationSet, + EditorView, + GutterMarker, + gutterLineClass, + lineNumbers, +} from '@codemirror/view'; + +export interface LineRange { + from: number; + to: number; +} + +export interface LineHighlightOptions { + initialRange?: LineRange; + onLineClick?: (line: number) => void; +} + +export const setHighlightedLine = StateEffect.define(); + +const highlightLineDecoration = Decoration.line({ + attributes: {class: 'cm-highlighted-line'}, +}); + +const highlightedGutterClass = new (class extends GutterMarker { + elementClass = 'cm-highlighted-gutter-line'; +})(); + +const highlightLineTheme = EditorView.baseTheme({ + '.cm-highlighted-line': { + backgroundColor: 'var(--g-color-base-selection) !important', + }, + '.cm-highlighted-gutter-line': { + backgroundColor: 'var(--g-color-base-selection) !important', + color: 'var(--g-color-text-primary) !important', + }, + '.cm-lineNumbers .cm-gutterElement': { + cursor: 'pointer', + }, +}); + +export function lineHighlight(options?: LineHighlightOptions): Extension { + const initialRange = options?.initialRange ?? null; + + const highlightedLineField = StateField.define({ + create: () => initialRange, + update(value, tr) { + for (const effect of tr.effects) { + if (effect.is(setHighlightedLine)) { + return effect.value; + } + } + return value; + }, + }); + + const highlightGutterDecoration = gutterLineClass.compute([highlightedLineField], (state) => { + const range = state.field(highlightedLineField); + + if (range === null) { + return RangeSet.empty; + } + + const builder = new RangeSetBuilder(); + + for (let line = range.from; line <= range.to; line++) { + try { + const cmLine = state.doc.line(line + 1); + builder.add(cmLine.from, cmLine.from, highlightedGutterClass); + } catch {} + } + + return builder.finish(); + }); + + const highlightLineDecorations = EditorView.decorations.compute( + [highlightedLineField], + (state): DecorationSet => { + const range = state.field(highlightedLineField); + + if (range === null) { + return Decoration.none; + } + + const decorations: Range[] = []; + + for (let line = range.from; line <= range.to; line++) { + try { + const cmLine = state.doc.line(line + 1); + decorations.push(highlightLineDecoration.range(cmLine.from)); + } catch {} + } + + return Decoration.set(decorations); + }, + ); + + const clickableLineNumbers = lineNumbers({ + domEventHandlers: { + click(view, line) { + const lineNum = view.state.doc.lineAt(line.from).number - 1; + const current = view.state.field(highlightedLineField); + const isSingleSelected = + current !== null && current.from === lineNum && current.to === lineNum; + view.dispatch({ + effects: setHighlightedLine.of( + isSingleSelected ? null : {from: lineNum, to: lineNum}, + ), + }); + options?.onLineClick?.(lineNum); + return true; + }, + }, + }); + + return [ + highlightedLineField, + highlightLineDecorations, + highlightGutterDecoration, + clickableLineNumbers, + highlightLineTheme, + ]; +} diff --git a/packages/editor/src/markup/codemirror/line-highlight/index.ts b/packages/editor/src/markup/codemirror/line-highlight/index.ts new file mode 100644 index 000000000..f2e856dc6 --- /dev/null +++ b/packages/editor/src/markup/codemirror/line-highlight/index.ts @@ -0,0 +1,2 @@ +export type {LineRange, LineHighlightOptions} from './extension'; +export {lineHighlight, setHighlightedLine} from './extension'; From 28f1cc4eb5a83aaa719fc8604d40d1aee61558a9 Mon Sep 17 00:00:00 2001 From: saxumcordis Date: Thu, 30 Apr 2026 14:59:09 +0300 Subject: [PATCH 2/2] squash --- packages/editor/src/bundle/types.ts | 18 ++++-------------- .../editor/src/markup/codemirror/create.ts | 2 +- .../codemirror/line-highlight/extension.ts | 5 +---- .../markup/codemirror/line-highlight/index.ts | 3 ++- .../markup/codemirror/line-highlight/types.ts | 17 +++++++++++++++++ 5 files changed, 25 insertions(+), 20 deletions(-) create mode 100644 packages/editor/src/markup/codemirror/line-highlight/types.ts diff --git a/packages/editor/src/bundle/types.ts b/packages/editor/src/bundle/types.ts index 584f7f5a6..0b3039c24 100644 --- a/packages/editor/src/bundle/types.ts +++ b/packages/editor/src/bundle/types.ts @@ -5,7 +5,9 @@ import type {ReactNode} from 'react'; import type {MarkupString} from '../common'; import type {EscapeConfig, Extension} from '../core'; import type {Logger2} from '../logger'; -import type {CreateCodemirrorParams, LineRange, YfmLangOptions} from '../markup'; +import type {CreateCodemirrorParams} from '../markup/codemirror/create'; +import type {MarkupLineNumbersConfig} from '../markup/codemirror/line-highlight/types'; +import type {YfmLangOptions} from '../markup/codemirror/yfm'; import type {FileUploadHandler} from '../utils'; import type {DirectiveSyntaxContext, DirectiveSyntaxOption} from '../utils/directive'; import type {ParseInsertedUrlAsImage} from '../utils/upload'; @@ -117,19 +119,7 @@ export type MarkdownEditorExperimentalOptions = { */ preserveMarkupFormatting?: boolean; }; - -export interface MarkupLineNumbersConfig { - /** Show line numbers in the gutter. Default: false */ - enabled?: boolean; - /** Enable line highlighting (clickable line numbers + highlight decoration). Default: false */ - highlightLines?: boolean; - /** Initial line range to highlight on mount (0-based, inclusive) */ - initialSelectedLines?: LineRange; - /** Called when user clicks on a line number (only when highlightLines is true). 0-based line number. */ - onLineClick?: (line: number) => void; - /** 0-based line number to scroll to on mount in markup mode */ - scrollToLine?: number; -} +export type {MarkupLineNumbersConfig}; export type MarkdownEditorMarkupConfig = { /** diff --git a/packages/editor/src/markup/codemirror/create.ts b/packages/editor/src/markup/codemirror/create.ts index a5bd5a6ce..e6c1af296 100644 --- a/packages/editor/src/markup/codemirror/create.ts +++ b/packages/editor/src/markup/codemirror/create.ts @@ -23,7 +23,6 @@ import {InputState} from 'src/utils/input-state'; import {ActionName} from '../../bundle/config/action-names'; import type {EventMap} from '../../bundle/events'; -import type {MarkupLineNumbersConfig} from '../../bundle/types'; import type {ReactRenderStorage} from '../../extensions'; import {type Logger2, globalLogger} from '../../logger'; import {Action as A, formatter as f} from '../../shortcuts'; @@ -57,6 +56,7 @@ import {FilesUploadPlugin} from './files-upload-plugin'; import {gravityHighlightStyle, gravityTheme} from './gravity'; import {MarkdownConverter} from './html-to-markdown/converters'; import {lineHighlight} from './line-highlight'; +import type {MarkupLineNumbersConfig} from './line-highlight/types'; import {LoggerFacet} from './logger-facet'; import {PairingCharactersExtension} from './pairing-chars'; import {ReactRendererFacet} from './react-facet'; diff --git a/packages/editor/src/markup/codemirror/line-highlight/extension.ts b/packages/editor/src/markup/codemirror/line-highlight/extension.ts index 4c94d938a..12971ebab 100644 --- a/packages/editor/src/markup/codemirror/line-highlight/extension.ts +++ b/packages/editor/src/markup/codemirror/line-highlight/extension.ts @@ -15,10 +15,7 @@ import { lineNumbers, } from '@codemirror/view'; -export interface LineRange { - from: number; - to: number; -} +import type {LineRange} from './types'; export interface LineHighlightOptions { initialRange?: LineRange; diff --git a/packages/editor/src/markup/codemirror/line-highlight/index.ts b/packages/editor/src/markup/codemirror/line-highlight/index.ts index f2e856dc6..e7f890a0c 100644 --- a/packages/editor/src/markup/codemirror/line-highlight/index.ts +++ b/packages/editor/src/markup/codemirror/line-highlight/index.ts @@ -1,2 +1,3 @@ -export type {LineRange, LineHighlightOptions} from './extension'; +export type {LineRange} from './types'; +export type {LineHighlightOptions} from './extension'; export {lineHighlight, setHighlightedLine} from './extension'; diff --git a/packages/editor/src/markup/codemirror/line-highlight/types.ts b/packages/editor/src/markup/codemirror/line-highlight/types.ts new file mode 100644 index 000000000..1640b84dc --- /dev/null +++ b/packages/editor/src/markup/codemirror/line-highlight/types.ts @@ -0,0 +1,17 @@ +export interface LineRange { + from: number; + to: number; +} + +export interface MarkupLineNumbersConfig { + /** Show line numbers in the gutter. Default: false */ + enabled?: boolean; + /** Enable line highlighting (clickable line numbers + highlight decoration). Default: false */ + highlightLines?: boolean; + /** Initial line range to highlight on mount (0-based, inclusive) */ + initialSelectedLines?: LineRange; + /** Called when user clicks on a line number (only when highlightLines is true). 0-based line number. */ + onLineClick?: (line: number) => void; + /** 0-based line number to scroll to on mount in markup mode */ + scrollToLine?: number; +}