@cascivo/editor 0.1.1 → 0.2.1
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/README.md +15 -0
- package/dist/editor.css +2 -0
- package/dist/index.d.ts +293 -0
- package/dist/index.js +1278 -0
- package/package.json +4 -3
- package/readme.body.md +15 -0
package/README.md
CHANGED
|
@@ -58,6 +58,21 @@ import { Highlight } from '@cascivo/editor'
|
|
|
58
58
|
Ships small, tree-shakeable grammars: `plaintext`, `json`, `javascript`, `typescript`, `css`,
|
|
59
59
|
`html`, `markdown`, `bash`. Register your own with `registerGrammar(grammar)`.
|
|
60
60
|
|
|
61
|
+
### Large documents
|
|
62
|
+
|
|
63
|
+
The editor edits long Markdown documents — generated docs, concatenated books, big notes —
|
|
64
|
+
well past the old ~5,000-line ceiling. On every render it tokenizes only the **visible window**
|
|
65
|
+
(O(viewport)) rather than the whole document, and an edit re-tokenizes only the **changed
|
|
66
|
+
suffix** until the grammar state reconverges. This is the same overlay + owned-tokenizer model,
|
|
67
|
+
with **zero new dependencies** and **byte-identical highlighting output** — just a persistent
|
|
68
|
+
per-line state index (`LineStateIndex`) feeding a viewport-scoped `tokenizeRange`. Scrolling and
|
|
69
|
+
typing stay smooth well past 50,000 lines.
|
|
70
|
+
|
|
71
|
+
One trade-off: with `wrap` (soft-wrap) on, row heights are variable so DOM windowing is disabled
|
|
72
|
+
and every row renders (O(n) render). Edits stay cheap, but for sustained editing of very large
|
|
73
|
+
documents (≳10,000 lines) disable `wrap`. See [`PERFORMANCE.md`](./PERFORMANCE.md) for the
|
|
74
|
+
measured before/after numbers and the deferred worker-offload boundary.
|
|
75
|
+
|
|
61
76
|
### Extending the editor
|
|
62
77
|
|
|
63
78
|
Three bounded seams — no plugin lifecycle, no transaction filters (use a full editor
|
package/dist/editor.css
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
@layer cascivo.component{._root_1m6ns_2{background:var(--cascivo-editor-bg);max-block-size:100%;color:var(--cascivo-editor-fg);border:1px solid var(--cascivo-editor-border);border-radius:var(--cascivo-radius-field);font-family:var(--cascivo-font-mono);font-size:var(--cascivo-text-sm);line-height:var(--cascivo-leading-normal);tab-size:var(--cascivo-editor-tab-size,2);display:flex;overflow:auto}._gutter_1m6ns_17{padding-block:var(--cascivo-space-3);background:var(--cascivo-editor-gutter-bg);color:var(--cascivo-editor-gutter-fg);text-align:end;-webkit-user-select:none;user-select:none;flex:none;padding-inline-start:var(--cascivo-space-3);padding-inline-end:var(--cascivo-space-2)}._gutterLine_1m6ns_28{min-block-size:1lh;display:block}._gutterActive_1m6ns_35{inset-inline:0;top:var(--cascivo-space-3);block-size:1lh;transform:translateY(calc(var(--cascivo-editor-caret-line,0) * 1lh));background:var(--cascivo-editor-gutter-active,var(--cascivo-editor-current-line));pointer-events:none;position:absolute}._pre_1m6ns_45{min-inline-size:0;padding-block:var(--cascivo-space-3);padding-inline:var(--cascivo-space-3);white-space:pre;font:inherit;tab-size:inherit;flex:auto;margin-block:0;margin-inline:0}._code_1m6ns_57{font:inherit}._line_1m6ns_62{min-block-size:1lh;display:block}._root_1m6ns_2[data-wrap=true] ._pre_1m6ns_45{white-space:pre-wrap;word-break:break-word}._root_1m6ns_2[data-line-numbers=false] ._gutter_1m6ns_17{display:none}._plain_1m6ns_77{color:var(--cascivo-editor-fg)}._keyword_1m6ns_80{color:var(--cascivo-editor-syntax-keyword)}._string_1m6ns_83{color:var(--cascivo-editor-syntax-string)}._number_1m6ns_86{color:var(--cascivo-editor-syntax-number)}._comment_1m6ns_89{color:var(--cascivo-editor-syntax-comment);font-style:italic}._function_1m6ns_93{color:var(--cascivo-editor-syntax-function)}._type_1m6ns_96{color:var(--cascivo-editor-syntax-type)}._operator_1m6ns_99{color:var(--cascivo-editor-syntax-operator)}._punctuation_1m6ns_102{color:var(--cascivo-editor-syntax-punctuation)}._variable_1m6ns_105{color:var(--cascivo-editor-syntax-variable)}._tag_1m6ns_108{color:var(--cascivo-editor-syntax-tag)}._attr_1m6ns_111{color:var(--cascivo-editor-syntax-attr)}._regexp_1m6ns_114{color:var(--cascivo-editor-syntax-regexp)}._boolean_1m6ns_117{color:var(--cascivo-editor-syntax-boolean)}._property_1m6ns_120{color:var(--cascivo-editor-syntax-property)}._match_1m6ns_125{background:var(--cascivo-editor-match,#7dd3fc47);border-radius:2px}._matchCurrent_1m6ns_129{background:var(--cascivo-editor-match-current,#facc1580);border-radius:2px}._bracketMatch_1m6ns_133{outline:1px solid var(--cascivo-editor-bracket,currentColor);border-radius:2px}._root_18xci_2{background:var(--cascivo-editor-bg);color:var(--cascivo-editor-fg);border:1px solid var(--cascivo-editor-border);border-radius:var(--cascivo-radius-field);font-family:var(--cascivo-font-mono);font-size:var(--cascivo-text-sm);line-height:var(--cascivo-leading-normal);tab-size:var(--cascivo-editor-tab-size,2);transition:box-shadow var(--cascivo-duration-150) var(--cascivo-ease-out);display:flex;position:relative;overflow:hidden}._root_18xci_2:has(._textarea_18xci_18:focus-visible){box-shadow:var(--cascivo-focus-ring);border-color:var(--cascivo-color-accent)}._gutter_18xci_23{padding-block:var(--cascivo-space-3);background:var(--cascivo-editor-gutter-bg);color:var(--cascivo-editor-gutter-fg);text-align:end;-webkit-user-select:none;user-select:none;flex:none;padding-inline-start:var(--cascivo-space-3);padding-inline-end:var(--cascivo-space-2);position:relative;overflow:hidden}._codeArea_18xci_36{flex:auto;min-inline-size:0;position:relative}._pre_18xci_43{padding-block:var(--cascivo-space-3);padding-inline:var(--cascivo-space-3);white-space:pre;pointer-events:none;font:inherit;tab-size:inherit;margin-block:0;margin-inline:0;position:absolute;inset:0;overflow:hidden}._currentLine_18xci_58{inset-inline:0;top:var(--cascivo-space-3);block-size:1lh;transform:translateY(calc(var(--cascivo-editor-caret-line,0) * 1lh));background:var(--cascivo-editor-current-line);pointer-events:none;position:absolute}._textarea_18xci_18{block-size:100%;inline-size:100%;padding-block:var(--cascivo-space-3);padding-inline:var(--cascivo-space-3);resize:none;color:#0000;caret-color:var(--cascivo-editor-fg);white-space:pre;font:inherit;tab-size:inherit;background:0 0;border:0;outline:none;margin-block:0;margin-inline:0;position:absolute;inset:0;overflow:auto}._textarea_18xci_18::placeholder{color:var(--cascivo-editor-gutter-fg)}._textarea_18xci_18::selection{background:var(--cascivo-editor-selection)}._textarea_18xci_18:disabled{cursor:not-allowed;opacity:var(--cascivo-disabled-opacity)}._root_18xci_2[data-wrap=true] ._pre_18xci_43,._root_18xci_2[data-wrap=true] ._textarea_18xci_18{white-space:pre-wrap;word-break:break-word}._root_18xci_2[data-line-numbers=false] ._gutter_18xci_23{display:none}@media (pointer:coarse){._root_18xci_2{min-block-size:var(--cascivo-target-min-coarse,2.75rem)}}@media (prefers-reduced-motion:reduce){._root_18xci_2{transition:none}}._panel_hawkv_2{z-index:2;gap:var(--cascivo-space-1);padding:var(--cascivo-space-2);background:var(--cascivo-editor-bg);border:1px solid var(--cascivo-editor-border);border-radius:var(--cascivo-radius-field);box-shadow:var(--cascivo-shadow-md,0 4px 12px #0003);font-family:var(--cascivo-font-sans);font-size:var(--cascivo-text-sm);color:var(--cascivo-editor-fg);flex-direction:column;display:flex;position:absolute;inset-block-start:var(--cascivo-space-2);inset-inline-end:var(--cascivo-space-2)}._row_hawkv_20{align-items:center;gap:var(--cascivo-space-1);display:flex}._input_hawkv_26{min-inline-size:8rem;padding-block:var(--cascivo-space-1);padding-inline:var(--cascivo-space-2);background:var(--cascivo-editor-gutter-bg,transparent);color:inherit;border:1px solid var(--cascivo-editor-border);border-radius:var(--cascivo-radius-field);font:inherit;flex:auto}._input_hawkv_26:focus-visible{border-color:var(--cascivo-color-accent);box-shadow:var(--cascivo-focus-ring);outline:none}._count_hawkv_44{padding-inline:var(--cascivo-space-1);color:var(--cascivo-editor-gutter-fg);font-variant-numeric:tabular-nums;white-space:nowrap;flex:none}._button_hawkv_52,._toggle_hawkv_53{min-inline-size:1.75rem;padding-block:var(--cascivo-space-1);padding-inline:var(--cascivo-space-2);color:inherit;border-radius:var(--cascivo-radius-field);font:inherit;cursor:pointer;background:0 0;border:1px solid #0000;justify-content:center;align-items:center;line-height:1;display:inline-flex}._button_hawkv_52:hover,._toggle_hawkv_53:hover{background:var(--cascivo-editor-current-line)}._button_hawkv_52:focus-visible,._toggle_hawkv_53:focus-visible{box-shadow:var(--cascivo-focus-ring);outline:none}._active_hawkv_80{border-color:var(--cascivo-color-accent);color:var(--cascivo-color-accent)}@media (pointer:coarse){._button_hawkv_52,._toggle_hawkv_53,._input_hawkv_26{min-block-size:var(--cascivo-target-min-coarse,2.75rem)}}}@media (forced-colors:active){._root_18xci_2{border:1px solid fieldtext}._pre_18xci_43{display:none}._textarea_18xci_18{color:canvastext;caret-color:canvastext}}
|
|
2
|
+
/*$vite$:1*/
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { HTMLAttributes, KeyboardEvent, Ref, TextareaHTMLAttributes } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Token classes the syntax palette colors. Every kind maps to a
|
|
5
|
+
* `--cascivo-editor-syntax-<kind>` custom property (see @cascivo/tokens). `plain`
|
|
6
|
+
* is uncolored text (whitespace, identifiers a grammar chose not to classify).
|
|
7
|
+
*/
|
|
8
|
+
type TokenKind = 'keyword' | 'string' | 'number' | 'comment' | 'function' | 'type' | 'operator' | 'punctuation' | 'variable' | 'tag' | 'attr' | 'regexp' | 'boolean' | 'property' | 'plain';
|
|
9
|
+
/** A single highlighted span. The concatenation of a line's token values is the line. */
|
|
10
|
+
interface Token {
|
|
11
|
+
kind: TokenKind;
|
|
12
|
+
value: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Opaque per-grammar continuation carried from one line to the next so multi-line
|
|
16
|
+
* constructs (block comments, template literals, fenced code) continue correctly.
|
|
17
|
+
* Each grammar owns the meaning of its own state strings; `'default'` is the start.
|
|
18
|
+
*/
|
|
19
|
+
type GrammarState = string;
|
|
20
|
+
/** Result of tokenizing one line: its spans plus the state to feed the next line. */
|
|
21
|
+
interface LineTokens {
|
|
22
|
+
tokens: Token[];
|
|
23
|
+
state: GrammarState;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* A language grammar. Pure and deterministic: `tokenizeLine` must depend only on
|
|
27
|
+
* `(line, state)` and must be lossless (the token values concatenate back to `line`).
|
|
28
|
+
*/
|
|
29
|
+
interface Grammar {
|
|
30
|
+
name: string;
|
|
31
|
+
initialState: GrammarState;
|
|
32
|
+
tokenizeLine(line: string, state: GrammarState): LineTokens;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* A persistent, mutable index of per-line grammar **end-states** — the state
|
|
36
|
+
* carried *after* each line, which is exactly the start-state of the next line.
|
|
37
|
+
* It lets a windowed tokenizer obtain the start-state of any visible line as a
|
|
38
|
+
* read (or a lazy compute) instead of re-walking the whole document from line 0
|
|
39
|
+
* on every render.
|
|
40
|
+
*
|
|
41
|
+
* `endStates[i]` is the state after line `i`; the start-state of line `i` is
|
|
42
|
+
* therefore `endStates[i-1]` (line 0 starts at `grammar.initialState`). The array
|
|
43
|
+
* is always a contiguous prefix `[0, length-1]` — methods never leave gaps.
|
|
44
|
+
*
|
|
45
|
+
* Framework-free: pure data + methods, no React, no signals. The component holds
|
|
46
|
+
* one in a `useRef` as mutable infrastructure (like the undo/redo history handle).
|
|
47
|
+
*/
|
|
48
|
+
interface LineStateIndex {
|
|
49
|
+
/** The grammar this index threads state for. */
|
|
50
|
+
readonly grammar: Grammar;
|
|
51
|
+
/** Count of known end-states (always a contiguous prefix from line 0). */
|
|
52
|
+
readonly length: number;
|
|
53
|
+
/** Lazily compute + memoize end-states up to (and including) line `upto`. */
|
|
54
|
+
ensure(lines: readonly string[], upto: number): void;
|
|
55
|
+
/** The threaded start-state for line `i` (ensures the prefix as a side effect). */
|
|
56
|
+
startStateOf(lines: readonly string[], i: number): GrammarState;
|
|
57
|
+
/** Record the end-state of line `i` (append at the prefix end, or overwrite). */
|
|
58
|
+
setEndState(i: number, state: GrammarState): void;
|
|
59
|
+
/** Drop the end-state of `line` and everything after it (called on an edit). */
|
|
60
|
+
invalidateFrom(line: number): void;
|
|
61
|
+
}
|
|
62
|
+
/** Create an empty {@link LineStateIndex} for `grammar`. */
|
|
63
|
+
declare function createLineStateIndex(grammar: Grammar): LineStateIndex;
|
|
64
|
+
/** Result of tokenizing one line: spans plus the continuation state. */
|
|
65
|
+
interface TokenizeResult {
|
|
66
|
+
tokens: Token[];
|
|
67
|
+
endState: GrammarState;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Tokenize a single line, restartable from `startState`, memoized. Pure from the
|
|
71
|
+
* caller's perspective: identical `(grammar, line, startState)` yield identical
|
|
72
|
+
* results (a cache hit returns the previously-computed value).
|
|
73
|
+
*/
|
|
74
|
+
declare function tokenize(grammar: Grammar, line: string, startState: GrammarState): TokenizeResult;
|
|
75
|
+
/**
|
|
76
|
+
* Tokenize a whole document, threading each line's `endState` into the next so
|
|
77
|
+
* block comments / template literals / fenced code continue across lines.
|
|
78
|
+
* Returns one token array per line. The per-line memo makes a single-line edit
|
|
79
|
+
* re-tokenize only the changed line(s) until the start-state reconverges.
|
|
80
|
+
*/
|
|
81
|
+
declare function tokenizeDocument(grammar: Grammar, text: string): Token[][];
|
|
82
|
+
/**
|
|
83
|
+
* Tokenize only the line range `[from, to)`, using a {@link LineStateIndex} for
|
|
84
|
+
* the window's seed state instead of re-walking the document from line 0. The
|
|
85
|
+
* index supplies the start-state of `from` (computing the prefix lazily, once),
|
|
86
|
+
* and each line's fresh end-state is written back into the index so later windows
|
|
87
|
+
* reuse it — making per-render work O(viewport), not O(document).
|
|
88
|
+
*
|
|
89
|
+
* Returns a plain `Token[][]` of length `to - from`: element `j` is line
|
|
90
|
+
* `from + j`. The consumer knows the window starts at `from`, so alignment with
|
|
91
|
+
* the rendered rows stays trivial. Output is byte-for-byte identical to
|
|
92
|
+
* `tokenizeDocument(grammar, lines.join('\n')).slice(from, to)`.
|
|
93
|
+
*/
|
|
94
|
+
declare function tokenizeRange(grammar: Grammar, lines: readonly string[], from: number, to: number, index: LineStateIndex): Token[][];
|
|
95
|
+
/** Register (or replace) a grammar under its `name`. */
|
|
96
|
+
declare function registerGrammar(grammar: Grammar): void;
|
|
97
|
+
/** Resolve a grammar by name, falling back to `plaintext` for unknown languages. */
|
|
98
|
+
declare function getGrammar(name?: string): Grammar;
|
|
99
|
+
/** List the names of all currently-registered grammars. */
|
|
100
|
+
declare function listGrammars(): string[];
|
|
101
|
+
/**
|
|
102
|
+
* One tokenizer rule. `match` is anchored at the current scan position (compiled
|
|
103
|
+
* sticky internally). On a non-empty match the matched text becomes a token of
|
|
104
|
+
* `kind`, the position advances, and the state transitions via `push`/`pop`.
|
|
105
|
+
*/
|
|
106
|
+
interface Rule {
|
|
107
|
+
match: RegExp;
|
|
108
|
+
kind: TokenKind;
|
|
109
|
+
/** Switch to this state after matching (for entering multi-line constructs). */
|
|
110
|
+
push?: string;
|
|
111
|
+
/** Return to the `default` state after matching (for leaving them). */
|
|
112
|
+
pop?: boolean;
|
|
113
|
+
}
|
|
114
|
+
interface RuleSpec {
|
|
115
|
+
name: string;
|
|
116
|
+
/** Per-state ordered rule lists. Must include a `default` state. */
|
|
117
|
+
states: Record<string, Rule[]>;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Build a {@link Grammar} from ordered, state-keyed regex rules — a tiny
|
|
121
|
+
* Monarch/Prism-style engine. At each position, the first rule in the current
|
|
122
|
+
* state whose sticky regex matches wins; unmatched characters accumulate into
|
|
123
|
+
* `plain` tokens, so the tokenizer is always lossless. State carries across
|
|
124
|
+
* lines for block comments, template literals, and fenced code.
|
|
125
|
+
*/
|
|
126
|
+
declare function createRuleGrammar(spec: RuleSpec): Grammar;
|
|
127
|
+
/** The trivial fallback grammar: one uncolored `plain` token per line. */
|
|
128
|
+
declare const plaintext: Grammar;
|
|
129
|
+
/**
|
|
130
|
+
* JSON: property keys vs string values, numbers, booleans/null, punctuation.
|
|
131
|
+
* A string immediately followed by a colon is a property key; other strings are
|
|
132
|
+
* values.
|
|
133
|
+
*/
|
|
134
|
+
declare const json: Grammar;
|
|
135
|
+
/** JavaScript: keywords, strings (incl. multi-line template literals), numbers,
|
|
136
|
+
* line + block comments (state carry), function calls, identifiers, operators. */
|
|
137
|
+
declare const javascript: Grammar;
|
|
138
|
+
declare const typescript: Grammar;
|
|
139
|
+
/**
|
|
140
|
+
* CSS: at-rules, selectors, property names, values, numbers/units, strings, and
|
|
141
|
+
* `/* *\/` block comments (state carried across lines).
|
|
142
|
+
*/
|
|
143
|
+
declare const css: Grammar;
|
|
144
|
+
/**
|
|
145
|
+
* HTML: tags, attribute names/values, `<!-- -->` comments (state carried across
|
|
146
|
+
* lines), and text. Inside a tag, attributes and strings are colored until `>`.
|
|
147
|
+
*/
|
|
148
|
+
declare const html: Grammar;
|
|
149
|
+
/**
|
|
150
|
+
* Markdown for a notes/wiki surface: ATX headings, horizontal rules, task &
|
|
151
|
+
* ordered/unordered lists, blockquotes, inline + fenced code (state carried across
|
|
152
|
+
* lines), strong/emphasis/strikethrough, and links/images. Line-based, so setext
|
|
153
|
+
* headings and CommonMark tables/footnotes are intentionally out of scope; the
|
|
154
|
+
* fence body is colored uniformly (embedded-language highlight is a future step).
|
|
155
|
+
*/
|
|
156
|
+
declare const markdown: Grammar;
|
|
157
|
+
/** Bash: comments, strings, keywords, variables (`$VAR` / `${...}`), operators. */
|
|
158
|
+
declare const bash: Grammar;
|
|
159
|
+
/**
|
|
160
|
+
* A visual decoration: a `[start, end)` column range on a given line tagged with a
|
|
161
|
+
* CSS class. The overlay layer splits token spans at decoration boundaries and adds
|
|
162
|
+
* the class to the covered slice. Used internally for search matches and bracket
|
|
163
|
+
* pairs, and exposed publicly via the editor's `decorations` prop.
|
|
164
|
+
*/
|
|
165
|
+
interface Decoration {
|
|
166
|
+
line: number;
|
|
167
|
+
start: number;
|
|
168
|
+
end: number;
|
|
169
|
+
className: string;
|
|
170
|
+
}
|
|
171
|
+
/** What a {@link Command} receives: the live textarea, the originating event, and the commit fn. */
|
|
172
|
+
interface CommandContext {
|
|
173
|
+
textarea: HTMLTextAreaElement;
|
|
174
|
+
event: KeyboardEvent<HTMLTextAreaElement>;
|
|
175
|
+
/** Commit a new document value through the editor's controllable signal + history. */
|
|
176
|
+
setText: (value: string) => void;
|
|
177
|
+
}
|
|
178
|
+
/** A keymap command. Returns `true` when it handled the event (the caller then prevents default). */
|
|
179
|
+
type Command = (ctx: CommandContext) => boolean;
|
|
180
|
+
/** A public keymap: chord string → command. Merged over the built-ins (user wins). */
|
|
181
|
+
type KeyMap = Record<string, Command>;
|
|
182
|
+
/**
|
|
183
|
+
* Per-instance theme overrides — a partial map of `--cascivo-editor-*` custom
|
|
184
|
+
* properties spread onto the editor root. Swapping it re-themes live (Zen mode),
|
|
185
|
+
* while global `data-theme` remains the default.
|
|
186
|
+
*/
|
|
187
|
+
type EditorTheme = Partial<Record<`--cascivo-editor-${string}`, string>>;
|
|
188
|
+
/**
|
|
189
|
+
* Imperative handle (via `ref`) for callers that drive their own transactions —
|
|
190
|
+
* a remote pull, a programmatic insert, a reset. Edits route through the same
|
|
191
|
+
* history as keyboard edits, so they stay undoable.
|
|
192
|
+
*/
|
|
193
|
+
interface CodeEditorHandle {
|
|
194
|
+
/** Replace `[from, to)` with `text` (an undoable edit), leaving the caret after it. */
|
|
195
|
+
applyEdit(range: {
|
|
196
|
+
from: number;
|
|
197
|
+
to: number;
|
|
198
|
+
}, text: string): void;
|
|
199
|
+
/** Current selection offsets. */
|
|
200
|
+
getSelection(): {
|
|
201
|
+
start: number;
|
|
202
|
+
end: number;
|
|
203
|
+
};
|
|
204
|
+
/** Focus the editing surface. */
|
|
205
|
+
focus(): void;
|
|
206
|
+
/** Undo / redo the owned history. */
|
|
207
|
+
undo(): void;
|
|
208
|
+
redo(): void;
|
|
209
|
+
/** Open the find panel. */
|
|
210
|
+
openFind(): void;
|
|
211
|
+
}
|
|
212
|
+
interface CodeEditorProps extends Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, 'value' | 'defaultValue' | 'onChange' | 'wrap'> {
|
|
213
|
+
/** Controlled value. */
|
|
214
|
+
value?: string;
|
|
215
|
+
/** Initial value for uncontrolled use. */
|
|
216
|
+
defaultValue?: string;
|
|
217
|
+
/** Called with the new text on every edit. */
|
|
218
|
+
onValueChange?: (value: string) => void;
|
|
219
|
+
/** Grammar name (defaults to `plaintext`; unknown names fall back to it). */
|
|
220
|
+
language?: string;
|
|
221
|
+
/** Show the line-number gutter (default true). */
|
|
222
|
+
lineNumbers?: boolean;
|
|
223
|
+
/** Spaces per tab stop (default 2). */
|
|
224
|
+
tabSize?: number;
|
|
225
|
+
/** Insert spaces (default) vs a literal tab on Tab. */
|
|
226
|
+
insertSpaces?: boolean;
|
|
227
|
+
/** Soft-wrap long lines instead of scrolling horizontally (default false). */
|
|
228
|
+
wrap?: boolean;
|
|
229
|
+
/**
|
|
230
|
+
* Render only the visible lines for large documents. Defaults to auto
|
|
231
|
+
* (on above ~1000 lines); disabled when `wrap` makes row heights variable.
|
|
232
|
+
*/
|
|
233
|
+
virtualize?: boolean;
|
|
234
|
+
/** Accessible label for the editor (defaults to the i18n "Code editor"). */
|
|
235
|
+
label?: string;
|
|
236
|
+
/** Called with the current value on `Mod-S` (the browser save dialog is suppressed). */
|
|
237
|
+
onSave?: (value: string) => void;
|
|
238
|
+
/** Extra key bindings, merged over the built-ins (user wins on the same chord). */
|
|
239
|
+
keymap?: KeyMap;
|
|
240
|
+
/** Extra decorations — offset ranges → CSS class — merged with internal ones. */
|
|
241
|
+
decorations?: readonly Decoration[] | ((value: string) => readonly Decoration[]);
|
|
242
|
+
/** Per-instance `--cascivo-editor-*` overrides; swapping it re-themes live. */
|
|
243
|
+
theme?: EditorTheme;
|
|
244
|
+
/** Highlight the bracket matching the one adjacent to the caret (default false). */
|
|
245
|
+
bracketMatching?: boolean;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* A lightweight code editor: a native `<textarea>` overlaid on a syntax-highlighted
|
|
249
|
+
* `Highlight` layer. The browser owns editing/caret/IME/undo/a11y; JS handles only
|
|
250
|
+
* tokenizing, scroll-sync, and Tab/Shift-Tab indent. Signal-driven, no banned hooks.
|
|
251
|
+
*
|
|
252
|
+
* Performance: tokenization is per-line memoized, the highlight layer is
|
|
253
|
+
* rAF-debounced (typing never blocks), and large documents window to the visible
|
|
254
|
+
* range while the textarea always holds the full text.
|
|
255
|
+
*/
|
|
256
|
+
declare const CodeEditor: import("react").ForwardRefExoticComponent<CodeEditorProps & import("react").RefAttributes<CodeEditorHandle>>;
|
|
257
|
+
interface HighlightProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
|
|
258
|
+
/** Code to render. */
|
|
259
|
+
value: string;
|
|
260
|
+
/** Grammar name (defaults to `plaintext`; unknown names fall back to it). */
|
|
261
|
+
language?: string;
|
|
262
|
+
/** Show the line-number gutter. */
|
|
263
|
+
lineNumbers?: boolean;
|
|
264
|
+
/** Soft-wrap long lines instead of scrolling horizontally. */
|
|
265
|
+
wrap?: boolean;
|
|
266
|
+
/** Spaces per tab stop (default 2). */
|
|
267
|
+
tabSize?: number;
|
|
268
|
+
/** Accessible label for the read-only code block. */
|
|
269
|
+
label?: string;
|
|
270
|
+
/** Ref to the scrollable `<pre>` (used by `CodeEditor` for scroll-sync). */
|
|
271
|
+
preRef?: Ref<HTMLPreElement>;
|
|
272
|
+
/** Ref to the gutter column (used by `CodeEditor` for scroll-sync). */
|
|
273
|
+
gutterRef?: Ref<HTMLDivElement>;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Read-only, syntax-highlighted `<pre><code>` rendered from the owned tokenizer.
|
|
277
|
+
* The same render layer `CodeEditor` overlays its `<textarea>` on. Signal-safe,
|
|
278
|
+
* themeable, and accessible — no banned hooks.
|
|
279
|
+
*/
|
|
280
|
+
declare function Highlight({
|
|
281
|
+
value,
|
|
282
|
+
language,
|
|
283
|
+
lineNumbers,
|
|
284
|
+
wrap,
|
|
285
|
+
tabSize,
|
|
286
|
+
label,
|
|
287
|
+
className,
|
|
288
|
+
preRef,
|
|
289
|
+
gutterRef,
|
|
290
|
+
style,
|
|
291
|
+
...rest
|
|
292
|
+
}: HighlightProps): import("react").JSX.Element;
|
|
293
|
+
export { CodeEditor, type CodeEditorHandle, type CodeEditorProps, type Command, type CommandContext, type Decoration, type EditorTheme, type Grammar, type GrammarState, Highlight, type HighlightProps, type KeyMap, type LineStateIndex, type LineTokens, type Rule, type RuleSpec, type Token, type TokenKind, type TokenizeResult, bash, createLineStateIndex, createRuleGrammar, css, getGrammar, html, javascript, json, listGrammars, markdown, plaintext, registerGrammar, tokenize, tokenizeDocument, tokenizeRange, typescript };
|