@pfmcodes/caret 0.2.8 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,55 +1,68 @@
1
- # Caret
2
1
  ![LOGO](https://github.com/PFMCODES/Caret/raw/main/logo.svg)
2
+ # Caret
3
3
  [![License: MIT](https://img.shields.io/badge/License-MIT-4000ff.svg)](https://opensource.org/licenses/MIT)
4
- [![JavaScript](https://img.shields.io/badge/JavaScript-63.7%25-f6fa03)](https://github.com/PFMCODES/lexius-edior)
5
- [![JavaScript](https://img.shields.io/badge/TypeScript-32.3%25-0244f7)](https://github.com/PFMCODES/lexius-edior)
6
- [![CSS](https://img.shields.io/badge/CSS-4.0%25-2e7ad1)](https://github.com/pfmcodes/lexius-editor)
4
+ [![JavaScript](https://img.shields.io/badge/JavaScript-63.7%25-f6fa03)](https://github.com/PFMCODES/Caret)
5
+ [![TypeScript](https://img.shields.io/badge/TypeScript-32.3%25-0244f7)](https://github.com/PFMCODES/Caret)
6
+ [![CSS](https://img.shields.io/badge/CSS-4.0%25-2e7ad1)](https://github.com/PFMCODES/Caret)
7
7
 
8
- A lightweight, feature-rich code editor with real-time syntax highlighting and custom caret rendering. Built with vanilla JavaScript and powered by Highlight.js, caret delivers a smooth coding experience with professional-grade features.
8
+ A lightweight, fast code editor engine with real-time syntax highlighting, custom caret rendering, and a clean EditContext-based architecture. 551 lines. 26KB. 42ms load time.
9
9
 
10
10
  ## Features
11
11
 
12
- - **Live Syntax Highlighting** - Real-time code highlighting powered by Highlight.js
13
- - **Custom Caret** - Smooth, pixel-perfect Caret positioning and rendering
12
+ - **EditContext API** - Clean input handling with no browser fighting, no textarea hacks
13
+ - **Live Syntax Highlighting** - Real-time highlighting powered by Highlight.js
14
+ - **Custom Caret** - Pixel-perfect caret positioning via Range API
14
15
  - **Line Numbers** - Built-in line counter with dynamic updates
15
- - **Smart Indentation** - Tab/Shift+Tab support for indenting/unindenting code blocks
16
- - **Theme Support** - Multiple syntax highlighting themes (light/dark modes)
17
- - **Smooth Scrolling** - Synchronized scrolling for code, highlights, and line numbers
18
- - **ES Modules** - Modern ESM architecture for easy integration
19
- - **TypeScript Ready** - Full TypeScript definitions included
20
- - **Lightweight** - Pure JavaScript, no heavy frameworks required
16
+ - **Smart Indentation** - Tab/Shift+Tab for indenting and unindenting code blocks
17
+ - **Undo/Redo** - Full undo/redo stack with cursor position restoration (Ctrl+Z / Ctrl+Y / Ctrl+Shift+Z)
18
+ - **Theme Support** - Custom background, text, caret and line counter colors
19
+ - **Lock Mode** - Read-only mode for displaying code
20
+ - **Font Support** - Custom font loading
21
+ - **Paste Handling** - Always pastes plain text, no rich HTML
22
+ - **ES Modules** - Modern ESM architecture
23
+ - **Lightweight** - 551 lines, 26KB total, loads in ~42ms
21
24
 
22
25
  ## Table of Contents
23
26
 
24
- - [What's new](#What's-New?)
27
+ - [What's New](#whats-new)
25
28
  - [Installation](#installation)
26
29
  - [Quick Start](#quick-start)
27
30
  - [Project Structure](#project-structure)
28
31
  - [Usage](#usage)
32
+ - [API Reference](#api-reference)
29
33
  - [Customization](#customization)
30
- - [Contributing](#contributing)
34
+ - [Browser Support](#browser-support)
35
+ - [Performance](#performance)
31
36
  - [License](#license)
32
- - [Perfomance Notes](#performance-notes)
33
37
 
34
- ## What's-New?
38
+ ## What's New
39
+
40
+ ### v0.3.0 — Complete Rewrite
41
+ - **Ditched textarea** — rebuilt on Chrome's EditContext API
42
+ - **No more sync issues** — single text model, no dual layer fighting
43
+ - **Undo/redo with cursor restoration** — cursor returns to exact position
44
+ - **Pixel-perfect caret** — positioned via Range API, no canvas math
45
+ - **Modular architecture** — `textEditor.js`, `caret.js`, `lineCounter.js`, `font.js`, `languages.js`
46
+ - **setLanguage()** — switch language at runtime
47
+ - **delete()** — clean teardown with full event listener removal
48
+ - **onClick cursor positioning** — click anywhere to place cursor
49
+ - **Known limitation** — EditContext requires Chrome/Chromium (Firefox support pending)
35
50
 
36
51
  ### v0.2.8
37
- - just mistake i made has been fixed
52
+ - Bug fixes
38
53
 
39
54
  ### v0.2.7
40
- - another cleanup but this time, with file optimizations and updated comments at the end of each containing short summaries of each function and variables(only some important variables)
55
+ - File optimizations and updated comments
41
56
 
42
57
  ### v0.2.6
43
-
44
- - CommonJs support has been removed to reduce package size
58
+ - CommonJS support removed to reduce package size
45
59
 
46
60
  ### v0.2.5
47
61
  - Multiple editor instances support
48
62
  - Word-aware line wrapping
49
63
  - Fixed language re-registration bug
50
- - Known issue: custom caret may be 1 character off on original lines of a wrapped line
51
64
 
52
- ## Installation
65
+ ## Installation
53
66
 
54
67
  ### NPM
55
68
 
@@ -71,480 +84,522 @@ pnpm add @pfmcodes/caret
71
84
 
72
85
  ## Quick Start
73
86
 
74
- ### Basic Usage
75
-
76
87
  ```html
77
88
  <!DOCTYPE html>
78
89
  <html lang="en">
79
90
  <head>
80
91
  <meta charset="UTF-8">
81
92
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
82
- <title>Caret</title>
83
- <link rel="stylesheet" href="node_modules/caret/index.css">
93
+ <title>Caret Demo</title>
94
+ <link rel="shortcut icon" href="logo.svg" type="image/svg">
95
+ <style>
96
+ body {
97
+ font-family: system-ui, -apple-system, sans-serif;
98
+ padding: 20px;
99
+ background: #1a1a2e;
100
+ color: #fff;
101
+ }
102
+
103
+ h1 {
104
+ text-align: center;
105
+ margin-bottom: 20px;
106
+ }
107
+
108
+ #editor {
109
+ width: 900px;
110
+ height: 600px;
111
+ margin: 20px auto;
112
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.4);
113
+ border-radius: 8px;
114
+ overflow: hidden;
115
+ }
116
+
117
+ #result {
118
+ width: 900px;
119
+ margin: 10px auto;
120
+ padding: 10px;
121
+ background: #111;
122
+ color: #0f0;
123
+ font-family: monospace;
124
+ font-size: 13px;
125
+ border-radius: 8px;
126
+ min-height: 40px;
127
+ white-space: pre-wrap;
128
+ }
129
+
130
+ .controls {
131
+ max-width: 900px;
132
+ margin: 0 auto 20px;
133
+ display: flex;
134
+ flex-wrap: wrap;
135
+ gap: 8px;
136
+ }
137
+
138
+ .controls span {
139
+ font-size: 12px;
140
+ opacity: 0.5;
141
+ align-self: center;
142
+ margin: 0 4px;
143
+ }
144
+
145
+ button {
146
+ padding: 8px 16px;
147
+ border: none;
148
+ background: #7116d8;
149
+ color: white;
150
+ border-radius: 4px;
151
+ cursor: pointer;
152
+ font-size: 13px;
153
+ }
154
+
155
+ button:hover {
156
+ background: #5a11ab;
157
+ }
158
+ </style>
84
159
  </head>
85
160
  <body>
161
+ <h1>Caret Demo</h1>
162
+
163
+ <div class="controls">
164
+ <span>Language:</span>
165
+ <button onclick="window.changeLanguage('javascript')">JavaScript</button>
166
+ <button onclick="window.changeLanguage('python')">Python</button>
167
+ <button onclick="window.changeLanguage('html')">HTML</button>
168
+ <span>Theme:</span>
169
+ <button onclick="window.changeTheme('tokyo-night-dark')">Tokyo Night</button>
170
+ <button onclick="window.changeTheme('monokai')">Monokai</button>
171
+ <button onclick="window.changeTheme('github-dark')">GitHub Dark</button>
172
+ <button onclick="window.changeTheme('atom-one-dark')">Atom One Dark</button>
173
+ <span>Actions:</span>
174
+ <button onclick="window.runCode()">▶ Run</button>
175
+ <button onclick="window.getCode()">Get Code</button>
176
+ </div>
177
+
86
178
  <div id="editor"></div>
87
-
179
+ <div id="result"></div>
180
+
88
181
  <script type="module">
89
- import editor from './node_modules/@pfmcodes/caret/index.js';
90
-
91
- const instance = await editor.editor.createEditor(
182
+ import { createTextEditor } from './components/textEditor.js';
183
+
184
+ window.language = 'javascript';
185
+ window.currentTheme = 'tokyo-night-dark';
186
+
187
+ const jsCode = `// Welcome to Caret!
188
+ function fibonacci(n) {
189
+ if (n <= 1) return n;
190
+ return fibonacci(n - 1) + fibonacci(n - 2);
191
+ }
192
+
193
+ // Calculate first 10 Fibonacci numbers
194
+ for (let i = 0; i < 10; i++) {
195
+ console.log(\`F(\${i}) = \${fibonacci(i)}\`);
196
+ }`;
197
+
198
+ const pyCode = `# Welcome to Caret!
199
+ def fibonacci(n):
200
+ if n < 1:
201
+ return n
202
+ return fibonacci(n - 1) + fibonacci(n - 2)
203
+
204
+ # Calculate first 25 Fibonacci numbers
205
+ for i in range(25):
206
+ print(" i: " + f'{i} = {fibonacci(i)}. ')`;
207
+
208
+ const htmlCode = `<!-- Welcome to Caret! -->
209
+ <!DOCTYPE html>
210
+ <html lang="en">
211
+ <head>
212
+ <meta charset="UTF-8">
213
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
214
+ <title>Caret HTML Demo</title>
215
+ </head>
216
+ <body>
217
+ <h1>Hello, Caret!</h1>
218
+ <p>This is a simple HTML example to demonstrate Caret's capabilities.</p>
219
+ <ul>
220
+ <li>HTML5 support</li>
221
+ <li>Syntax highlighting</li>
222
+ <li>Auto-completion</li>
223
+ </ul>
224
+ </body>
225
+ </html>`;
226
+
227
+ const editorInstance = await createTextEditor(
92
228
  document.getElementById('editor'),
229
+ jsCode,
230
+ 'demo-editor',
93
231
  {
94
- value: 'console.log("Hello, World!");',
232
+ dark: true,
95
233
  language: 'javascript',
96
- theme: 'hybrid'
234
+ hlTheme: 'tokyo-night-dark',
235
+ focusColor: '#7116d8',
236
+ id: Math.floor(Math.random() * 1000000000),
237
+ theme: {
238
+ dark: {
239
+ 'background.editor': '#1a1a2e',
240
+ 'background.lineCounter': '#16213e',
241
+ 'color.editor': '#d4d4d4',
242
+ 'color.lineCounter': '#888',
243
+ 'editor.caret': '#7116d8'
244
+ },
245
+ light: {
246
+ 'background.editor': '#fff',
247
+ 'background.lineCounter': '#f0f0f0',
248
+ 'color.editor': '#000',
249
+ 'color.lineCounter': '#666',
250
+ 'editor.caret': '#7116d8'
251
+ }
252
+ }
97
253
  }
98
254
  );
99
- </script>
100
- </body>
101
- </html>
102
- ```
103
255
 
104
- ### ES Module Import
256
+ window.editorInstance = editorInstance;
105
257
 
106
- ```javascript
107
- import editor from './node_modules/@pfmcodes/caret/index.js';
258
+ window.changeLanguage = async (lang) => {
259
+ window.language = lang;
260
+ await editorInstance.setLanguage(lang);
261
+ if (lang === 'javascript') editorInstance.setValue(jsCode);
262
+ if (lang === 'python') editorInstance.setValue(pyCode);
263
+ if (lang === 'html') editorInstance.setValue(htmlCode);
264
+ };
108
265
 
109
- // Create editor instance
110
- const editorInstance = await editor.editor.createEditor(
111
- document.getElementById('editor'),
112
- {
113
- value: '', // Initial code
114
- language: 'python', // Programming language
115
- theme: 'monokai' // Highlight.js theme
116
- }
117
- );
266
+ window.changeTheme = (theme) => {
267
+ window.currentTheme = theme;
268
+ // update the hlTheme stylesheet
269
+ const link = document.getElementById('caret-theme-demo-editor');
270
+ if (link) link.href = `https://esm.sh/@pfmcodes/highlight.js@1.0.0/styles/${theme}.css`;
271
+ };
272
+
273
+ window.runCode = async () => {
274
+ const resultEl = document.getElementById('result');
275
+ resultEl.textContent = 'Running...';
276
+
277
+ if (window.language === 'html') {
278
+ resultEl.innerHTML = editorInstance.getValue();
279
+ return;
280
+ }
281
+
282
+ const code = editorInstance.getValue();
283
+ const lang = window.language;
284
+
285
+ try {
286
+ const res = await fetch('https://lexius-transpiler.onrender.com/run', {
287
+ method: 'POST',
288
+ headers: { 'Content-Type': 'application/json' },
289
+ body: JSON.stringify({ code, lang })
290
+ });
291
+
292
+ if (!res.ok) throw new Error(`Server error: ${res.status}`);
293
+
294
+ const data = await res.json();
295
+ const exec = data.result ?? {};
296
+ const stdout = exec.stdout ?? '';
297
+ const stderr = exec.stderr ?? '';
298
+ const result = exec.result !== undefined && exec.result !== null
299
+ ? String(exec.result) : '';
300
+
301
+ resultEl.textContent = [stdout, stderr, result].filter(Boolean).join('\n') + '\n';
302
+ } catch (err) {
303
+ resultEl.textContent = 'Error: ' + (err.message || err) + '\n';
304
+ }
305
+ };
306
+
307
+ window.getCode = async () => {
308
+ try {
309
+ await navigator.clipboard.writeText(editorInstance.getValue());
310
+ window.confirm('Text copied to clipboard');
311
+ } catch (err) {
312
+ window.alert('Failed to copy: ', err);
313
+ }
314
+ };
315
+ </script>
316
+ </body>
317
+ </html>
118
318
  ```
119
319
 
120
320
  ## Project Structure
121
321
 
122
322
  ```
123
323
  caret/
124
- ├── types/ # TypeScript type definitions
125
- ├── .gitignore # Git ignore rules
126
- ├── .npmignore # NPM ignore rules
127
- ├── editor.js # main editor file essential for ui
128
- ├── index.css # Core styles
129
- ├── index.js # Main file
130
- ├── langauges.js # handles langauge related tasks
131
- ├── LICENSE # MIT License
132
- ├── package.json # Package configuration
133
- ├── README.md # The file containing instructions and help
134
- └── theme.js # handles theme related tasks
324
+ ├── components/
325
+ ├── textEditor.js # Text editor(core) — EditContext, undo/redo, highlighting
326
+ ├── caret.js # Custom caret positioning via Range API
327
+ ├── lineCounter.js # Line number display
328
+ ├── font.js # Custom font loading
329
+ │ └── languages.js # Highlight.js language registration
330
+ ├── .gitignore
331
+ ├── .npmignore
332
+ ├── index.js # Main file
333
+ ├── LICENSE
334
+ ├── logo.svg
335
+ ├── package-lock.json
336
+ ├── package.json
337
+ ├── package.json
338
+ ├── README.md
339
+ └── utilities.js # Shared utilities
135
340
  ```
136
341
 
137
342
  ## Usage
138
343
 
139
- ### JavaScript Editor
344
+ ### Basic Editor
140
345
 
141
346
  ```javascript
142
- import editor from './node_modules/@pfmcodes/caret/index.js';
143
- const jsEditor = await editor.editor.createEditor(
144
- document.getElementById('js-editor'),
145
- {
146
- value: `function greet(name) {
147
- return \`Hello, \${name}!\`;
148
- }
347
+ import { createEditor } from './node_modules/@pfmcodes/caret/index.js';
149
348
 
150
- console.log(greet('World'));`,
349
+ const editor = await createEditor(
350
+ document.getElementById('editor'), // parent element
351
+ 'const x = 42;', // initial content
352
+ 'my-editor', // unique id
353
+ {
354
+ dark: true,
151
355
  language: 'javascript',
152
- theme: 'atom-one-dark'
356
+ hlTheme: 'tokyo-night-dark'
153
357
  }
154
358
  );
155
359
  ```
156
360
 
157
- ### Python Editor
361
+ ### Read-Only Display
158
362
 
159
363
  ```javascript
160
- const pyEditor = await editor.editor.createEditor(
161
- document.getElementById('py-editor'),
162
- {
163
- value: `def fibonacci(n):
164
- if n <= 1:
165
- return n
166
- return fibonacci(n-1) + fibonacci(n-2)
364
+ import { createEditor } from './node_modules/@pfmcodes/caret/index.js';
167
365
 
168
- print([fibonacci(i) for i in range(10)])`,
366
+ const editor = await createEditor(
367
+ document.getElementById('editor'),
368
+ code,
369
+ 'readonly-editor',
370
+ {
371
+ dark: true,
169
372
  language: 'python',
170
- theme: 'github-dark'
373
+ hlTheme: 'github-dark',
374
+ lock: true
171
375
  }
172
376
  );
173
377
  ```
174
378
 
175
- ### Empty Editor (Start from Scratch)
379
+ ### Multiple Instances
176
380
 
177
381
  ```javascript
178
- const emptyEditor = await editor.editor.createEditor(
179
- document.getElementById('empty-editor'),
180
- {
181
- value: '',
182
- language: 'javascript',
183
- theme: 'hybrid'
184
- }
185
- );
186
- ```
187
-
188
- ## Customization
189
-
190
- ### Custom Styling
382
+ import { createEditor } from './node_modules/@pfmcodes/caret/index.js';
191
383
 
192
- The editor comes with default styles that you can override:
384
+ // each editor needs a unique id
193
385
 
194
- ```css
195
- /* Custom editor styling */
196
- #editor {
197
- width: 800px !important;
198
- height: 500px !important;
199
- font-size: 16px !important;
200
- }
201
-
202
- /* Customize line numbers */
203
- .Caret-lineCounter {
204
- background: #1e1e1e;
205
- }
206
-
207
- .Caret-lineCounter-number {
208
- font-size: 12px;
209
- padding: 0 8px;
210
- }
211
-
212
- /* Customize the textarea */
213
- #Caret-textarea {
214
- font-family: 'Fira Code', 'Consolas', monospace;
215
- line-height: 1.6;
216
- }
217
-
218
- /* Customize the Caret */
219
- #Caret-caret {
220
- background: #00ff00 !important;
221
- width: 3px !important;
222
- }
386
+ const editor1 = await createEditor(el1, code1, 'editor-1', options);
387
+ const editor2 = await createEditor(el2, code2, 'editor-2', options);
223
388
  ```
224
389
 
225
- ## Advanced Features
226
-
227
- ### Multi-Language Support
228
-
229
- Caret supports all languages available in Highlight.js:
390
+ ### Custom Theme
230
391
 
231
392
  ```javascript
232
- // JavaScript
233
- await editor.editor.createEditor(el, { language: 'javascript', ... });
234
-
235
- // Python
236
- await editor.editor.createEditor(el, { language: 'python', ... });
237
-
238
- // TypeScript
239
- await editor.editor.createEditor(el, { language: 'typescript', ... });
240
-
241
- // HTML
242
- await editor.editor.createEditor(el, { language: 'html', ... });
393
+ import { createEditor } from './node_modules/@pfmcodes/caret/index.js';
243
394
 
244
- // CSS
245
- await editor.editor.createEditor(el, { language: 'css', ... });
246
-
247
- // And many more...
395
+ const editor = await createEditor(
396
+ document.getElementById('editor'),
397
+ code,
398
+ 'editor-1',
399
+ {
400
+ dark: true,
401
+ language: 'javascript',
402
+ hlTheme: 'tokyo-night-dark',
403
+ focusColor: '#fff',
404
+ theme: {
405
+ dark: {
406
+ 'background.editor': '#222',
407
+ 'background.lineCounter': '#333',
408
+ 'color.editor': '#d4d4d4',
409
+ 'color.lineCounter': '#d4d4d4',
410
+ 'editor.caret': '#37ff29'
411
+ },
412
+ light: {
413
+ 'background.editor': '#cfd8f7',
414
+ 'background.lineCounter': '#e2e7f9',
415
+ 'color.editor': '#000',
416
+ 'color.lineCounter': '#000',
417
+ 'editor.caret': '#37ff29'
418
+ }
419
+ }
420
+ }
421
+ );
248
422
  ```
249
423
 
250
- ### Dynamic Language Switching
424
+ ### Custom Font
251
425
 
252
426
  ```javascript
253
- const editorInstance = await editor.editor.createEditor(el, {
254
- value: 'console.log("Hello");',
255
- language: 'javascript',
256
- theme: 'hybrid'
257
- });
427
+ import { createEditor } from './node_modules/@pfmcodes/caret/index.js';
258
428
 
259
- // Later, switch to Python
260
- editorInstance.setValue('print("Hello")');
261
- editorInstance.setLanguage('python');
262
- ```
263
-
264
- ### Real-Time Code Editing
265
-
266
- The editor automatically:
267
- - Updates syntax highlighting as you type
268
- - Adjusts line numbers dynamically
269
- - Maintains Caret position accurately
270
- - Synchronizes all visual components
271
-
272
- ### Configuration Options
273
-
274
- ```javascript
275
- const editorInstance = await editor.editor.createEditor(
276
- containerElement,
429
+ const editor = await createEditor(
430
+ document.getElementById('editor'),
431
+ code,
432
+ 'editor-1',
277
433
  {
278
- // Initial code content
279
- value: 'const x = 42;',
280
-
281
- // Programming language for syntax highlighting
282
- // Supports: javascript, python, java, cpp, html, css, json, etc.
434
+ dark: true,
283
435
  language: 'javascript',
284
-
285
- // Highlight.js theme name
286
- // Examples: 'hybrid', 'monokai', 'atom-one-dark', 'github', 'vs2015'
287
- theme: 'hybrid'
436
+ font: {
437
+ url: './fonts/FiraCode.ttf',
438
+ name: 'Fira Code'
439
+ }
288
440
  }
289
441
  );
290
442
  ```
291
443
 
292
- ### Available Themes
293
-
294
- Caret supports all Highlight.js themes. Popular options include:
295
-
296
- **Dark Themes:**
297
- - `atom-one-dark`
298
- - `monokai`
299
- - `night-owl`
300
- - `nord`
301
- - `tokyo-night-dark`
302
- - `vs2015`
303
-
304
- **Light Themes:**
305
- - `github`
306
- - `atom-one-light`
307
- - `stackoverflow-light`
308
- - `xcode`
309
-
310
444
  ## API Reference
311
445
 
312
- ### createEditor(container, options)
446
+ ### createEditor(parent, content, id, options)
313
447
 
314
448
  Creates a new editor instance.
315
449
 
316
450
  **Parameters:**
317
- - `container` (HTMLElement) - The DOM element to attach the editor to
318
- - `options` (Object) - Configuration options
319
- - `value` (string) - Initial code content
320
- - `language` (string) - Programming language for syntax highlighting
321
- - `theme` (string) - Highlight.js theme name
322
451
 
323
- **Returns:** Promise<EditorInstance>
452
+ | Parameter | Type | Required | Description |
453
+ |-----------|------|----------|-------------|
454
+ | `parent` | `HTMLElement` | ✅ | Container element |
455
+ | `content` | `string` | ✅ | Initial content |
456
+ | `options` | `object` | ✅ | Configuration |
457
+
458
+ **Options:**
459
+
460
+ | Option | Type | Default | Description |
461
+ |--------|------|---------|-------------|
462
+ | `dark` | `boolean` | `false` | Dark mode |
463
+ | `shadow` | `boolean` | `true` | Box shadow |
464
+ | `focusColor` | `string` | `#7c3aed` | Border color on focus |
465
+ | `shadowColor` | `string` | `#000` | Shadow color |
466
+ | `lock` | `boolean` | `false` | Read-only mode |
467
+ | `language` | `string` | `plaintext` | Highlight.js language |
468
+ | `hlTheme` | `string` | `hybrid` | Highlight.js theme |
469
+ | `font` | `object` | — | Custom font `{ url, name }` |
470
+ | `theme` | `object` | — | Custom colors (see above) |
471
+ `id` | `string/number` | — | required,
472
+
473
+ **Returns:** `Promise<EditorInstance>`
324
474
 
325
475
  ### EditorInstance Methods
326
476
 
327
477
  ```javascript
328
- // Get current editor content
329
- const code = editorInstance.getValue();
478
+ // Get current content
479
+ const code = editor.getValue();
330
480
 
331
- // Set editor content programmatically
332
- editorInstance.setValue('console.log("New code");');
481
+ // Set content
482
+ editor.setValue('console.log("hello");');
333
483
 
334
- // Focus the editor
335
- editorInstance.focus();
484
+ // Listen for changes
485
+ editor.onChange((text) => {
486
+ console.log('content changed:', text);
487
+ });
488
+
489
+ // Check if focused
490
+ const focused = editor.isFocused();
336
491
 
337
- // Change the programming language
338
- editorInstance.setLanguage('python');
492
+ // Switch language
493
+ await editor.setLanguage('python');
339
494
 
340
- // Destroy the editor instance
341
- editorInstance.destroy();
495
+ // Destroy instance and clean up
496
+ editor.delete();
342
497
  ```
343
498
 
344
- ### Editor Features
499
+ ### Keyboard Shortcuts
345
500
 
346
- #### Automatic Line Numbering
347
- Line numbers are automatically generated and synchronized with your code.
501
+ | Shortcut | Action |
502
+ |----------|--------|
503
+ | `Tab` | Indent (4 spaces) |
504
+ | `Shift+Tab` | Unindent |
505
+ | `Ctrl+Z` | Undo |
506
+ | `Ctrl+Y` | Redo |
507
+ | `Ctrl+Shift+Z` | Redo |
348
508
 
349
- #### Smart Tab Handling
350
- - **Tab** - Indent selected lines or insert 4 spaces
351
- - **Shift + Tab** - Unindent selected lines
509
+ ### Global Undo/Redo Stack
352
510
 
353
- #### Custom Caret
354
- The editor features a custom-rendered Caret that adapts to your theme (light/dark).
511
+ Each editor instance stores its undo/redo stack on `window.caret`:
355
512
 
356
- #### Synchronized Scrolling
357
- All editor components (code, highlights, line numbers) scroll together smoothly.
513
+ ```javascript
514
+ // access undo stack for a specific editor
515
+ window.caret['undoStack.editor-1']; // array of { content, cursor }
516
+ window.caret['redoStack.editor-1'];
517
+ ```
358
518
 
359
- ## Complete Example
519
+ ## Customization
360
520
 
361
- Here's a complete working example:
521
+ ### CSS Override
362
522
 
363
- ```html
364
- <!DOCTYPE html>
365
- <html lang="en">
366
- <head>
367
- <meta charset="UTF-8">
368
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
369
- <title>Caret Demo</title>
370
- <link rel="stylesheet" href="./index.css">
371
- <style>
372
- body {
373
- font-family: system-ui, -apple-system, sans-serif;
374
- padding: 20px;
375
- background: #f5f5f5;
376
- }
377
-
378
- #editor {
379
- width: 900px;
380
- height: 600px;
381
- margin: 20px auto;
382
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
383
- border-radius: 8px;
384
- overflow: hidden;
385
- }
386
-
387
- .controls {
388
- max-width: 900px;
389
- margin: 0 auto 20px;
390
- }
391
-
392
- button {
393
- padding: 8px 16px;
394
- margin-right: 8px;
395
- border: none;
396
- background: #7116d8;
397
- color: white;
398
- border-radius: 4px;
399
- cursor: pointer;
400
- }
401
-
402
- button:hover {
403
- background: #5a11ab;
404
- }
405
- </style>
406
- </head>
407
- <body>
408
- <h1 style="text-align: center;">Caret Demo</h1>
409
-
410
- <div class="controls">
411
- <button onclick="window.changeLanguage('javascript')">JavaScript</button>
412
- <button onclick="window.changeLanguage('python')">Python</button>
413
- <button onclick="window.changeLanguage('html')">HTML</button>
414
- <button onclick="window.changeTheme('monokai')">Monokai</button>
415
- <button onclick="window.changeTheme('github-dark')">GitHub</button>
416
- <button onclick="window.changeTheme('atom-one-dark')">Atom One Dark</button>
417
- <button onclick="window.getCode()">Get Code</button>
418
- </div>
419
-
420
- <div id="editor"></div>
421
-
422
- <script type="module">
423
- import editor from './node_modules/@pfmcodes/caret/index.js';
424
-
425
- // Initialize editor
426
- const editorInstance = await editor.editor.createEditor(
427
- document.getElementById('editor'),
428
- {
429
- value: `// Welcome to Caret!
430
- function fibonacci(n) {
431
- if (n <= 1) return n;
432
- return fibonacci(n - 1) + fibonacci(n - 2);
523
+ ```css
524
+ /* editor container */
525
+ #editor {
526
+ width: 900px;
527
+ height: 500px;
528
+ font-size: 16px;
433
529
  }
434
530
 
435
- // Calculate first 10 Fibonacci numbers
436
- for (let i = 0; i < 10; i++) {
437
- console.log(\`F(\${i}) = \${fibonacci(i)}\`);
438
- }`,
439
- language: 'javascript',
440
- theme: 'tokyo-night-dark'
441
- }
442
- );
443
-
444
- // Make it globally accessible for demo buttons
445
- window.editorInstance = editorInstance;
446
-
447
- window.changeLanguage = (lang) => {
448
- editorInstance.setLanguage(lang);
449
- };
450
-
451
- window.changeTheme = (theme) => {
452
- editor.theme.setTheme(theme);
453
- };
454
-
455
- window.getCode = () => {
456
- const code = editorInstance.getValue();
457
- console.log(code);
458
- alert('Code copied to console!');
459
- };
460
- </script>
461
- </body>
462
- </html>
531
+ /* line numbers */
532
+ .lineCounter {
533
+ min-width: 40px;
534
+ }
535
+
536
+ .line-number {
537
+ padding: 0 8px;
538
+ }
463
539
  ```
464
540
 
465
- ## Technical Details
541
+ ### Available hlTheme values
466
542
 
467
- ### How It Works
543
+ **Dark:**
544
+ - `atom-one-dark`
545
+ - `monokai`
546
+ - `night-owl`
547
+ - `nord`
548
+ - `tokyo-night-dark`
549
+ - `vs2015`
550
+ - `hybrid`
551
+ - `github-dark`
552
+
553
+ **Light:**
554
+ - `github`
555
+ - `atom-one-light`
556
+ - `stackoverflow-light`
557
+ - `xcode`
468
558
 
469
- Caret uses a clever layering technique:
559
+ ## Browser Support
470
560
 
471
- 1. **Textarea Layer** - Handles user input and cursor management
472
- 2. **Pre/Code Layer** - Displays syntax-highlighted code (overlay)
473
- 3. **Custom Caret** - Renders a styled Caret in the correct position
474
- 4. **Line Numbers** - Dynamically generated and synchronized
561
+ | Browser | Support |
562
+ |---------|---------|
563
+ | Chrome / Chromium | Full support |
564
+ | Edge | Full support |
565
+ | Firefox | EditContext not yet supported |
566
+ | Safari | EditContext not yet supported |
475
567
 
476
- The editor synchronizes all layers during:
477
- - Typing (input events)
478
- - Scrolling (scroll events)
479
- - Navigation (click, keyup events)
568
+ Caret v0.3.0 uses the [EditContext API](https://developer.mozilla.org/en-US/docs/Web/API/EditContext_API) which is currently only available in Chromium-based browsers. Firefox support is tracked [here](https://bugzilla.mozilla.org/show_bug.cgi?id=1850301).
480
569
 
481
- ### Performance
570
+ ## Performance
482
571
 
483
- - **Real-time Highlighting**: Uses Highlight.js for fast, accurate syntax highlighting
484
- - **Canvas Measurement**: Employs HTML5 Canvas API for precise text width calculations
485
- - **Event Optimization**: Efficiently updates only what's necessary on each interaction
486
- - **Heavy Optimization**: v0.1.5 used to handle 500 lines before lagging, with new caret@0.2.0 and onwards, there's almost 20 times performance increase now it can handle 10K+ lines smoothly before lagging(better results in optimized browsers like firefox)
572
+ | Metric | Caret v0.3.0 | Monaco | CodeMirror 6 |
573
+ |--------|-------------|--------|--------------|
574
+ | Bundle size | **26KB** | ~5MB | ~400KB |
575
+ | Load time | **~42ms** | ~2-3s | ~500ms |
576
+ | Lines of code | **551** | ~300,000 | ~50,000 |
577
+ | Architecture | EditContext | textarea | contenteditable |
487
578
 
488
- ### Browser Support
579
+ ## How It Works
489
580
 
490
- - Modern browsers with ES6+ support
491
- - Chrome, Firefox, Safari, Edge (latest versions)
492
- - Requires JavaScript modules support
581
+ Caret v0.3.0 uses Chrome's EditContext API to completely separate input handling from rendering:
493
582
 
494
- ## Contributing
583
+ 1. **EditContext** receives all keyboard input, IME, clipboard events
584
+ 2. **Text model** — a single string `text` is the source of truth
585
+ 3. **render()** — calls `hljs.highlight()` and sets `main.innerHTML`
586
+ 4. **Caret** — positioned via `Range.getBoundingClientRect()`, no canvas math
587
+ 5. **Undo/redo** — pure string operations stored in `window.caret`
495
588
 
496
- Contributions are welcome! Please follow these steps:
589
+ No dual-layer sync issues. No textarea fighting. No canvas measurements.
590
+
591
+ ## Contributing
497
592
 
498
593
  1. Fork the repository
499
594
  2. Create your feature branch (`git checkout -b feature/amazing-feature`)
500
- 3. Commit your changes (`git commit -m 'Add some amazing feature'`)
595
+ 3. Commit your changes (`git commit -m 'Add amazing feature'`)
501
596
  4. Push to the branch (`git push origin feature/amazing-feature`)
502
597
  5. Open a Pull Request
503
598
 
504
- ### Development Setup
505
-
506
- ```bash
507
- # Clone the repository
508
- git clone https://github.com/pfmcodes/lexius-editor.git
509
-
510
- # Navigate to the directory
511
- cd lexius-editor
512
-
513
- # Install dependencies
514
- npm install
515
-
516
- # Run development server (if applicable)
517
- npm run dev
518
-
519
- # Build the project
520
- npm run build
521
- ```
522
-
523
599
  ## License
524
600
 
525
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
526
-
527
- ## Performance Notes
528
- - Handles 10k+ lines smoothly in all browsers
529
- - Firefox DevTools can inspect up to 3M lines without breaking a sweat
530
- - Chrome DevTools politely requests you don't inspect past 300k lines
531
-
532
- ## Acknowledgments
533
-
534
- - Built with modern JavaScript/TypeScript
535
- - Syntax highlighting powered by Highlight.js
536
- - Inspired by various text editor projects in the JavaScript ecosystem
537
-
538
- ## Support
539
-
540
- - **Issues**: [GitHub Issues](https://github.com/pfmcodes/lexius-editor/issues)
541
- - **Discussions**: [GitHub Discussions](https://github.com/pfmcodes/lexius-editor/discussions)
542
-
543
- ## Links
544
-
545
- - [GitHub Repository](https://github.com/pfmcodes/lexius-editor)
546
- - [NPM Package](https://www.npmjs.com/package/@pfmcodes/caret) *(if published)*
601
+ MIT License see [LICENSE](LICENSE) for details.
547
602
 
548
603
  ---
549
604
 
550
- Made with ❤️ by [PFMCODES](https://github.com/PFMCODES)
605
+ Made with ❤️ by [PFMCODES](https://github.com/PFMCODES)