@pfmcodes/caret 0.1.4 → 0.2.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,25 +1,25 @@
1
1
  # Caret
2
2
 
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-64.0%25-f6fa03)](https://github.com/PFMCODES/lexius-edior)
5
- [![CSS](https://img.shields.io/badge/CSS-18.0%25-2e7ad1)](https://github.com/pfmcodes/lexius-editor)
6
- [![SCSS](https://img.shields.io/badge/SCSS-18.0%25-f8899d)](https://github.com/pfmcodes/lexius-editor)
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)
7
7
 
8
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.
9
9
 
10
- ## Features
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
14
- - 🔢 **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
12
+ - **Live Syntax Highlighting** - Real-time code highlighting powered by Highlight.js
13
+ - **Custom Caret** - Smooth, pixel-perfect Caret positioning and rendering
14
+ - **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
21
21
 
22
- ## 📋 Table of Contents
22
+ ## Table of Contents
23
23
 
24
24
  - [What's new](#What's-New?)
25
25
  - [Installation](#installation)
@@ -29,12 +29,14 @@ A lightweight, feature-rich code editor with real-time syntax highlighting and c
29
29
  - [Customization](#customization)
30
30
  - [Contributing](#contributing)
31
31
  - [License](#license)
32
+ - [Performance Notes](#performance-notes)
32
33
 
33
34
  ## What's-New?
34
35
 
35
- ### New fresh package directory, now the heavy work is done in the cloud while minimizing package size to just a few Kilo Bytes.
36
+ ### The editor has been optimized like crazy, the editor used to lag at 500 lines not it does not lag until 10k+ lines(x20 performance increase)
37
+ ### for more info about the performance check out [this.](#performance-notes)
36
38
 
37
- ## 🚀 Installation
39
+ ## Installation
38
40
 
39
41
  ### NPM
40
42
 
@@ -54,7 +56,7 @@ yarn add @pfmcodes/caret
54
56
  pnpm add @pfmcodes/caret
55
57
  ```
56
58
 
57
- ## 🏁 Quick Start
59
+ ## Quick Start
58
60
 
59
61
  ### Basic Usage
60
62
 
@@ -73,7 +75,7 @@ pnpm add @pfmcodes/caret
73
75
  <script type="module">
74
76
  import editor from './node_modules/@pfmcodes/caret/esm/index.js';
75
77
 
76
- const instance = await editor.createEditor(
78
+ const instance = await editor.editor.createEditor(
77
79
  document.getElementById('editor'),
78
80
  {
79
81
  value: 'console.log("Hello, World!");',
@@ -92,7 +94,7 @@ pnpm add @pfmcodes/caret
92
94
  import editor from '@pfmcodes/caret';
93
95
 
94
96
  // Create editor instance
95
- const editorInstance = await editor.createEditor(
97
+ const editorInstance = await editor.editor.createEditor(
96
98
  document.getElementById('editor'),
97
99
  {
98
100
  value: '', // Initial code
@@ -102,7 +104,7 @@ const editorInstance = await editor.createEditor(
102
104
  );
103
105
  ```
104
106
 
105
- ## 📁 Project Structure
107
+ ## Project Structure
106
108
 
107
109
  ```
108
110
  caret/
@@ -116,14 +118,14 @@ caret/
116
118
  └── LICENSE # MIT License
117
119
  ```
118
120
 
119
- ## 💡 Usage
121
+ ## Usage
120
122
 
121
123
  ### JavaScript Editor
122
124
 
123
125
  ```javascript
124
- import editor from '@pfmcodes/caret';
126
+ import editor from '@pfmcodes/caret'; // auto link to commonjs version
125
127
 
126
- const jsEditor = await editor.createEditor(
128
+ const jsEditor = await editor.editor.createEditor(
127
129
  document.getElementById('js-editor'),
128
130
  {
129
131
  value: `function greet(name) {
@@ -168,7 +170,7 @@ const emptyEditor = await editor.createEditor(
168
170
  );
169
171
  ```
170
172
 
171
- ## 🎨 Customization
173
+ ## Customization
172
174
 
173
175
  ### Custom Styling
174
176
 
@@ -205,7 +207,7 @@ The editor comes with default styles that you can override:
205
207
  }
206
208
  ```
207
209
 
208
- ## 🎯 Advanced Features
210
+ ## Advanced Features
209
211
 
210
212
  ### Multi-Language Support
211
213
 
@@ -290,7 +292,7 @@ Caret supports all Highlight.js themes. Popular options include:
290
292
  - `stackoverflow-light`
291
293
  - `xcode`
292
294
 
293
- ## 🛠️ API Reference
295
+ ## API Reference
294
296
 
295
297
  ### createEditor(container, options)
296
298
 
@@ -339,7 +341,7 @@ The editor features a custom-rendered Caret that adapts to your theme (light/dar
339
341
  #### Synchronized Scrolling
340
342
  All editor components (code, highlights, line numbers) scroll together smoothly.
341
343
 
342
- ## 📖 Complete Example
344
+ ## Complete Example
343
345
 
344
346
  Here's a complete working example:
345
347
 
@@ -457,7 +459,7 @@ for (let i = 0; i < 10; i++) {
457
459
  </html>
458
460
  ```
459
461
 
460
- ## ⚙️ Technical Details
462
+ ## Technical Details
461
463
 
462
464
  ### How It Works
463
465
 
@@ -478,6 +480,7 @@ The editor synchronizes all layers during:
478
480
  - **Real-time Highlighting**: Uses Highlight.js for fast, accurate syntax highlighting
479
481
  - **Canvas Measurement**: Employs HTML5 Canvas API for precise text width calculations
480
482
  - **Event Optimization**: Efficiently updates only what's necessary on each interaction
483
+ - **Heavy Optimization**: previous version(0.1.6) used to handle 500 lines before lagging, with new caret@0.2.0, there's almost 20 times perfomace increase now it can handle 10K+ lines smoothly before lagging(better results in optimized browsers like firfox)
481
484
 
482
485
  ### Browser Support
483
486
 
@@ -485,7 +488,7 @@ The editor synchronizes all layers during:
485
488
  - Chrome, Firefox, Safari, Edge (latest versions)
486
489
  - Requires JavaScript modules support
487
490
 
488
- ## 🤝 Contributing
491
+ ## Contributing
489
492
 
490
493
  Contributions are welcome! Please follow these steps:
491
494
 
@@ -514,26 +517,31 @@ npm run dev
514
517
  npm run build
515
518
  ```
516
519
 
517
- ## 📝 License
520
+ ## License
518
521
 
519
522
  This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
520
523
 
521
- ## 🙏 Acknowledgments
524
+ ## Performance Notes
525
+ - Handles 10k+ lines smoothly in all browsers
526
+ - Firefox DevTools can inspect up to 3M lines without breaking a sweat
527
+ - Chrome DevTools politely requests you don't inspect past 300k lines
528
+
529
+ ## Acknowledgments
522
530
 
523
531
  - Built with modern JavaScript/TypeScript
524
532
  - Syntax highlighting powered by Highlight.js
525
533
  - Inspired by various text editor projects in the JavaScript ecosystem
526
534
 
527
- ## 📞 Support
535
+ ## Support
528
536
 
529
537
  - **Issues**: [GitHub Issues](https://github.com/pfmcodes/lexius-editor/issues)
530
538
  - **Discussions**: [GitHub Discussions](https://github.com/pfmcodes/lexius-editor/discussions)
531
539
 
532
- ## 🔗 Links
540
+ ## Links
533
541
 
534
542
  - [GitHub Repository](https://github.com/pfmcodes/lexius-editor)
535
543
  - [NPM Package](https://www.npmjs.com/package/@pfmcodes/caret) *(if published)*
536
544
 
537
545
  ---
538
546
 
539
- Made with ❤️ by [PFMCODES](https://github.com/PFMCODES)
547
+ Made with ❤️ by [PFMCODES](https://github.com/PFMCODES)
@@ -18,6 +18,10 @@ async function createEditor(editor, data) {
18
18
  highlighted.id = "Caret-highlighted";
19
19
  caret.id = "Caret-caret";
20
20
  lineCounter.id = "Caret-lineCounter";
21
+ editor1.className = 'dark';
22
+ highlighted.className = 'dark';
23
+ caret.className = 'dark';
24
+ lineCounter.className = 'dark';
21
25
  editor1.style.backgroundColor = isDark ? "#222" : "#fff";
22
26
  let code = data.value || "";
23
27
  let language = data.language;
@@ -33,10 +37,10 @@ async function createEditor(editor, data) {
33
37
  const link = document.createElement("link");
34
38
  link.rel = "stylesheet";
35
39
  link.id = "Caret-theme";
36
- link.href = `./highlight.js/styles/${theme}.css`;
40
+ link.href = `https://esm.sh/@pfmcodes/highlight.js@1.0.0/styles/${theme}.css`;
37
41
  document.head.appendChild(link);
38
42
  } else {
39
- themeLink.href = `./highlight.js/styles/${theme}.css`;
43
+ themeLink.href = `https://esm.sh/@pfmcodes/highlight.js@1.0.0/styles/${theme}.css`;
40
44
  }
41
45
  } else {
42
46
  let themeLink = document.getElementById("Caret-theme");
@@ -44,7 +48,7 @@ async function createEditor(editor, data) {
44
48
  const link = document.createElement("link");
45
49
  link.rel = "stylesheet";
46
50
  link.id = "Caret-theme";
47
- link.href = `./highlight.js/styles/hybrid.css`;
51
+ link.href = `https://esm.sh/@pfmcodes/highlight.js@1.0.0/styles/hybrid.css`;
48
52
  document.head.appendChild(link);
49
53
  } else {
50
54
  themeLink.href = `./highlight.js/styles/hybrid.css`;
@@ -55,12 +59,13 @@ async function createEditor(editor, data) {
55
59
  editor1.autocomplete = "off";
56
60
  editor1.autocorrect = "off";
57
61
  editor.style = "position: relative; width: 600px; height: 300px; overflow: hidden; /* 👈 CRITICAL */ font-size: 14px;"
58
- if (code) {
59
- editor1.value = code;
62
+ if (code && editor && editor1 && language && highlighted) {
60
63
  editor1.style.paddingTop = "-9px";
61
- highlighted.innerHTML = hljs.highlight(code, { language: language }).value;
64
+ console.log(data.value + " data.value");
65
+ editor1.value = data.value;
66
+ highlighted.innerHTML = await _render(data.value, language, editor1);
62
67
  }
63
- const keyDown = (e) => {
68
+ const keyDown = async (e) => {
64
69
  if (e.key !== "Tab") return;
65
70
 
66
71
  e.preventDefault();
@@ -113,7 +118,7 @@ async function createEditor(editor, data) {
113
118
  editor1.selectionEnd =
114
119
  end + delta * newLines.length;
115
120
 
116
- highlighted.innerHTML = hljs.highlight(editor1.value, { language }).value;
121
+ highlighted.innerHTML = await _render(editor1.value, language, editor1);
117
122
  updateLineNumbers();
118
123
  updateCaret();
119
124
  }
@@ -139,6 +144,7 @@ async function createEditor(editor, data) {
139
144
  lineCounter.innerHTML = html;
140
145
  }
141
146
 
147
+ highlighted.style.paddingTop = "12px"
142
148
 
143
149
  function getFontMetrics() {
144
150
  const metrics = measureCtx.measureText("Mg");
@@ -187,19 +193,19 @@ async function createEditor(editor, data) {
187
193
  (lineHeight - ascent) +
188
194
  "px";
189
195
 
190
- caret.style.height = `${lineHeight}px`;
196
+ caret.style.height = `${lineHeight - 5}px`;
191
197
  }
192
- const input = () => {
198
+ const input = async () => {
193
199
  caret.style.opacity = "1";
194
- highlighted.innerHTML = hljs.highlight(editor1.value, { language: language }).value;
200
+ highlighted.innerHTML = await _render(editor1.value, language, editor1);
195
201
  updateLineNumbers();
196
202
  updateCaret();
197
203
  };
198
204
  editor1.addEventListener("input", input);
199
- const scroll = () => {
205
+ const scroll = async () => {
200
206
  const x = -editor1.scrollLeft;
201
207
  const y = -editor1.scrollTop;
202
-
208
+ highlighted.innerHTML = await _render(editor1.value, language, editor1);
203
209
  highlighted.style.transform = `translate(${x}px, ${y}px)`;
204
210
  caret.style.transform = `translate(${x}px, ${y}px)`;
205
211
  lineCounter.style.transform = `translateY(${y}px)`;
@@ -225,8 +231,8 @@ async function createEditor(editor, data) {
225
231
  editor1.removeEventListener('keydown', keyDown);
226
232
  editor.innerHTML = "";
227
233
  }
228
- function refresh() {
229
- highlighted.innerHTML = hljs.highlight(editor1.value, { language }).value;
234
+ async function refresh() {
235
+ highlighted.innerHTML = await _render(editor1.value, language, editor1);
230
236
  updateLineNumbers();
231
237
  updateCaret();
232
238
  }
@@ -258,6 +264,52 @@ async function createEditor(editor, data) {
258
264
  };
259
265
  }
260
266
 
267
+ function escapeHtml(str) {
268
+ return str
269
+ .replace(/&/g, "&amp;")
270
+ .replace(/</g, "&lt;")
271
+ .replace(/>/g, "&gt;");
272
+ }
273
+
274
+ async function _render(code, language, editor) {
275
+ // If no editor context provided, just highlight everything (initial load)
276
+ if (!editor) {
277
+ return hljs.highlight(code, { language }).value;
278
+ }
279
+
280
+ const scrollTop = editor.scrollTop;
281
+ const scrollBottom = scrollTop + editor.clientHeight;
282
+ const style = getComputedStyle(editor);
283
+ const lineHeight = parseFloat(style.lineHeight);
284
+
285
+ // Calculate visible line range
286
+ const startLine = Math.floor(scrollTop / lineHeight);
287
+ const endLine = Math.ceil(scrollBottom / lineHeight);
288
+
289
+ const lines = code.split("\n");
290
+
291
+ // Add buffer (render extra lines above/below for smooth scrolling)
292
+ const bufferLines = 10;
293
+ const visibleStart = Math.max(0, startLine - bufferLines) || 0;
294
+ const visibleEnd = Math.min(lines.length, endLine + bufferLines) || 0;
295
+
296
+ // Split into three sections
297
+ const beforeLines = lines.slice(0, visibleStart);
298
+ const visibleLines = lines.slice(visibleStart, visibleEnd);
299
+ const afterLines = lines.slice(visibleEnd);
300
+
301
+ // Only highlight visible portion
302
+
303
+ const highlightedVisible = hljs.highlight(visibleLines.join("\n"), { language }).value;
304
+ // Plain text for non-visible areas (no highlighting = faster)
305
+ if (highlightedVisible.trim() === "") {
306
+ return hljs.highlight(escapeHtml(code), { language }).value;
307
+ }
308
+ const beforeHTML = "\n".repeat(beforeLines.length);
309
+ const afterHTML = "\n".repeat(afterLines.length);
310
+ return beforeHTML + highlightedVisible + afterHTML;
311
+ }
312
+
261
313
  const editor = {
262
314
  createEditor
263
315
  };
package/commonjs/theme.js CHANGED
@@ -1,6 +1,6 @@
1
1
  function setTheme(name) {
2
2
  const link = document.getElementById("Caret-theme");
3
- link.href = `./highlight.js/styles/${name}.css`;
3
+ link.href = `https://esm.sh/@pfmcodes/highlight.js@1.0.0/styles/${name}.css`;
4
4
  }
5
5
 
6
6
  function removeTheme() {
package/esm/editor.js CHANGED
@@ -22,7 +22,6 @@ async function createEditor(editor, data) {
22
22
  highlighted.className = 'dark';
23
23
  caret.className = 'dark';
24
24
  lineCounter.className = 'dark';
25
- editor.classList.add("");
26
25
  editor1.style.backgroundColor = isDark ? "#222" : "#fff";
27
26
  let code = data.value || "";
28
27
  let language = data.language;
@@ -38,10 +37,10 @@ async function createEditor(editor, data) {
38
37
  const link = document.createElement("link");
39
38
  link.rel = "stylesheet";
40
39
  link.id = "Caret-theme";
41
- link.href = `./highlight.js/styles/${theme}.css`;
40
+ link.href = `https://esm.sh/@pfmcodes/highlight.js@1.0.0/styles/${theme}.css`;
42
41
  document.head.appendChild(link);
43
42
  } else {
44
- themeLink.href = `./highlight.js/styles/${theme}.css`;
43
+ themeLink.href = `https://esm.sh/@pfmcodes/highlight.js@1.0.0/styles/${theme}.css`;
45
44
  }
46
45
  } else {
47
46
  let themeLink = document.getElementById("Caret-theme");
@@ -49,7 +48,7 @@ async function createEditor(editor, data) {
49
48
  const link = document.createElement("link");
50
49
  link.rel = "stylesheet";
51
50
  link.id = "Caret-theme";
52
- link.href = `./highlight.js/styles/hybrid.css`;
51
+ link.href = `https://esm.sh/@pfmcodes/highlight.js@1.0.0/styles/hybrid.css`;
53
52
  document.head.appendChild(link);
54
53
  } else {
55
54
  themeLink.href = `./highlight.js/styles/hybrid.css`;
@@ -60,12 +59,13 @@ async function createEditor(editor, data) {
60
59
  editor1.autocomplete = "off";
61
60
  editor1.autocorrect = "off";
62
61
  editor.style = "position: relative; width: 600px; height: 300px; overflow: hidden; /* 👈 CRITICAL */ font-size: 14px;"
63
- if (code) {
64
- editor1.value = code;
62
+ if (code && editor && editor1 && language && highlighted) {
65
63
  editor1.style.paddingTop = "-9px";
66
- highlighted.innerHTML = hljs.highlight(code, { language: language }).value;
64
+ console.log(data.value + " data.value");
65
+ editor1.value = data.value;
66
+ highlighted.innerHTML = await _render(data.value, language, editor1);
67
67
  }
68
- const keyDown = (e) => {
68
+ const keyDown = async (e) => {
69
69
  if (e.key !== "Tab") return;
70
70
 
71
71
  e.preventDefault();
@@ -118,7 +118,7 @@ async function createEditor(editor, data) {
118
118
  editor1.selectionEnd =
119
119
  end + delta * newLines.length;
120
120
 
121
- highlighted.innerHTML = hljs.highlight(editor1.value, { language }).value;
121
+ highlighted.innerHTML = await _render(editor1.value, language, editor1);
122
122
  updateLineNumbers();
123
123
  updateCaret();
124
124
  }
@@ -195,17 +195,17 @@ async function createEditor(editor, data) {
195
195
 
196
196
  caret.style.height = `${lineHeight - 5}px`;
197
197
  }
198
- const input = () => {
198
+ const input = async () => {
199
199
  caret.style.opacity = "1";
200
- highlighted.innerHTML = hljs.highlight(editor1.value, { language: language }).value;
200
+ highlighted.innerHTML = await _render(editor1.value, language, editor1);
201
201
  updateLineNumbers();
202
202
  updateCaret();
203
203
  };
204
204
  editor1.addEventListener("input", input);
205
- const scroll = () => {
205
+ const scroll = async () => {
206
206
  const x = -editor1.scrollLeft;
207
207
  const y = -editor1.scrollTop;
208
-
208
+ highlighted.innerHTML = await _render(editor1.value, language, editor1);
209
209
  highlighted.style.transform = `translate(${x}px, ${y}px)`;
210
210
  caret.style.transform = `translate(${x}px, ${y}px)`;
211
211
  lineCounter.style.transform = `translateY(${y}px)`;
@@ -231,8 +231,8 @@ async function createEditor(editor, data) {
231
231
  editor1.removeEventListener('keydown', keyDown);
232
232
  editor.innerHTML = "";
233
233
  }
234
- function refresh() {
235
- highlighted.innerHTML = hljs.highlight(editor1.value, { language }).value;
234
+ async function refresh() {
235
+ highlighted.innerHTML = await _render(editor1.value, language, editor1);
236
236
  updateLineNumbers();
237
237
  updateCaret();
238
238
  }
@@ -264,6 +264,52 @@ async function createEditor(editor, data) {
264
264
  };
265
265
  }
266
266
 
267
+ function escapeHtml(str) {
268
+ return str
269
+ .replace(/&/g, "&amp;")
270
+ .replace(/</g, "&lt;")
271
+ .replace(/>/g, "&gt;");
272
+ }
273
+
274
+ async function _render(code, language, editor) {
275
+ // If no editor context provided, just highlight everything (initial load)
276
+ if (!editor) {
277
+ return hljs.highlight(code, { language }).value;
278
+ }
279
+
280
+ const scrollTop = editor.scrollTop;
281
+ const scrollBottom = scrollTop + editor.clientHeight;
282
+ const style = getComputedStyle(editor);
283
+ const lineHeight = parseFloat(style.lineHeight);
284
+
285
+ // Calculate visible line range
286
+ const startLine = Math.floor(scrollTop / lineHeight);
287
+ const endLine = Math.ceil(scrollBottom / lineHeight);
288
+
289
+ const lines = code.split("\n");
290
+
291
+ // Add buffer (render extra lines above/below for smooth scrolling)
292
+ const bufferLines = 10;
293
+ const visibleStart = Math.max(0, startLine - bufferLines) || 0;
294
+ const visibleEnd = Math.min(lines.length, endLine + bufferLines) || 0;
295
+
296
+ // Split into three sections
297
+ const beforeLines = lines.slice(0, visibleStart);
298
+ const visibleLines = lines.slice(visibleStart, visibleEnd);
299
+ const afterLines = lines.slice(visibleEnd);
300
+
301
+ // Only highlight visible portion
302
+
303
+ const highlightedVisible = hljs.highlight(visibleLines.join("\n"), { language }).value;
304
+ // Plain text for non-visible areas (no highlighting = faster)
305
+ if (highlightedVisible.trim() === "") {
306
+ return hljs.highlight(escapeHtml(code), { language }).value;
307
+ }
308
+ const beforeHTML = "\n".repeat(beforeLines.length);
309
+ const afterHTML = "\n".repeat(afterLines.length);
310
+ return beforeHTML + highlightedVisible + afterHTML;
311
+ }
312
+
267
313
  const editor = {
268
314
  createEditor
269
315
  };
package/esm/theme.js CHANGED
@@ -1,6 +1,6 @@
1
1
  function setTheme(name) {
2
2
  const link = document.getElementById("Caret-theme");
3
- link.href = `./highlight.js/styles/${name}.css`;
3
+ link.href = `https://esm.sh/@pfmcodes/highlight.js@1.0.0/styles/${name}.css`;
4
4
  }
5
5
 
6
6
  function removeTheme() {
package/index.css CHANGED
@@ -2,16 +2,21 @@
2
2
  position: absolute;
3
3
  border: none;
4
4
  inset: 0;
5
- padding: 10px;
5
+ padding: 10px 10px 10px 0px;
6
6
  font-family: monospace;
7
7
  background: transparent;
8
8
  color: transparent;
9
9
  caret-color: #fff;
10
10
  border: 1px solid #ccc;
11
- overflow: scroll; /* 👈 ONLY THIS SCROLLS */
11
+ overflow-x: scroll; /* 👈 ONLY THIS SCROLLS */
12
12
  z-index: 1;
13
13
  width: 100%;
14
14
  }
15
+
16
+ #Caret-texarea::-webkit-scrollbar {
17
+ z-index: 3;
18
+ }
19
+
15
20
  #Caret-textarea:focus {
16
21
  outline: none;
17
22
  }
package/package.json CHANGED
@@ -1,16 +1,18 @@
1
1
  {
2
2
  "name": "@pfmcodes/caret",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "description": "The official code editor engine for lexius",
5
5
  "type": "module",
6
- "main": "./index.cjs",
7
- "types": "./types/types.d.ts",
6
+ "main": "./esm/index.js",
7
+ "types": "./types/index.d.ts",
8
8
  "exports": {
9
9
  ".": {
10
+ "types": "./types/index.d.ts",
10
11
  "import": "./esm/index.js",
11
- "require": "./commonjs/index.js",
12
- "types": "./types/index.d.ts"
13
- }
12
+ "require": "./commonjs/index.js"
13
+ },
14
+ "./commonjs": "./commonjs/index.js",
15
+ "./esm": "./esm/index.js"
14
16
  },
15
17
  "repository": {
16
18
  "type": "git",
@@ -24,6 +26,7 @@
24
26
  "syntax-highlighting",
25
27
  "language-server",
26
28
  "typescript",
29
+ "python",
27
30
  "javascript",
28
31
  "ide",
29
32
  "browser-editor",
@@ -35,4 +38,4 @@
35
38
  "url": "https://github.com/PFMCODES/lexius-editor/issues"
36
39
  },
37
40
  "homepage": "https://github.com/PFMCODES/lexius-editor#readme"
38
- }
41
+ }
@@ -0,0 +1,334 @@
1
+ import hljs from "https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/core.js"; // Use default export
2
+ import languages from "./languages.ts";
3
+
4
+ languages.init();
5
+
6
+ async function createEditor(editor: HTMLElement, data: { value?: string, language: string, theme?: string }) {
7
+ const editor1: HTMLTextAreaElement = document.createElement("textarea");
8
+ const highlighted: HTMLPreElement = document.createElement("pre");
9
+ const caret: HTMLDivElement = document.createElement("div");
10
+ const measureCanvas: HTMLCanvasElement = document.createElement("canvas");
11
+ const measureCtx: CanvasRenderingContext2D = measureCanvas.getContext("2d") as CanvasRenderingContext2D;
12
+ if (!measureCtx) {
13
+ throw new Error("Failed to get 2D context from canvas");
14
+ }
15
+ const isDark = data.theme && (data.theme.includes("dark") || data.theme.includes("night"));
16
+ const caretColor = isDark ? "#fff" : "#7116d8";
17
+ const lineColor = isDark ? "#fff" : "#000";
18
+ const lineCounter: HTMLDivElement = document.createElement("div");
19
+
20
+ editor1.id = "Caret-textarea";
21
+ highlighted.id = "Caret-highlighted";
22
+ caret.id = "Caret-caret";
23
+ lineCounter.id = "Caret-lineCounter";
24
+ editor1.className = 'dark';
25
+ highlighted.className = 'dark';
26
+ caret.className = 'dark';
27
+ lineCounter.className = 'dark';
28
+ editor1.style.backgroundColor = isDark ? "#222" : "#fff";
29
+ let code = data.value || "";
30
+ let language = data.language;
31
+ let theme = data.theme;
32
+ if (!languages.registeredLanguages.includes(language)) {
33
+ const mod = await import(`https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/languages/${language}.js`);
34
+ languages.registerLanguage(language, mod.default);
35
+ languages.registeredLanguages.push(language);
36
+ }
37
+ if (theme) {
38
+ let themeLink: HTMLLinkElement | null = document.getElementById("Caret-theme") as HTMLLinkElement;
39
+ if (!themeLink) {
40
+ const link = document.createElement("link");
41
+ link.rel = "stylesheet";
42
+ link.id = "Caret-theme";
43
+ link.href = `https://esm.sh/@pfmcodes/highlight.js@1.0.0/styles/${theme}.css`;
44
+ document.head.appendChild(link);
45
+ } else {
46
+ themeLink.href = `https://esm.sh/@pfmcodes/highlight.js@1.0.0/styles/${theme}.css`;
47
+ }
48
+ } else {
49
+ let themeLink: HTMLLinkElement | null = document.getElementById("Caret-theme") as HTMLLinkElement;
50
+ if (!themeLink) {
51
+ const link = document.createElement("link");
52
+ link.rel = "stylesheet";
53
+ link.id = "Caret-theme";
54
+ link.href = `https://esm.sh/@pfmcodes/highlight.js@1.0.0/styles/hybrid.css`;
55
+ document.head.appendChild(link);
56
+ } else {
57
+ themeLink.href = `./highlight.js/styles/hybrid.css`;
58
+ }
59
+ }
60
+ editor1.spellcheck = false;
61
+ editor1.autocapitalize = "off";
62
+ editor1.autocomplete = "off";
63
+ editor1.autocorrect = "off" as any;
64
+ editor.style = "position: relative; width: 600px; height: 300px; overflow: hidden; /* 👈 CRITICAL */ font-size: 14px;"
65
+ if (code && editor && editor1 && language && highlighted) {
66
+ editor1.style.paddingTop = "-9px";
67
+ console.log(data.value + " data.value");
68
+ editor1.value = data.value as string;
69
+ highlighted.innerHTML = await _render(code, language, editor1);
70
+ }
71
+ const keyDown = async (e: KeyboardEvent) => {
72
+ if (e.key !== "Tab") return;
73
+
74
+ e.preventDefault();
75
+
76
+ const value = editor1.value;
77
+ const start = editor1.selectionStart;
78
+ const end = editor1.selectionEnd;
79
+
80
+ const indent = " ";
81
+
82
+ // Find line start & end
83
+ const lineStart = value.lastIndexOf("\n", start - 1) + 1;
84
+ const lineEnd = value.indexOf("\n", end);
85
+ const finalLineEnd = lineEnd === -1 ? value.length : lineEnd;
86
+
87
+ const block = value.slice(lineStart, finalLineEnd);
88
+ const lines = block.split("\n");
89
+
90
+ let newLines;
91
+ let delta = 0;
92
+
93
+ if (e.shiftKey) {
94
+ // UNINDENT
95
+ newLines = lines.map(line => {
96
+ if (line.startsWith(indent)) {
97
+ delta -= indent.length;
98
+ return line.slice(indent.length);
99
+ }
100
+ if (line.startsWith("\t")) {
101
+ delta -= 1;
102
+ return line.slice(1);
103
+ }
104
+ return line;
105
+ });
106
+ } else {
107
+ // INDENT
108
+ newLines = lines.map(line => indent + line);
109
+ delta = indent.length;
110
+ }
111
+
112
+ const newBlock = newLines.join("\n");
113
+
114
+ editor1.value =
115
+ value.slice(0, lineStart) +
116
+ newBlock +
117
+ value.slice(finalLineEnd);
118
+
119
+ // Fix selection
120
+ editor1.selectionStart = start + delta;
121
+ editor1.selectionEnd =
122
+ end + delta * newLines.length;
123
+
124
+ highlighted.innerHTML = await _render(editor1.value, language, editor1);
125
+ updateLineNumbers();
126
+ updateCaret();
127
+ }
128
+ editor1.addEventListener("keydown", keyDown);
129
+ editor.appendChild(lineCounter);
130
+ editor.appendChild(highlighted);
131
+ editor.appendChild(editor1);
132
+ editor.appendChild(caret);
133
+
134
+ function updateFontMetrics() {
135
+ const style = getComputedStyle(editor1);
136
+ measureCtx.font = `${style.fontSize} ${style.fontFamily}`;
137
+ }
138
+
139
+ function updateLineNumbers() {
140
+ const lineCount = editor1.value.split("\n").length;
141
+
142
+ let html = "";
143
+ for (let i = 1; i <= lineCount; i++) {
144
+ html += `<div class="Caret-lineCounter-number" style="color: ${lineColor}">${i}</div>`;
145
+ }
146
+
147
+ lineCounter.innerHTML = html;
148
+ }
149
+
150
+ highlighted.style.paddingTop = "12px"
151
+
152
+ function getFontMetrics() {
153
+ const metrics = measureCtx.measureText("Mg");
154
+ return {
155
+ ascent: metrics.actualBoundingBoxAscent,
156
+ descent: metrics.actualBoundingBoxDescent,
157
+ height: metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent
158
+ };
159
+ }
160
+ const focus = () => {
161
+ caret.style.opacity = "1";
162
+ caret.style.background = caretColor;
163
+ };
164
+
165
+ editor1.addEventListener("focus", focus);
166
+
167
+ const blur = () => {
168
+ caret.style.opacity = "0";
169
+ };
170
+
171
+ editor1.addEventListener("blur", blur);
172
+
173
+ function updateCaret() {
174
+ const start = editor1.selectionStart;
175
+ const text = editor1.value.slice(0, start);
176
+
177
+ const lines = text.split("\n");
178
+ const lineIndex = lines.length - 1;
179
+ const lineText = lines[lineIndex].replace(/\t/g, " ");
180
+
181
+ const style = getComputedStyle(editor1);
182
+ const paddingLeft = parseFloat(style.paddingLeft);
183
+ const paddingTop = parseFloat(style.paddingTop);
184
+ const lineHeight = parseFloat(style.lineHeight);
185
+
186
+ updateFontMetrics();
187
+ const metrics = measureCtx.measureText("Mg");
188
+ const ascent = metrics.actualBoundingBoxAscent;
189
+
190
+ caret.style.left =
191
+ paddingLeft + measureCtx.measureText(lineText).width + "px";
192
+ caret.style.top =
193
+ -9 +
194
+ paddingTop +
195
+ lineIndex * lineHeight +
196
+ (lineHeight - ascent) +
197
+ "px";
198
+
199
+ caret.style.height = `${lineHeight - 5}px`;
200
+ }
201
+ const input = async () => {
202
+ caret.style.opacity = "1";
203
+ highlighted.innerHTML = await _render(editor1.value, language, editor1);
204
+ updateLineNumbers();
205
+ updateCaret();
206
+ };
207
+ editor1.addEventListener("input", input);
208
+ const scroll = async () => {
209
+ const x = -editor1.scrollLeft;
210
+ const y = -editor1.scrollTop;
211
+ highlighted.innerHTML = await _render(editor1.value, language, editor1);
212
+ highlighted.style.transform = `translate(${x}px, ${y}px)`;
213
+ caret.style.transform = `translate(${x}px, ${y}px)`;
214
+ lineCounter.style.transform = `translateY(${y}px)`;
215
+ };
216
+ editor1.addEventListener("scroll", scroll);
217
+
218
+ updateFontMetrics();
219
+ getFontMetrics();
220
+
221
+ editor1.addEventListener("click", updateCaret);
222
+ editor1.addEventListener("keyup", updateCaret);
223
+
224
+ // Initial caret position
225
+ updateLineNumbers();
226
+ updateCaret();
227
+
228
+ // Focus the editor
229
+ editor1.focus();
230
+ function destroy() {
231
+ editor1.removeEventListener('click', updateCaret);
232
+ editor1.removeEventListener('keyup', updateCaret);
233
+ editor1.removeEventListener('scroll', scroll);
234
+ editor1.removeEventListener('keydown', keyDown);
235
+ editor.innerHTML = "";
236
+ }
237
+ async function refresh() {
238
+ highlighted.innerHTML = await _render(editor1.value, language, editor1);
239
+ updateLineNumbers();
240
+ updateCaret();
241
+ }
242
+ function getValue() {
243
+ return editor1.value;
244
+ }
245
+ function setValue(i: string) {
246
+ editor1.value = i;
247
+ refresh();
248
+ }
249
+ async function setLanguage(l: string) {
250
+ if (!languages.registeredLanguages.includes(l)) {
251
+ if (l === "html" || l === "svg") {
252
+ language = "xml";
253
+ l = "xml";
254
+ }
255
+ const mod = await import(`https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/languages/${l}.js`);
256
+
257
+ }
258
+ language = l;
259
+ refresh();
260
+ }
261
+ return {
262
+ getValue,
263
+ setValue,
264
+ focus,
265
+ setLanguage,
266
+ destroy
267
+ };
268
+ }
269
+
270
+ function escapeHtml(str: string) {
271
+ return str
272
+ .replace(/&/g, "&amp;")
273
+ .replace(/</g, "&lt;")
274
+ .replace(/>/g, "&gt;");
275
+ }
276
+
277
+ async function _render(code: string, language: string, editor: HTMLElement) {
278
+ // If no editor context provided, just highlight everything (initial load)
279
+ if (!editor) {
280
+ return hljs.highlight(code, { language }).value;
281
+ }
282
+
283
+ const scrollTop = editor.scrollTop;
284
+ const scrollBottom = scrollTop + editor.clientHeight;
285
+ const style = getComputedStyle(editor);
286
+ const lineHeight = parseFloat(style.lineHeight);
287
+
288
+ // Calculate visible line range
289
+ const startLine = Math.floor(scrollTop / lineHeight);
290
+ const endLine = Math.ceil(scrollBottom / lineHeight);
291
+
292
+ const lines = code.split("\n");
293
+
294
+ // Add buffer (render extra lines above/below for smooth scrolling)
295
+ const bufferLines = 10;
296
+ const visibleStart = Math.max(0, startLine - bufferLines) || 0;
297
+ const visibleEnd = Math.min(lines.length, endLine + bufferLines) || 0;
298
+
299
+ // Split into three sections
300
+ const beforeLines = lines.slice(0, visibleStart);
301
+ const visibleLines = lines.slice(visibleStart, visibleEnd);
302
+ const afterLines = lines.slice(visibleEnd);
303
+
304
+ // Only highlight visible portion
305
+
306
+ const highlightedVisible = hljs.highlight(visibleLines.join("\n"), { language }).value;
307
+ // Plain text for non-visible areas (no highlighting = faster)
308
+ if (highlightedVisible.trim() === "") {
309
+ return hljs.highlight(escapeHtml(code), { language }).value;
310
+ }
311
+ const beforeHTML = "\n".repeat(beforeLines.length);
312
+ const afterHTML = "\n".repeat(afterLines.length);
313
+ return beforeHTML + highlightedVisible + afterHTML;
314
+ }
315
+
316
+ const editor = {
317
+ createEditor
318
+ };
319
+
320
+ export default editor;
321
+
322
+ /*
323
+
324
+ createEditor: creates the main editor, using html Elements like, textarea and etc.
325
+ refresh: refreshs the editor
326
+ getValue: return the current value from the editor
327
+ setValue: sets a certain value to the editor's value
328
+ focus: focusses the editor
329
+ destroy: destroys and removeEventListeners
330
+ updateCaret: updates the caret positon, height and other metrics using math
331
+ updateLineNumbers: just add new line numbers
332
+ getFontMetrics: returns back the font's metrics like height
333
+ updateFontMetrics: update the fontMetrics
334
+ */
@@ -0,0 +1,4 @@
1
+ declare module "https://esm.sh/@pfmcodes/highlight.js@1.0.0/*" {
2
+ const mod: any;
3
+ export default mod;
4
+ }
@@ -0,0 +1,34 @@
1
+ declare module "@pfmcodes/caret" {
2
+ interface CaretEditorAPI {
3
+ createEditor(
4
+ el: HTMLElement,
5
+ data: any
6
+ ): Promise<{
7
+ getValue(): string;
8
+ setValue(v: string): void;
9
+ focus(): void;
10
+ setLanguage(l: string): Promise<void>;
11
+ destroy(): void;
12
+ }>;
13
+ }
14
+
15
+ interface CaretThemeApi {
16
+ setTheme(name: string): void;
17
+ removeTheme(name: string): void;
18
+ }
19
+
20
+ interface CaretLanguageApi {
21
+ registeredLanguages: string[];
22
+ init(): void;
23
+ registerLanguage(name: string, definition: any): void;
24
+ hljs: any;
25
+ }
26
+
27
+ const Caret: {
28
+ editor: CaretEditorAPI;
29
+ theme: CaretThemeApi;
30
+ language: CaretLanguageApi;
31
+ };
32
+
33
+ export default Caret;
34
+ }
package/types/index.ts ADDED
@@ -0,0 +1,10 @@
1
+ import editor from "./editor.ts";
2
+ import theme from "./theme.ts";
3
+ import language from "./languages.ts";
4
+
5
+ const Caret = {
6
+ editor,
7
+ theme,
8
+ language
9
+ }
10
+ export default Caret;
@@ -0,0 +1,117 @@
1
+ // @ts-ignore
2
+ import javascript from "https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/languages/javascript.js";
3
+ // @ts-ignore
4
+ import xml from "https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/languages/xml.js";
5
+ // @ts-ignore
6
+ import css from "https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/languages/css.js";
7
+ // @ts-ignore
8
+ import python from "https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/languages/python.js";
9
+ // @ts-ignore
10
+ import java from "https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/languages/java.js";
11
+ // @ts-ignore
12
+ import csharp from "https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/languages/csharp.js";
13
+ // @ts-ignore
14
+ import cpp from "https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/languages/cpp.js";
15
+ // @ts-ignore
16
+ import ruby from "https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/languages/ruby.js";
17
+ // @ts-ignore
18
+ import php from "https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/languages/php.js";
19
+ // @ts-ignore
20
+ import go from "https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/languages/go.js";
21
+ // @ts-ignore
22
+ import c from "https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/languages/c.js";
23
+ // @ts-ignore
24
+ import rust from "https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/languages/rust.js";
25
+ // @ts-ignore
26
+ import kotlin from "https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/languages/kotlin.js";
27
+ // @ts-ignore
28
+ import swift from "https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/languages/swift.js";
29
+ // @ts-ignore
30
+ import typescript from "https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/languages/typescript.js";
31
+ // @ts-ignore
32
+ import json from "https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/languages/json.js";
33
+ // @ts-ignore
34
+ import bash from "https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/languages/bash.js";
35
+ // @ts-ignore
36
+ import plaintext from "https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/languages/plaintext.js";
37
+ // @ts-ignore
38
+ import hljs from "https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/core.js";
39
+
40
+ let registeredLanguages: Array<T> = [];
41
+ type T = any
42
+
43
+ function init() {
44
+ // Register all languages
45
+ hljs.registerLanguage("javascript", javascript);
46
+ hljs.registerLanguage("xml", xml);
47
+ hljs.registerLanguage("css", css);
48
+ hljs.registerLanguage("html", xml);
49
+ hljs.registerLanguage("python", python);
50
+ hljs.registerLanguage("java", java);
51
+ hljs.registerLanguage("csharp", csharp);
52
+ hljs.registerLanguage("cpp", cpp);
53
+ hljs.registerLanguage("ruby", ruby);
54
+ hljs.registerLanguage("php", php);
55
+ hljs.registerLanguage("go", go);
56
+ hljs.registerLanguage("c", c);
57
+ hljs.registerLanguage("rust", rust);
58
+ hljs.registerLanguage("kotlin", kotlin);
59
+ hljs.registerLanguage("swift", swift);
60
+ hljs.registerLanguage("typescript", typescript);
61
+ hljs.registerLanguage("json", json);
62
+ hljs.registerLanguage("bash", bash);
63
+ hljs.registerLanguage("shell", bash);
64
+ hljs.registerLanguage("sh", bash);
65
+ hljs.registerLanguage("plaintext", plaintext);
66
+ registeredLanguages = [
67
+ "javascript",
68
+ "js",
69
+ "xml",
70
+ "html",
71
+ "svg",
72
+ "python",
73
+ "java",
74
+ "csharp",
75
+ "cpp",
76
+ "ruby",
77
+ "php",
78
+ "go",
79
+ "c",
80
+ "rust",
81
+ "kotlin",
82
+ "swift",
83
+ "typescript",
84
+ "json",
85
+ "bash",
86
+ "shell",
87
+ "sh",
88
+ "plaintext"
89
+ ]
90
+ }
91
+
92
+ function registerLanguage(name: string, definition: any) {
93
+ hljs.registerLanguage(name, definition);
94
+ if (!registeredLanguages.includes(name)) {
95
+ registeredLanguages.push(name);
96
+ }
97
+ }
98
+
99
+ const languages = {
100
+ init,
101
+ registeredLanguages,
102
+ registerLanguage,
103
+ hljs
104
+ }
105
+
106
+ export default languages;
107
+
108
+ /*
109
+
110
+ registeredLannguage: added for the editor.js can check if the langauge provided already is regsitered or not
111
+
112
+
113
+ init: just registers some languages and updates the registeredLangauges variable
114
+
115
+ registerLanguage: just registers a language
116
+
117
+ */
package/types/theme.ts ADDED
@@ -0,0 +1,18 @@
1
+ function setTheme(name: string) {
2
+ const link = document.getElementById("Caret-theme") as HTMLLinkElement;
3
+ link.href = `./highlight.js/styles/${name}.css`;
4
+ }
5
+
6
+ function removeTheme() {
7
+ const link = document.getElementById("Caret-theme") as HTMLLinkElement;
8
+ if (link && link.parentNode) {
9
+ link.parentNode.removeChild(link);
10
+ }
11
+ }
12
+
13
+ const theme = {
14
+ removeTheme,
15
+ setTheme
16
+ }
17
+
18
+ export default theme;