@cascivo/editor 0.0.1 → 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 urbanisierung
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -16,7 +16,11 @@
16
16
 
17
17
  ---
18
18
 
19
- A lightweight, CSS-native, signal-driven code editor for cascivo. `CodeEditor` overlays a native `<textarea>` on a syntax-highlighted `<pre>`, so the browser owns the caret, selection, IME, undo, and accessibility — JS is limited to a tiny owned tokenizer and scroll-sync. `Highlight` is the read-only renderer for snippets and docs. Zero runtime dependencies, themeable through the cascivo token system.
19
+ A lightweight, CSS-native, signal-driven code editor for cascivo. `CodeEditor` overlays a native `<textarea>` on a syntax-highlighted `<pre>`, so the browser owns the caret, selection, IME, and accessibility — JS adds an owned tokenizer, scroll-sync, and a thin layer of editing affordances. `Highlight` is the read-only renderer for snippets and docs. Zero runtime dependencies, themeable through the cascivo token system.
20
+
21
+ Beyond highlighting it provides the essentials for editing real documents: **owned undo/redo** (`Mod-Z` / `Mod-Shift-Z`) that survives programmatic `value` changes, **selection-preserving, echo-safe controlled sync** (external/remote updates don't jump the caret), **find & replace** (`Mod-F`), a **`Mod-S` save** hook, **per-instance theming** that can switch live, **bracket matching**, an **active-line gutter**, and an imperative **`CodeEditorHandle`**.
22
+
23
+ It is deliberately not a full editor engine: no LSP/IntelliSense, multi-cursor, code folding, minimap, or vim mode — reach for a full framework (Monaco/CodeMirror) if you need those.
20
24
 
21
25
  ## Install
22
26
 
@@ -54,6 +58,32 @@ import { Highlight } from '@cascivo/editor'
54
58
  Ships small, tree-shakeable grammars: `plaintext`, `json`, `javascript`, `typescript`, `css`,
55
59
  `html`, `markdown`, `bash`. Register your own with `registerGrammar(grammar)`.
56
60
 
61
+ ### Extending the editor
62
+
63
+ Three bounded seams — no plugin lifecycle, no transaction filters (use a full editor
64
+ framework if you need those):
65
+
66
+ - **Key bindings** — pass a `keymap` of `chord → command`. Chords use `Mod` for
67
+ Cmd/Ctrl (e.g. `'Mod-s'`, `'Mod-Shift-z'`, `'Shift-Tab'`); a command returns `true`
68
+ when it handled the event. User bindings merge over (and override) the built-ins.
69
+ - **Decorations** — pass `decorations` (an array, or `(value) => Decoration[]`) to tag
70
+ `{ line, start, end, className }` column ranges; they render as extra classes in the
71
+ highlight layer (the same seam find and bracket-matching use).
72
+ - **Grammars** — register a language with `registerGrammar(grammar)`.
73
+
74
+ ```tsx
75
+ <CodeEditor
76
+ language="markdown"
77
+ onSave={(value) => save(value)} // Mod-S
78
+ keymap={{
79
+ 'Mod-/': ({ textarea, setText }) => {
80
+ /* toggle comment */ return true
81
+ },
82
+ }}
83
+ decorations={(value) => findTodos(value)}
84
+ />
85
+ ```
86
+
57
87
  ### React apps must subscribe to signals
58
88
 
59
89
  `CodeEditor` and `Highlight` are signal-driven. In a plain React app (no Babel signals transform),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cascivo/editor",
3
- "version": "0.0.1",
3
+ "version": "0.1.1",
4
4
  "private": false,
5
5
  "description": "Lightweight CSS-native code editor — native textarea overlay + owned zero-dependency tokenizer",
6
6
  "keywords": [
@@ -42,31 +42,31 @@
42
42
  "access": "public",
43
43
  "provenance": true
44
44
  },
45
- "scripts": {
46
- "build": "vp build && node scripts/flatten-types.mjs && node scripts/check-types-flat.mjs",
47
- "check": "tsc --noEmit",
48
- "test": "vp test"
49
- },
50
45
  "dependencies": {
51
- "@cascivo/core": "workspace:^",
52
- "@cascivo/i18n": "workspace:^"
46
+ "@cascivo/core": "^0.1.3",
47
+ "@cascivo/i18n": "^0.1.6"
53
48
  },
54
49
  "devDependencies": {
55
- "@testing-library/jest-dom": "catalog:",
56
- "@testing-library/react": "catalog:",
57
- "@testing-library/user-event": "catalog:",
58
- "@types/react": "catalog:",
59
- "@types/react-dom": "catalog:",
60
- "fast-check": "catalog:",
61
- "jsdom": "catalog:",
62
- "react": "catalog:",
63
- "react-dom": "catalog:",
64
- "typescript": "catalog:",
65
- "vite-plus": "catalog:"
50
+ "@testing-library/jest-dom": "^6.9.1",
51
+ "@testing-library/react": "^16.3.2",
52
+ "@testing-library/user-event": "^14",
53
+ "@types/react": "^19.2.17",
54
+ "@types/react-dom": "^19.2.3",
55
+ "fast-check": "^4.0.0",
56
+ "jsdom": "^29",
57
+ "react": "^19.2.7",
58
+ "react-dom": "^19.2.7",
59
+ "typescript": "^5",
60
+ "vite-plus": "^0.2.1"
66
61
  },
67
62
  "peerDependencies": {
68
63
  "@preact/signals-react": ">=2.0.0",
69
64
  "react": ">=18.0.0",
70
65
  "react-dom": ">=18.0.0"
66
+ },
67
+ "scripts": {
68
+ "build": "vp build && node scripts/flatten-types.mjs && node scripts/check-types-flat.mjs",
69
+ "check": "tsc --noEmit",
70
+ "test": "vp test"
71
71
  }
72
- }
72
+ }
package/readme.body.md CHANGED
@@ -1,4 +1,8 @@
1
- A lightweight, CSS-native, signal-driven code editor for cascivo. `CodeEditor` overlays a native `<textarea>` on a syntax-highlighted `<pre>`, so the browser owns the caret, selection, IME, undo, and accessibility — JS is limited to a tiny owned tokenizer and scroll-sync. `Highlight` is the read-only renderer for snippets and docs. Zero runtime dependencies, themeable through the cascivo token system.
1
+ A lightweight, CSS-native, signal-driven code editor for cascivo. `CodeEditor` overlays a native `<textarea>` on a syntax-highlighted `<pre>`, so the browser owns the caret, selection, IME, and accessibility — JS adds an owned tokenizer, scroll-sync, and a thin layer of editing affordances. `Highlight` is the read-only renderer for snippets and docs. Zero runtime dependencies, themeable through the cascivo token system.
2
+
3
+ Beyond highlighting it provides the essentials for editing real documents: **owned undo/redo** (`Mod-Z` / `Mod-Shift-Z`) that survives programmatic `value` changes, **selection-preserving, echo-safe controlled sync** (external/remote updates don't jump the caret), **find & replace** (`Mod-F`), a **`Mod-S` save** hook, **per-instance theming** that can switch live, **bracket matching**, an **active-line gutter**, and an imperative **`CodeEditorHandle`**.
4
+
5
+ It is deliberately not a full editor engine: no LSP/IntelliSense, multi-cursor, code folding, minimap, or vim mode — reach for a full framework (Monaco/CodeMirror) if you need those.
2
6
 
3
7
  ## Install
4
8
 
@@ -36,6 +40,32 @@ import { Highlight } from '@cascivo/editor'
36
40
  Ships small, tree-shakeable grammars: `plaintext`, `json`, `javascript`, `typescript`, `css`,
37
41
  `html`, `markdown`, `bash`. Register your own with `registerGrammar(grammar)`.
38
42
 
43
+ ### Extending the editor
44
+
45
+ Three bounded seams — no plugin lifecycle, no transaction filters (use a full editor
46
+ framework if you need those):
47
+
48
+ - **Key bindings** — pass a `keymap` of `chord → command`. Chords use `Mod` for
49
+ Cmd/Ctrl (e.g. `'Mod-s'`, `'Mod-Shift-z'`, `'Shift-Tab'`); a command returns `true`
50
+ when it handled the event. User bindings merge over (and override) the built-ins.
51
+ - **Decorations** — pass `decorations` (an array, or `(value) => Decoration[]`) to tag
52
+ `{ line, start, end, className }` column ranges; they render as extra classes in the
53
+ highlight layer (the same seam find and bracket-matching use).
54
+ - **Grammars** — register a language with `registerGrammar(grammar)`.
55
+
56
+ ```tsx
57
+ <CodeEditor
58
+ language="markdown"
59
+ onSave={(value) => save(value)} // Mod-S
60
+ keymap={{
61
+ 'Mod-/': ({ textarea, setText }) => {
62
+ /* toggle comment */ return true
63
+ },
64
+ }}
65
+ decorations={(value) => findTodos(value)}
66
+ />
67
+ ```
68
+
39
69
  ### React apps must subscribe to signals
40
70
 
41
71
  `CodeEditor` and `Highlight` are signal-driven. In a plain React app (no Babel signals transform),
package/dist/editor.css DELETED
@@ -1,2 +0,0 @@
1
- @layer cascivo.component{._root_4k2k5_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_4k2k5_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_4k2k5_28{min-block-size:1lh;display:block}._pre_4k2k5_33{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_4k2k5_45{font:inherit}._line_4k2k5_50{min-block-size:1lh;display:block}._root_4k2k5_2[data-wrap=true] ._pre_4k2k5_33{white-space:pre-wrap;word-break:break-word}._root_4k2k5_2[data-line-numbers=false] ._gutter_4k2k5_17{display:none}._plain_4k2k5_65{color:var(--cascivo-editor-fg)}._keyword_4k2k5_68{color:var(--cascivo-editor-syntax-keyword)}._string_4k2k5_71{color:var(--cascivo-editor-syntax-string)}._number_4k2k5_74{color:var(--cascivo-editor-syntax-number)}._comment_4k2k5_77{color:var(--cascivo-editor-syntax-comment);font-style:italic}._function_4k2k5_81{color:var(--cascivo-editor-syntax-function)}._type_4k2k5_84{color:var(--cascivo-editor-syntax-type)}._operator_4k2k5_87{color:var(--cascivo-editor-syntax-operator)}._punctuation_4k2k5_90{color:var(--cascivo-editor-syntax-punctuation)}._variable_4k2k5_93{color:var(--cascivo-editor-syntax-variable)}._tag_4k2k5_96{color:var(--cascivo-editor-syntax-tag)}._attr_4k2k5_99{color:var(--cascivo-editor-syntax-attr)}._regexp_4k2k5_102{color:var(--cascivo-editor-syntax-regexp)}._boolean_4k2k5_105{color:var(--cascivo-editor-syntax-boolean)}._property_4k2k5_108{color:var(--cascivo-editor-syntax-property)}._root_q71hw_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_q71hw_2:has(._textarea_q71hw_18:focus-visible){box-shadow:var(--cascivo-focus-ring);border-color:var(--cascivo-color-accent)}._gutter_q71hw_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);overflow:hidden}._codeArea_q71hw_35{flex:auto;min-inline-size:0;position:relative}._pre_q71hw_42{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_q71hw_57{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_q71hw_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_q71hw_18::placeholder{color:var(--cascivo-editor-gutter-fg)}._textarea_q71hw_18::selection{background:var(--cascivo-editor-selection)}._textarea_q71hw_18:disabled{cursor:not-allowed;opacity:var(--cascivo-disabled-opacity)}._root_q71hw_2[data-wrap=true] ._pre_q71hw_42,._root_q71hw_2[data-wrap=true] ._textarea_q71hw_18{white-space:pre-wrap;word-break:break-word}._root_q71hw_2[data-line-numbers=false] ._gutter_q71hw_23{display:none}@media (pointer:coarse){._root_q71hw_2{min-block-size:var(--cascivo-target-min-coarse,2.75rem)}}@media (prefers-reduced-motion:reduce){._root_q71hw_2{transition:none}}}@media (forced-colors:active){._root_q71hw_2{border:1px solid fieldtext}._pre_q71hw_42{display:none}._textarea_q71hw_18{color:canvastext;caret-color:canvastext}}
2
- /*$vite$:1*/
package/dist/index.d.ts DELETED
@@ -1,202 +0,0 @@
1
- import { HTMLAttributes, 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
- /** Result of tokenizing one line: spans plus the continuation state. */
35
- interface TokenizeResult {
36
- tokens: Token[];
37
- endState: GrammarState;
38
- }
39
- /**
40
- * Tokenize a single line, restartable from `startState`, memoized. Pure from the
41
- * caller's perspective: identical `(grammar, line, startState)` yield identical
42
- * results (a cache hit returns the previously-computed value).
43
- */
44
- declare function tokenize(grammar: Grammar, line: string, startState: GrammarState): TokenizeResult;
45
- /**
46
- * Tokenize a whole document, threading each line's `endState` into the next so
47
- * block comments / template literals / fenced code continue across lines.
48
- * Returns one token array per line. The per-line memo makes a single-line edit
49
- * re-tokenize only the changed line(s) until the start-state reconverges.
50
- */
51
- declare function tokenizeDocument(grammar: Grammar, text: string): Token[][];
52
- /** Register (or replace) a grammar under its `name`. */
53
- declare function registerGrammar(grammar: Grammar): void;
54
- /** Resolve a grammar by name, falling back to `plaintext` for unknown languages. */
55
- declare function getGrammar(name?: string): Grammar;
56
- /** List the names of all currently-registered grammars. */
57
- declare function listGrammars(): string[];
58
- /**
59
- * One tokenizer rule. `match` is anchored at the current scan position (compiled
60
- * sticky internally). On a non-empty match the matched text becomes a token of
61
- * `kind`, the position advances, and the state transitions via `push`/`pop`.
62
- */
63
- interface Rule {
64
- match: RegExp;
65
- kind: TokenKind;
66
- /** Switch to this state after matching (for entering multi-line constructs). */
67
- push?: string;
68
- /** Return to the `default` state after matching (for leaving them). */
69
- pop?: boolean;
70
- }
71
- interface RuleSpec {
72
- name: string;
73
- /** Per-state ordered rule lists. Must include a `default` state. */
74
- states: Record<string, Rule[]>;
75
- }
76
- /**
77
- * Build a {@link Grammar} from ordered, state-keyed regex rules — a tiny
78
- * Monarch/Prism-style engine. At each position, the first rule in the current
79
- * state whose sticky regex matches wins; unmatched characters accumulate into
80
- * `plain` tokens, so the tokenizer is always lossless. State carries across
81
- * lines for block comments, template literals, and fenced code.
82
- */
83
- declare function createRuleGrammar(spec: RuleSpec): Grammar;
84
- /** The trivial fallback grammar: one uncolored `plain` token per line. */
85
- declare const plaintext: Grammar;
86
- /**
87
- * JSON: property keys vs string values, numbers, booleans/null, punctuation.
88
- * A string immediately followed by a colon is a property key; other strings are
89
- * values.
90
- */
91
- declare const json: Grammar;
92
- /** JavaScript: keywords, strings (incl. multi-line template literals), numbers,
93
- * line + block comments (state carry), function calls, identifiers, operators. */
94
- declare const javascript: Grammar;
95
- declare const typescript: Grammar;
96
- /**
97
- * CSS: at-rules, selectors, property names, values, numbers/units, strings, and
98
- * `/* *\/` block comments (state carried across lines).
99
- */
100
- declare const css: Grammar;
101
- /**
102
- * HTML: tags, attribute names/values, `<!-- -->` comments (state carried across
103
- * lines), and text. Inside a tag, attributes and strings are colored until `>`.
104
- */
105
- declare const html: Grammar;
106
- /**
107
- * Markdown: headings, strong/emphasis, inline code, fenced code blocks (state
108
- * carried across lines), links, and list markers.
109
- */
110
- declare const markdown: Grammar;
111
- /** Bash: comments, strings, keywords, variables (`$VAR` / `${...}`), operators. */
112
- declare const bash: Grammar;
113
- interface CodeEditorProps extends Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, 'value' | 'defaultValue' | 'onChange' | 'wrap'> {
114
- /** Controlled value. */
115
- value?: string;
116
- /** Initial value for uncontrolled use. */
117
- defaultValue?: string;
118
- /** Called with the new text on every edit. */
119
- onValueChange?: (value: string) => void;
120
- /** Grammar name (defaults to `plaintext`; unknown names fall back to it). */
121
- language?: string;
122
- /** Show the line-number gutter (default true). */
123
- lineNumbers?: boolean;
124
- /** Spaces per tab stop (default 2). */
125
- tabSize?: number;
126
- /** Insert spaces (default) vs a literal tab on Tab. */
127
- insertSpaces?: boolean;
128
- /** Soft-wrap long lines instead of scrolling horizontally (default false). */
129
- wrap?: boolean;
130
- /**
131
- * Render only the visible lines for large documents. Defaults to auto
132
- * (on above ~1000 lines); disabled when `wrap` makes row heights variable.
133
- */
134
- virtualize?: boolean;
135
- /** Accessible label for the editor (defaults to the i18n "Code editor"). */
136
- label?: string;
137
- }
138
- /**
139
- * A lightweight code editor: a native `<textarea>` overlaid on a syntax-highlighted
140
- * `Highlight` layer. The browser owns editing/caret/IME/undo/a11y; JS handles only
141
- * tokenizing, scroll-sync, and Tab/Shift-Tab indent. Signal-driven, no banned hooks.
142
- *
143
- * Performance: tokenization is per-line memoized, the highlight layer is
144
- * rAF-debounced (typing never blocks), and large documents window to the visible
145
- * range while the textarea always holds the full text.
146
- */
147
- declare function CodeEditor({
148
- value,
149
- defaultValue,
150
- onValueChange,
151
- language,
152
- lineNumbers,
153
- tabSize,
154
- insertSpaces,
155
- wrap,
156
- virtualize,
157
- readOnly,
158
- disabled,
159
- spellCheck,
160
- label,
161
- className,
162
- style,
163
- onKeyDown,
164
- ...rest
165
- }: CodeEditorProps): import("react").JSX.Element;
166
- interface HighlightProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
167
- /** Code to render. */
168
- value: string;
169
- /** Grammar name (defaults to `plaintext`; unknown names fall back to it). */
170
- language?: string;
171
- /** Show the line-number gutter. */
172
- lineNumbers?: boolean;
173
- /** Soft-wrap long lines instead of scrolling horizontally. */
174
- wrap?: boolean;
175
- /** Spaces per tab stop (default 2). */
176
- tabSize?: number;
177
- /** Accessible label for the read-only code block. */
178
- label?: string;
179
- /** Ref to the scrollable `<pre>` (used by `CodeEditor` for scroll-sync). */
180
- preRef?: Ref<HTMLPreElement>;
181
- /** Ref to the gutter column (used by `CodeEditor` for scroll-sync). */
182
- gutterRef?: Ref<HTMLDivElement>;
183
- }
184
- /**
185
- * Read-only, syntax-highlighted `<pre><code>` rendered from the owned tokenizer.
186
- * The same render layer `CodeEditor` overlays its `<textarea>` on. Signal-safe,
187
- * themeable, and accessible — no banned hooks.
188
- */
189
- declare function Highlight({
190
- value,
191
- language,
192
- lineNumbers,
193
- wrap,
194
- tabSize,
195
- label,
196
- className,
197
- preRef,
198
- gutterRef,
199
- style,
200
- ...rest
201
- }: HighlightProps): import("react").JSX.Element;
202
- export { CodeEditor, type CodeEditorProps, type Grammar, type GrammarState, Highlight, type HighlightProps, type LineTokens, type Rule, type RuleSpec, type Token, type TokenKind, type TokenizeResult, bash, createRuleGrammar, css, getGrammar, html, javascript, json, listGrammars, markdown, plaintext, registerGrammar, tokenize, tokenizeDocument, typescript };
package/dist/index.js DELETED
@@ -1,701 +0,0 @@
1
- "use client";
2
- import { cn as e, useControllableSignal as t, useSignal as n, useSignalEffect as r, useSignals as i } from "@cascivo/core";
3
- import { builtin as a, t as o } from "@cascivo/i18n";
4
- import { useRef as s } from "react";
5
- import { jsx as c, jsxs as l } from "react/jsx-runtime";
6
- //#region src/engine/tokenize.ts
7
- var u = 5e3, d = /* @__PURE__ */ new Map(), f = "\0";
8
- function p(e, t, n) {
9
- let r = `${e.name}${f}${n}${f}${t}`, i = d.get(r);
10
- if (i) return d.delete(r), d.set(r, i), i;
11
- let { tokens: a, state: o } = e.tokenizeLine(t, n), s = {
12
- tokens: a,
13
- endState: o
14
- };
15
- if (d.set(r, s), d.size > u) {
16
- let e = d.keys().next().value;
17
- e !== void 0 && d.delete(e);
18
- }
19
- return s;
20
- }
21
- function m(e, t) {
22
- let n = t.split("\n"), r = [], i = e.initialState;
23
- for (let t of n) {
24
- let n = p(e, t, i);
25
- r.push(n.tokens), i = n.endState;
26
- }
27
- return r;
28
- }
29
- //#endregion
30
- //#region src/engine/registry.ts
31
- var h = /* @__PURE__ */ new Map(), g = {
32
- name: "plaintext",
33
- initialState: "default",
34
- tokenizeLine: (e) => ({
35
- tokens: e.length > 0 ? [{
36
- kind: "plain",
37
- value: e
38
- }] : [],
39
- state: "default"
40
- })
41
- };
42
- h.set(g.name, g);
43
- function _(e) {
44
- h.set(e.name, e);
45
- }
46
- function v(e) {
47
- if (e !== void 0) {
48
- let t = h.get(e);
49
- if (t) return t;
50
- }
51
- return h.get("plaintext");
52
- }
53
- function y() {
54
- return [...h.keys()];
55
- }
56
- //#endregion
57
- //#region src/grammars/rules.ts
58
- function b(e) {
59
- let t = {};
60
- for (let [n, r] of Object.entries(e.states)) t[n] = r.map((e) => {
61
- let t = `${e.match.flags.replace(/[gy]/g, "")}y`, n = {
62
- re: new RegExp(e.match.source, t),
63
- kind: e.kind
64
- };
65
- return e.push !== void 0 && (n.push = e.push), e.pop !== void 0 && (n.pop = e.pop), n;
66
- });
67
- return {
68
- name: e.name,
69
- initialState: "default",
70
- tokenizeLine(e, n) {
71
- let r = t[n] ? n : "default", i = [], a = -1, o = 0, s = (t) => {
72
- a >= 0 && (i.push({
73
- kind: "plain",
74
- value: e.slice(a, t)
75
- }), a = -1);
76
- };
77
- for (; o < e.length;) {
78
- let n = t[r], c = !1;
79
- for (let a of n) {
80
- a.re.lastIndex = o;
81
- let n = a.re.exec(e);
82
- if (n !== null && n.index === o && n[0].length > 0) {
83
- s(o), i.push({
84
- kind: a.kind,
85
- value: n[0]
86
- }), o += n[0].length, a.pop ? r = "default" : a.push && t[a.push] && (r = a.push), c = !0;
87
- break;
88
- }
89
- }
90
- c || (a < 0 && (a = o), o++);
91
- }
92
- return s(e.length), {
93
- tokens: i,
94
- state: r
95
- };
96
- }
97
- };
98
- }
99
- //#endregion
100
- //#region src/grammars/plaintext.ts
101
- var x = {
102
- name: "plaintext",
103
- initialState: "default",
104
- tokenizeLine: (e) => ({
105
- tokens: e.length > 0 ? [{
106
- kind: "plain",
107
- value: e
108
- }] : [],
109
- state: "default"
110
- })
111
- };
112
- _(x);
113
- //#endregion
114
- //#region src/grammars/json.ts
115
- var S = b({
116
- name: "json",
117
- states: { default: [
118
- {
119
- match: /"(?:[^"\\]|\\.)*"(?=\s*:)/,
120
- kind: "property"
121
- },
122
- {
123
- match: /"(?:[^"\\]|\\.)*"/,
124
- kind: "string"
125
- },
126
- {
127
- match: /\b(?:true|false|null)\b/,
128
- kind: "boolean"
129
- },
130
- {
131
- match: /-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/,
132
- kind: "number"
133
- },
134
- {
135
- match: /[{}[\],]/,
136
- kind: "punctuation"
137
- },
138
- {
139
- match: /:/,
140
- kind: "operator"
141
- }
142
- ] }
143
- });
144
- _(S);
145
- //#endregion
146
- //#region src/grammars/clike.ts
147
- var C = /* @__PURE__ */ "async.await.break.case.catch.class.const.continue.debugger.default.delete.do.else.export.extends.finally.for.function.if.import.in.instanceof.let.new.of.return.static.super.switch.this.throw.try.typeof.var.void.while.with.yield.as.from.get.set".split(".");
148
- function w(e) {
149
- return RegExp(`(?:${e.join("|")})\\b`);
150
- }
151
- function T(e, t, n = []) {
152
- let r = [
153
- {
154
- match: /\/\/.*/,
155
- kind: "comment"
156
- },
157
- {
158
- match: /\/\*/,
159
- kind: "comment",
160
- push: "block"
161
- },
162
- {
163
- match: /`/,
164
- kind: "string",
165
- push: "template"
166
- },
167
- {
168
- match: /'(?:[^'\\]|\\.)*'/,
169
- kind: "string"
170
- },
171
- {
172
- match: /"(?:[^"\\]|\\.)*"/,
173
- kind: "string"
174
- },
175
- {
176
- match: /0[xX][0-9a-fA-F]+|0[bB][01]+|0[oO][0-7]+|(?:\d[\d_]*\.?\d*|\.\d+)(?:[eE][+-]?\d+)?n?/,
177
- kind: "number"
178
- },
179
- {
180
- match: /\b(?:true|false|null|undefined|NaN|Infinity)\b/,
181
- kind: "boolean"
182
- }
183
- ];
184
- return n.length > 0 && r.push({
185
- match: w(n),
186
- kind: "type"
187
- }), r.push({
188
- match: w(t),
189
- kind: "keyword"
190
- }, {
191
- match: /[A-Za-z_$][\w$]*(?=\s*\()/,
192
- kind: "function"
193
- }, {
194
- match: /[A-Z][\w$]*/,
195
- kind: "type"
196
- }, {
197
- match: /[A-Za-z_$][\w$]*/,
198
- kind: "variable"
199
- }, {
200
- match: /[+\-*/%=<>!&|^~?]+/,
201
- kind: "operator"
202
- }, {
203
- match: /[{}()[\];,.:]/,
204
- kind: "punctuation"
205
- }), b({
206
- name: e,
207
- states: {
208
- default: r,
209
- block: [
210
- {
211
- match: /\*\//,
212
- kind: "comment",
213
- pop: !0
214
- },
215
- {
216
- match: /[^*]+/,
217
- kind: "comment"
218
- },
219
- {
220
- match: /\*/,
221
- kind: "comment"
222
- }
223
- ],
224
- template: [
225
- {
226
- match: /\\./,
227
- kind: "string"
228
- },
229
- {
230
- match: /`/,
231
- kind: "string",
232
- pop: !0
233
- },
234
- {
235
- match: /\$\{/,
236
- kind: "punctuation"
237
- },
238
- {
239
- match: /[^`\\$]+/,
240
- kind: "string"
241
- },
242
- {
243
- match: /\$/,
244
- kind: "string"
245
- }
246
- ]
247
- }
248
- });
249
- }
250
- //#endregion
251
- //#region src/grammars/javascript.ts
252
- var E = T("javascript", C);
253
- _(E);
254
- var D = T("typescript", C, [
255
- "interface",
256
- "type",
257
- "enum",
258
- "implements",
259
- "namespace",
260
- "declare",
261
- "abstract",
262
- "readonly",
263
- "public",
264
- "private",
265
- "protected",
266
- "keyof",
267
- "infer",
268
- "satisfies",
269
- "is",
270
- "asserts",
271
- "override",
272
- "string",
273
- "number",
274
- "boolean",
275
- "any",
276
- "unknown",
277
- "never",
278
- "object"
279
- ]);
280
- _(D);
281
- //#endregion
282
- //#region src/grammars/css.ts
283
- var O = b({
284
- name: "css",
285
- states: {
286
- default: [
287
- {
288
- match: /\/\*/,
289
- kind: "comment",
290
- push: "block"
291
- },
292
- {
293
- match: /@[\w-]+/,
294
- kind: "keyword"
295
- },
296
- {
297
- match: /"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/,
298
- kind: "string"
299
- },
300
- {
301
- match: /#[0-9a-fA-F]{3,8}\b/,
302
- kind: "number"
303
- },
304
- {
305
- match: /-?\d+(?:\.\d+)?(?:px|rem|em|%|vh|vw|s|ms|deg|fr|ch|ex)?/,
306
- kind: "number"
307
- },
308
- {
309
- match: /--[\w-]+/,
310
- kind: "variable"
311
- },
312
- {
313
- match: /[.#][\w-]+/,
314
- kind: "tag"
315
- },
316
- {
317
- match: /&|::?[\w-]+/,
318
- kind: "tag"
319
- },
320
- {
321
- match: /[\w-]+(?=\s*:)/,
322
- kind: "property"
323
- },
324
- {
325
- match: /[\w-]+(?=\s*\()/,
326
- kind: "function"
327
- },
328
- {
329
- match: /[{}();,:]/,
330
- kind: "punctuation"
331
- }
332
- ],
333
- block: [
334
- {
335
- match: /\*\//,
336
- kind: "comment",
337
- pop: !0
338
- },
339
- {
340
- match: /[^*]+/,
341
- kind: "comment"
342
- },
343
- {
344
- match: /\*/,
345
- kind: "comment"
346
- }
347
- ]
348
- }
349
- });
350
- _(O);
351
- //#endregion
352
- //#region src/grammars/html.ts
353
- var k = b({
354
- name: "html",
355
- states: {
356
- default: [
357
- {
358
- match: /<!--/,
359
- kind: "comment",
360
- push: "comment"
361
- },
362
- {
363
- match: /<!?\/?[A-Za-z][\w-]*/,
364
- kind: "tag",
365
- push: "tag"
366
- },
367
- {
368
- match: /&[a-zA-Z#0-9]+;/,
369
- kind: "boolean"
370
- }
371
- ],
372
- comment: [
373
- {
374
- match: /-->/,
375
- kind: "comment",
376
- pop: !0
377
- },
378
- {
379
- match: /[^-]+/,
380
- kind: "comment"
381
- },
382
- {
383
- match: /-/,
384
- kind: "comment"
385
- }
386
- ],
387
- tag: [
388
- {
389
- match: /\/?>/,
390
- kind: "tag",
391
- pop: !0
392
- },
393
- {
394
- match: /"(?:[^"\\]|\\.)*"|'[^']*'/,
395
- kind: "string"
396
- },
397
- {
398
- match: /[\w-]+(?=\s*=)/,
399
- kind: "attr"
400
- },
401
- {
402
- match: /=/,
403
- kind: "operator"
404
- },
405
- {
406
- match: /[\w-]+/,
407
- kind: "attr"
408
- }
409
- ]
410
- }
411
- });
412
- _(k);
413
- //#endregion
414
- //#region src/grammars/markdown.ts
415
- var A = b({
416
- name: "markdown",
417
- states: {
418
- default: [
419
- {
420
- match: /^```.*/,
421
- kind: "keyword",
422
- push: "fence"
423
- },
424
- {
425
- match: /^#{1,6}\s.*/,
426
- kind: "keyword"
427
- },
428
- {
429
- match: /^\s*(?:[-*+]|\d+\.)\s/,
430
- kind: "operator"
431
- },
432
- {
433
- match: /^\s*>\s.*/,
434
- kind: "comment"
435
- },
436
- {
437
- match: /`[^`]+`/,
438
- kind: "string"
439
- },
440
- {
441
- match: /\*\*[^*]+\*\*|__[^_]+__/,
442
- kind: "keyword"
443
- },
444
- {
445
- match: /\*[^*\s][^*]*\*|_[^_\s][^_]*_/,
446
- kind: "type"
447
- },
448
- {
449
- match: /!?\[[^\]]*\]\([^)]*\)/,
450
- kind: "function"
451
- }
452
- ],
453
- fence: [{
454
- match: /^```.*/,
455
- kind: "keyword",
456
- pop: !0
457
- }, {
458
- match: /.+/,
459
- kind: "string"
460
- }]
461
- }
462
- });
463
- _(A);
464
- //#endregion
465
- //#region src/grammars/bash.ts
466
- var j = b({
467
- name: "bash",
468
- states: { default: [
469
- {
470
- match: /#.*/,
471
- kind: "comment"
472
- },
473
- {
474
- match: /"(?:[^"\\]|\\.)*"/,
475
- kind: "string"
476
- },
477
- {
478
- match: /'[^']*'/,
479
- kind: "string"
480
- },
481
- {
482
- match: /\b(?:if|then|else|elif|fi|for|while|until|do|done|case|esac|in|function|select|return|exit|break|continue|local|export|readonly|declare|echo|cd|source|set|unset)\b/,
483
- kind: "keyword"
484
- },
485
- {
486
- match: /\$\{[^}]*\}|\$[A-Za-z_]\w*|\$[0-9@*#?$!]/,
487
- kind: "variable"
488
- },
489
- {
490
- match: /-?\b\d+\b/,
491
- kind: "number"
492
- },
493
- {
494
- match: /[A-Za-z_][\w-]*(?=\s*\(\s*\))/,
495
- kind: "function"
496
- },
497
- {
498
- match: /[|&;<>()]+|&&|\|\||=/,
499
- kind: "operator"
500
- }
501
- ] }
502
- });
503
- _(j);
504
- var M = {
505
- root: "_root_4k2k5_2",
506
- gutter: "_gutter_4k2k5_17",
507
- gutterLine: "_gutterLine_4k2k5_28",
508
- pre: "_pre_4k2k5_33",
509
- code: "_code_4k2k5_45",
510
- line: "_line_4k2k5_50",
511
- plain: "_plain_4k2k5_65",
512
- keyword: "_keyword_4k2k5_68",
513
- string: "_string_4k2k5_71",
514
- number: "_number_4k2k5_74",
515
- comment: "_comment_4k2k5_77",
516
- function: "_function_4k2k5_81",
517
- type: "_type_4k2k5_84",
518
- operator: "_operator_4k2k5_87",
519
- punctuation: "_punctuation_4k2k5_90",
520
- variable: "_variable_4k2k5_93",
521
- tag: "_tag_4k2k5_96",
522
- attr: "_attr_4k2k5_99",
523
- regexp: "_regexp_4k2k5_102",
524
- boolean: "_boolean_4k2k5_105",
525
- property: "_property_4k2k5_108"
526
- };
527
- //#endregion
528
- //#region src/editor/view.tsx
529
- function N(e, t = 0, n = e.length) {
530
- let r = [];
531
- for (let i = t; i < n; i++) {
532
- let t = e[i];
533
- r.push(/* @__PURE__ */ c("span", {
534
- className: M.line,
535
- children: t.length === 0 ? "​" : t.map((e, t) => /* @__PURE__ */ c("span", {
536
- className: M[e.kind],
537
- children: e.value
538
- }, t))
539
- }, i));
540
- }
541
- return r;
542
- }
543
- function P({ count: e, className: t, gutterRef: n, start: r = 0, end: i = e, topPad: a = 0, bottomPad: o = 0 }) {
544
- let s = [];
545
- for (let e = r + 1; e <= i; e++) s.push(/* @__PURE__ */ c("span", {
546
- className: M.gutterLine,
547
- children: e
548
- }, e));
549
- return /* @__PURE__ */ l("div", {
550
- ref: n,
551
- className: t,
552
- "aria-hidden": "true",
553
- children: [
554
- a > 0 && /* @__PURE__ */ c("div", { style: { blockSize: a } }),
555
- s,
556
- o > 0 && /* @__PURE__ */ c("div", { style: { blockSize: o } })
557
- ]
558
- });
559
- }
560
- var F = {
561
- root: "_root_q71hw_2",
562
- textarea: "_textarea_q71hw_18",
563
- gutter: "_gutter_q71hw_23",
564
- codeArea: "_codeArea_q71hw_35",
565
- pre: "_pre_q71hw_42",
566
- currentLine: "_currentLine_q71hw_57"
567
- }, I = 1e3, L = 12;
568
- function R({ value: u, defaultValue: d, onValueChange: f, language: p = "plaintext", lineNumbers: h = !0, tabSize: g = 2, insertSpaces: _ = !0, wrap: y = !1, virtualize: b, readOnly: x = !1, disabled: S = !1, spellCheck: C = !1, label: w, className: T, style: E, onKeyDown: D, ...O }) {
569
- i();
570
- let [k, A] = t({
571
- value: u,
572
- defaultValue: d ?? "",
573
- onChange: f
574
- }), j = s(null), M = s(null), R = s(null), z = s(null), B = n(k.value), V = n(0), H = n(0), U = n(0);
575
- r(() => {
576
- let e = k.value;
577
- if (typeof requestAnimationFrame != "function") {
578
- B.value = e;
579
- return;
580
- }
581
- let t = requestAnimationFrame(() => {
582
- B.value = e;
583
- });
584
- return () => cancelAnimationFrame(t);
585
- }), r(() => {
586
- let e = z.current;
587
- if (!e) return;
588
- let t = () => {
589
- let t = Number.parseFloat(getComputedStyle(e).lineHeight);
590
- U.value = Number.isFinite(t) && t > 0 ? t : 0, H.value = e.clientHeight;
591
- }, n = () => {
592
- V.value = e.scrollTop, H.value = e.clientHeight, M.current && (M.current.scrollTop = e.scrollTop, M.current.scrollLeft = e.scrollLeft), R.current && (R.current.scrollTop = e.scrollTop);
593
- }, r = () => {
594
- let t = e.value.slice(0, e.selectionStart).split("\n").length - 1;
595
- j.current?.style.setProperty("--cascivo-editor-caret-line", String(t));
596
- };
597
- return t(), r(), e.addEventListener("scroll", n), e.addEventListener("keyup", r), e.addEventListener("click", r), e.addEventListener("input", r), () => {
598
- e.removeEventListener("scroll", n), e.removeEventListener("keyup", r), e.removeEventListener("click", r), e.removeEventListener("input", r);
599
- };
600
- });
601
- let W = m(v(p), B.value), G = W.length, K = U.value, q = (b ?? G > I) && !y && K > 0, J = 0, Y = G;
602
- if (q) {
603
- J = Math.max(0, Math.floor(V.value / K) - L);
604
- let e = Math.ceil(H.value / K);
605
- Y = Math.min(G, J + e + L * 2);
606
- }
607
- let X = J * K, Z = (G - Y) * K, Q = (e) => {
608
- if (e.key === "Tab" && !x && !S) {
609
- e.preventDefault();
610
- let t = e.currentTarget, { selectionStart: n, selectionEnd: r, value: i } = t, a = _ ? " ".repeat(g) : " ", o = i.lastIndexOf("\n", n - 1) + 1;
611
- if (e.shiftKey) {
612
- let e = i.slice(o, r).replace(RegExp(`^(?:\\t| {1,${g}})`, "gm"), "");
613
- t.setRangeText(e, o, r, "select");
614
- } else if (n === r) t.setRangeText(a, n, r, "end");
615
- else {
616
- let e = i.slice(o, r).replace(/^/gm, a);
617
- t.setRangeText(e, o, r, "select");
618
- }
619
- A(t.value);
620
- }
621
- D?.(e);
622
- }, $ = {
623
- "--cascivo-editor-tab-size": g,
624
- ...E
625
- };
626
- return /* @__PURE__ */ l("div", {
627
- ref: j,
628
- className: e(F.root, T),
629
- "data-wrap": y,
630
- "data-line-numbers": h,
631
- style: $,
632
- children: [h && /* @__PURE__ */ c(P, {
633
- count: G,
634
- className: F.gutter,
635
- gutterRef: R,
636
- start: J,
637
- end: Y,
638
- topPad: X,
639
- bottomPad: Z
640
- }), /* @__PURE__ */ l("div", {
641
- className: F.codeArea,
642
- children: [/* @__PURE__ */ l("pre", {
643
- ref: M,
644
- className: F.pre,
645
- "aria-hidden": "true",
646
- children: [
647
- /* @__PURE__ */ c("div", { className: F.currentLine }),
648
- X > 0 && /* @__PURE__ */ c("div", { style: { blockSize: X } }),
649
- /* @__PURE__ */ c("code", { children: N(W, J, Y) }),
650
- Z > 0 && /* @__PURE__ */ c("div", { style: { blockSize: Z } })
651
- ]
652
- }), /* @__PURE__ */ c("textarea", {
653
- ref: z,
654
- className: F.textarea,
655
- value: k.value,
656
- onChange: (e) => A(e.currentTarget.value),
657
- onKeyDown: Q,
658
- readOnly: x,
659
- disabled: S,
660
- spellCheck: C,
661
- "aria-label": w ?? o(a.editor.label),
662
- autoCapitalize: "off",
663
- autoCorrect: "off",
664
- autoComplete: "off",
665
- wrap: y ? "soft" : "off",
666
- ...O
667
- })]
668
- })]
669
- });
670
- }
671
- //#endregion
672
- //#region src/editor/highlight/highlight.tsx
673
- function z({ value: t, language: n = "plaintext", lineNumbers: r = !1, wrap: a = !1, tabSize: o = 2, label: s, className: u, preRef: d, gutterRef: f, style: p, ...h }) {
674
- i();
675
- let g = m(v(n), t), _ = {
676
- "--cascivo-editor-tab-size": o,
677
- ...p
678
- };
679
- return /* @__PURE__ */ l("div", {
680
- className: e(M.root, u),
681
- "data-wrap": a,
682
- "data-line-numbers": r,
683
- style: _,
684
- "aria-label": s,
685
- ...h,
686
- children: [r && /* @__PURE__ */ c(P, {
687
- count: g.length,
688
- className: M.gutter,
689
- gutterRef: f
690
- }), /* @__PURE__ */ c("pre", {
691
- ref: d,
692
- className: M.pre,
693
- children: /* @__PURE__ */ c("code", {
694
- className: M.code,
695
- children: N(g)
696
- })
697
- })]
698
- });
699
- }
700
- //#endregion
701
- export { R as CodeEditor, z as Highlight, j as bash, b as createRuleGrammar, O as css, v as getGrammar, k as html, E as javascript, S as json, y as listGrammars, A as markdown, x as plaintext, _ as registerGrammar, p as tokenize, m as tokenizeDocument, D as typescript };