@pfmcodes/caret 0.3.1 → 0.3.2
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 +36 -19
- package/components/textEditor.js +180 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -191,7 +191,7 @@ pnpm add @pfmcodes/caret
|
|
|
191
191
|
<div id="result"></div>
|
|
192
192
|
|
|
193
193
|
<script type="module">
|
|
194
|
-
import Caret from '
|
|
194
|
+
import Caret from './index.js';
|
|
195
195
|
|
|
196
196
|
window.language = 'javascript';
|
|
197
197
|
window.currentTheme = 'tokyo-night-dark';
|
|
@@ -239,13 +239,12 @@ for i in range(25):
|
|
|
239
239
|
const editorInstance = await Caret.createEditor(
|
|
240
240
|
document.getElementById('editor'),
|
|
241
241
|
jsCode,
|
|
242
|
-
'demo-editor',
|
|
243
242
|
{
|
|
244
243
|
dark: true,
|
|
245
244
|
language: 'javascript',
|
|
245
|
+
id: `${Math.floor(Math.random() * 1000000000)}`,
|
|
246
246
|
hlTheme: 'tokyo-night-dark',
|
|
247
247
|
focusColor: '#7116d8',
|
|
248
|
-
id: Math.floor(Math.random() * 1000000000),
|
|
249
248
|
theme: {
|
|
250
249
|
dark: {
|
|
251
250
|
'background.editor': '#1a1a2e',
|
|
@@ -264,7 +263,7 @@ for i in range(25):
|
|
|
264
263
|
}
|
|
265
264
|
}
|
|
266
265
|
);
|
|
267
|
-
|
|
266
|
+
|
|
268
267
|
window.editorInstance = editorInstance;
|
|
269
268
|
|
|
270
269
|
window.changeLanguage = async (lang) => {
|
|
@@ -358,8 +357,8 @@ import Caret from './node_modules/@pfmcodes/caret/index.js';
|
|
|
358
357
|
const editor = await Caret.createEditor(
|
|
359
358
|
document.getElementById('editor'), // parent element
|
|
360
359
|
'const x = 42;', // initial content
|
|
361
|
-
'my-editor', // unique id
|
|
362
360
|
{
|
|
361
|
+
id: 'my-editor', // unique id
|
|
363
362
|
dark: true,
|
|
364
363
|
language: 'javascript',
|
|
365
364
|
hlTheme: 'tokyo-night-dark'
|
|
@@ -375,10 +374,10 @@ import Caret from './node_modules/@pfmcodes/caret/index.js';
|
|
|
375
374
|
const editor = await Caret.createEditor(
|
|
376
375
|
document.getElementById('editor'),
|
|
377
376
|
code,
|
|
378
|
-
'readonly-editor',
|
|
379
377
|
{
|
|
380
378
|
dark: true,
|
|
381
379
|
language: 'python',
|
|
380
|
+
id: 'readonly-editor',
|
|
382
381
|
hlTheme: 'github-dark',
|
|
383
382
|
lock: true
|
|
384
383
|
}
|
|
@@ -391,8 +390,8 @@ const editor = await Caret.createEditor(
|
|
|
391
390
|
import Caret from './node_modules/@pfmcodes/caret/index.js';
|
|
392
391
|
|
|
393
392
|
// each editor needs a unique id
|
|
394
|
-
const editor1 = await Caret.createEditor(el1, code1, 'editor-1', options);
|
|
395
|
-
const editor2 = await Caret.createEditor(el2, code2, 'editor-2', options);
|
|
393
|
+
const editor1 = await Caret.createEditor(el1, code1, {id: 'editor-1', ...options});
|
|
394
|
+
const editor2 = await Caret.createEditor(el2, code2, { id: 'editor-2', ...options });
|
|
396
395
|
```
|
|
397
396
|
|
|
398
397
|
### Custom Theme
|
|
@@ -465,17 +464,18 @@ Creates a new editor instance.
|
|
|
465
464
|
|
|
466
465
|
**Options:**
|
|
467
466
|
|
|
468
|
-
| Option | Type | Default | Description |
|
|
469
|
-
|
|
470
|
-
| `dark` | `boolean` | `false` | Dark mode |
|
|
471
|
-
| `
|
|
472
|
-
| `
|
|
473
|
-
| `
|
|
474
|
-
| `
|
|
475
|
-
| `
|
|
476
|
-
| `
|
|
477
|
-
| `
|
|
478
|
-
| `
|
|
467
|
+
| Option | Type | Default | Required | Description |
|
|
468
|
+
|--------|------|---------|----------|-------------|
|
|
469
|
+
| `dark` | `boolean` | `false` | ✕ | Dark mode |
|
|
470
|
+
| `id` | `string` | - | ✓ | required to distinguish between multiple instances |
|
|
471
|
+
| `shadow` | `boolean` | `true` | ✕ | Box shadow |
|
|
472
|
+
| `focusColor` | `string` | `#7c3aed` | ✕ | Border color on focus |
|
|
473
|
+
| `shadowColor` | `string` | `#000` | ✕ | Shadow color |
|
|
474
|
+
| `lock` | `boolean` | `false` | ✕ | Read-only mode |
|
|
475
|
+
| `language` | `string` | `plaintext` | ✓ | Highlight.js language |
|
|
476
|
+
| `hlTheme` | `string` | `hybrid` | ✕ | Highlight.js theme |
|
|
477
|
+
| `font` | `object` | — | ✕ | Custom font `{ url, name }` |
|
|
478
|
+
| `theme` | `object` | — | ✕ | Custom colors ([see more.](#default-colors)) |
|
|
479
479
|
|
|
480
480
|
**Returns:** `Promise<EditorInstance>`
|
|
481
481
|
|
|
@@ -520,6 +520,23 @@ await editor.setLanguage('python');
|
|
|
520
520
|
editor.delete();
|
|
521
521
|
```
|
|
522
522
|
|
|
523
|
+
### Default Colors
|
|
524
|
+
from `option`:
|
|
525
|
+
| Property | Default |
|
|
526
|
+
|----------|---------|
|
|
527
|
+
| focusColor| #7c3aed |
|
|
528
|
+
| shadowColor | #000000 |
|
|
529
|
+
---
|
|
530
|
+
|
|
531
|
+
from `theme`:
|
|
532
|
+
| Property | Dark | Light |
|
|
533
|
+
|----------|------|-------|
|
|
534
|
+
| `background.editor` | #101010 | #d4d4d4 |
|
|
535
|
+
| `editor.caret` | #ffffff | #111111 |
|
|
536
|
+
| `color.editor` | #ffffff | #000000 |
|
|
537
|
+
| `color.lineCounter` | #ffffff | #111111 |
|
|
538
|
+
| `background.lineCounter` | #1e1e1e | #ffffff |
|
|
539
|
+
|
|
523
540
|
### Keyboard Shortcuts
|
|
524
541
|
|
|
525
542
|
| Shortcut | Action |
|
package/components/textEditor.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* createTextEditor {
|
|
3
3
|
* @param {HTMLElement} parent - The element to append the editor to
|
|
4
4
|
* @param {string} content - Initial content of the editor, default is ""
|
|
5
|
-
* @param {string|number} id - Unique ID for this instance, used for undo/redo stack namespacing
|
|
6
5
|
* @param {Object} options - Optional configuration {
|
|
6
|
+
* @param {string|number} id - Unique ID for this instance, used for undo/redo stack namespacing
|
|
7
7
|
* @param {boolean} dark - dark theme enabled, false by default
|
|
8
8
|
* @param {boolean} shadow - box shadow enabled, true by default
|
|
9
9
|
* @param {string} focusColor - border color on focus, default #7c3aed
|
|
@@ -49,9 +49,9 @@
|
|
|
49
49
|
*
|
|
50
50
|
* @notes
|
|
51
51
|
* - Requires Chrome/Chromium — uses EditContext API (not supported in Firefox/Safari yet)
|
|
52
|
-
* - Undo/redo stacks accessible globally via window.caret[
|
|
52
|
+
* - Undo/redo stacks accessible globally via window.caret.undoStack[id]
|
|
53
53
|
* - \u200B (zero-width space) used internally for newline rendering, stripped from getValue()
|
|
54
|
-
* - Keyboard shortcuts: Ctrl+Z undo, Ctrl+Y / Ctrl+Shift+Z redo, Tab indent, Shift+Tab unindent
|
|
54
|
+
* - Keyboard shortcuts: Ctrl+Z: undo, Ctrl+Y / Ctrl+Shift+Z: redo, Tab: indent, Shift+Tab: unindent, Arrow keys(←→↓↑): navigate around
|
|
55
55
|
* }
|
|
56
56
|
*/
|
|
57
57
|
|
|
@@ -64,7 +64,8 @@ import languages from "./languages.js";
|
|
|
64
64
|
|
|
65
65
|
languages.init();
|
|
66
66
|
|
|
67
|
-
async function createTextEditor(parent, content = "",
|
|
67
|
+
async function createTextEditor(parent, content = "", options = {}) {
|
|
68
|
+
const id = options.id;
|
|
68
69
|
let onCursorMoveFn = null;
|
|
69
70
|
async function isChromiumEngine() {
|
|
70
71
|
if (navigator.userAgentData) {
|
|
@@ -103,21 +104,36 @@ async function createTextEditor(parent, content = "", id, options = {}) {
|
|
|
103
104
|
}
|
|
104
105
|
});
|
|
105
106
|
if (id === undefined || id === null || (typeof id !== "string" && typeof id !== "number")) {
|
|
106
|
-
console.error(`parameter 'id'
|
|
107
|
+
console.error(`parameter 'id' from options must not be a '${typeof id}', it must be a number or string, current value of 'id': ${options.id}`);
|
|
107
108
|
return;
|
|
108
109
|
}
|
|
109
110
|
if (!parent || !(parent instanceof HTMLElement)) {
|
|
110
|
-
console.error(`'parent' parameter of function 'createTextEditor' must be an HTMLElement`);
|
|
111
|
+
console.error(`'parent' parameter of function 'createTextEditor' must be an HTMLElement, must not be a ${typeof parent}, current value of 'parent': ${options.parent}`);
|
|
111
112
|
return;
|
|
112
113
|
}
|
|
113
114
|
if (!("EditContext" in window)) {
|
|
114
115
|
console.error("EditContext API is not supported in ", await getBrowserName());
|
|
115
116
|
return;
|
|
116
117
|
}
|
|
118
|
+
if (options.language === undefined || options.language === null || typeof options.language !== "string") {
|
|
119
|
+
console.error(`parameter 'language' from options must be a string, not be a '${typeof options.language}', current value of 'langauge': ${options.language}`);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (!window.caret) window.caret = {
|
|
124
|
+
instances: {},
|
|
125
|
+
undoStack: {},
|
|
126
|
+
redoStack: {}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
if (window.caret.instances[id]) {
|
|
130
|
+
console.error(`Caret: instance with id "${id}" already exists`);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
117
133
|
|
|
118
|
-
|
|
119
|
-
window.caret[
|
|
120
|
-
window.caret[
|
|
134
|
+
window.caret.instances[id] = true;
|
|
135
|
+
window.caret.undoStack[id] = [{ content, cursor: 0 }];
|
|
136
|
+
window.caret.redoStack[id] = [];
|
|
121
137
|
|
|
122
138
|
const lock = options.lock || false;
|
|
123
139
|
const focusColor = options.focusColor || '#7c3aed';
|
|
@@ -127,15 +143,44 @@ async function createTextEditor(parent, content = "", id, options = {}) {
|
|
|
127
143
|
const theme = options.theme;
|
|
128
144
|
const font = options.font || {};
|
|
129
145
|
let language = options.language || "plaintext";
|
|
146
|
+
let cachedThemes = null;
|
|
147
|
+
let cachedBase16 = null;
|
|
148
|
+
let onThemeChangeFn = null;
|
|
149
|
+
|
|
150
|
+
const loadThemes = async () => {
|
|
151
|
+
if (!cachedThemes || !cachedBase16) {
|
|
152
|
+
const [themes, base16] = await Promise.all([
|
|
153
|
+
fetch('https://raw.githubusercontent.com/PFMCODES/Website./main/apps/caret/themes.json').then(res => res.json()),
|
|
154
|
+
fetch('https://raw.githubusercontent.com/PFMCODES/Website./main/apps/caret/themes-base16.json').then(res => res.json())
|
|
155
|
+
]);
|
|
156
|
+
|
|
157
|
+
cachedThemes = themes;
|
|
158
|
+
cachedBase16 = base16;
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const isValidTheme = async (theme) => {
|
|
163
|
+
if (!theme) return false;
|
|
164
|
+
|
|
165
|
+
await loadThemes();
|
|
166
|
+
|
|
167
|
+
const valid = cachedThemes.includes(theme) || cachedBase16.includes(theme);
|
|
168
|
+
|
|
169
|
+
if (!valid) {
|
|
170
|
+
console.warn(`${theme} is an invalid Highlight.js theme, defaulting to hybrid`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return valid;
|
|
174
|
+
};
|
|
130
175
|
|
|
131
176
|
const themeLink = document.createElement("link");
|
|
132
177
|
themeLink.rel = "stylesheet";
|
|
133
178
|
themeLink.id = `caret-theme-${id}`;
|
|
134
|
-
|
|
179
|
+
const validTheme = await isValidTheme(options.hlTheme);
|
|
180
|
+
themeLink.href = validTheme
|
|
135
181
|
? `https://esm.sh/@pfmcodes/highlight.js@1.0.0/styles/${options.hlTheme}.css`
|
|
136
182
|
: `https://esm.sh/@pfmcodes/highlight.js@1.0.0/styles/hybrid.css`;
|
|
137
183
|
document.head.appendChild(themeLink);
|
|
138
|
-
|
|
139
184
|
if (!languages.registeredLanguages.includes(language)) {
|
|
140
185
|
const mod = await import(`https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/languages/${language}.js`);
|
|
141
186
|
languages.registerLanguage(language, mod.default);
|
|
@@ -202,7 +247,7 @@ async function createTextEditor(parent, content = "", id, options = {}) {
|
|
|
202
247
|
if (options.theme) {
|
|
203
248
|
caretColor = dark ? options.theme.dark["editor.caret"] : options.theme.light["editor.caret"];
|
|
204
249
|
} else {
|
|
205
|
-
caretColor = "#fff";
|
|
250
|
+
caretColor = dark ? "#fff" : "#111";
|
|
206
251
|
}
|
|
207
252
|
|
|
208
253
|
parent.style.display = "flex";
|
|
@@ -234,16 +279,16 @@ async function createTextEditor(parent, content = "", id, options = {}) {
|
|
|
234
279
|
}
|
|
235
280
|
|
|
236
281
|
function saveState() {
|
|
237
|
-
const stack = window.caret[
|
|
282
|
+
const stack = window.caret.undoStack[id];
|
|
238
283
|
if (text !== stack[stack.length - 1]?.content) {
|
|
239
284
|
stack.push({ content: text, cursor: selStart });
|
|
240
|
-
window.caret[
|
|
285
|
+
window.caret.redoStack[id] = [];
|
|
241
286
|
}
|
|
242
287
|
}
|
|
243
288
|
|
|
244
289
|
function undo() {
|
|
245
|
-
const stack = window.caret[
|
|
246
|
-
const redoStack = window.caret[
|
|
290
|
+
const stack = window.caret.undoStack[id];
|
|
291
|
+
const redoStack = window.caret.redoStack[id];
|
|
247
292
|
if (stack.length <= 1) return;
|
|
248
293
|
const current = stack.pop();
|
|
249
294
|
redoStack.push(current);
|
|
@@ -257,8 +302,8 @@ async function createTextEditor(parent, content = "", id, options = {}) {
|
|
|
257
302
|
}
|
|
258
303
|
|
|
259
304
|
function redo() {
|
|
260
|
-
const stack = window.caret[
|
|
261
|
-
const redoStack = window.caret[
|
|
305
|
+
const stack = window.caret.undoStack[id];
|
|
306
|
+
const redoStack = window.caret.redoStack[id];
|
|
262
307
|
if (redoStack.length === 0) return;
|
|
263
308
|
const next = redoStack.pop();
|
|
264
309
|
stack.push(next);
|
|
@@ -271,6 +316,7 @@ async function createTextEditor(parent, content = "", id, options = {}) {
|
|
|
271
316
|
|
|
272
317
|
editContext.addEventListener("textupdate", (e) => {
|
|
273
318
|
if (lock) return;
|
|
319
|
+
console.log("updateRange:", e.updateRangeStart, e.updateRangeEnd, "text:", e.text);
|
|
274
320
|
text = text.slice(0, e.updateRangeStart) + e.text + text.slice(e.updateRangeEnd);
|
|
275
321
|
text = text.replaceAll("\u200B", "");
|
|
276
322
|
selStart = selEnd = e.selectionStart;
|
|
@@ -363,7 +409,22 @@ async function createTextEditor(parent, content = "", id, options = {}) {
|
|
|
363
409
|
render();
|
|
364
410
|
return;
|
|
365
411
|
}
|
|
366
|
-
if (e.key === "ArrowLeft") {
|
|
412
|
+
if (e.key === "ArrowLeft" && e.shiftKey) {
|
|
413
|
+
e.preventDefault();
|
|
414
|
+
selStart = Math.max(0, selStart - 1);
|
|
415
|
+
editContext.updateSelection(selStart, selEnd);
|
|
416
|
+
caret.update(selStart);
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (e.key === "ArrowRight" && e.shiftKey) {
|
|
421
|
+
e.preventDefault();
|
|
422
|
+
selEnd = Math.min(text.length, selEnd + 1);
|
|
423
|
+
editContext.updateSelection(selStart, selEnd);
|
|
424
|
+
caret.update(selEnd);
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
if (e.key === "ArrowLeft" && !e.shiftKey) {
|
|
367
428
|
e.preventDefault();
|
|
368
429
|
selStart = selEnd = Math.max(0, selStart - 1);
|
|
369
430
|
editContext.updateSelection(selStart, selEnd);
|
|
@@ -372,7 +433,7 @@ async function createTextEditor(parent, content = "", id, options = {}) {
|
|
|
372
433
|
return;
|
|
373
434
|
}
|
|
374
435
|
|
|
375
|
-
if (e.key === "ArrowRight") {
|
|
436
|
+
if (e.key === "ArrowRight" && !e.shiftKey) {
|
|
376
437
|
e.preventDefault();
|
|
377
438
|
selStart = selEnd = Math.min(text.length, selStart + 1);
|
|
378
439
|
editContext.updateSelection(selStart, selEnd);
|
|
@@ -381,7 +442,38 @@ async function createTextEditor(parent, content = "", id, options = {}) {
|
|
|
381
442
|
return;
|
|
382
443
|
}
|
|
383
444
|
|
|
384
|
-
if (e.key === "ArrowUp") {
|
|
445
|
+
if (e.key === "ArrowUp" && e.shiftKey) {
|
|
446
|
+
e.preventDefault();
|
|
447
|
+
const lineStart = text.lastIndexOf("\n", selStart - 1) + 1;
|
|
448
|
+
const prevLineEnd = lineStart - 1;
|
|
449
|
+
const prevLineStart = text.lastIndexOf("\n", prevLineEnd - 1) + 1;
|
|
450
|
+
const col = selStart - lineStart;
|
|
451
|
+
const prevLineLength = prevLineEnd - prevLineStart;
|
|
452
|
+
selStart = prevLineStart + Math.min(col, prevLineLength);
|
|
453
|
+
// selEnd stays — extends selection upward
|
|
454
|
+
editContext.updateSelection(selStart, selEnd);
|
|
455
|
+
caret.update(selStart);
|
|
456
|
+
if (onCursorMoveFn) onCursorMoveFn(selStart);
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
if (e.key === "ArrowDown" && e.shiftKey) {
|
|
461
|
+
e.preventDefault();
|
|
462
|
+
const lineStart = text.lastIndexOf("\n", selEnd - 1) + 1;
|
|
463
|
+
const nextLineStart = text.indexOf("\n", selEnd) + 1;
|
|
464
|
+
const nextLineEnd = text.indexOf("\n", nextLineStart);
|
|
465
|
+
const finalNextLineEnd = nextLineEnd === -1 ? text.length : nextLineEnd;
|
|
466
|
+
const col = selEnd - lineStart;
|
|
467
|
+
const nextLineLength = finalNextLineEnd - nextLineStart;
|
|
468
|
+
selEnd = nextLineStart + Math.min(col, nextLineLength);
|
|
469
|
+
// selStart stays — extends selection downward
|
|
470
|
+
editContext.updateSelection(selStart, selEnd);
|
|
471
|
+
caret.update(selEnd);
|
|
472
|
+
if (onCursorMoveFn) onCursorMoveFn(selEnd);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (e.key === "ArrowUp" && !e.shiftKey) {
|
|
385
477
|
e.preventDefault();
|
|
386
478
|
const lineStart = text.lastIndexOf("\n", selStart - 1) + 1;
|
|
387
479
|
const prevLineEnd = lineStart - 1;
|
|
@@ -395,7 +487,7 @@ async function createTextEditor(parent, content = "", id, options = {}) {
|
|
|
395
487
|
return;
|
|
396
488
|
}
|
|
397
489
|
|
|
398
|
-
if (e.key === "ArrowDown") {
|
|
490
|
+
if (e.key === "ArrowDown" && !e.shiftKey) {
|
|
399
491
|
e.preventDefault();
|
|
400
492
|
const lineStart = text.lastIndexOf("\n", selStart - 1) + 1;
|
|
401
493
|
const nextLineStart = text.indexOf("\n", selStart) + 1;
|
|
@@ -438,6 +530,60 @@ async function createTextEditor(parent, content = "", id, options = {}) {
|
|
|
438
530
|
caret.hide();
|
|
439
531
|
});
|
|
440
532
|
|
|
533
|
+
let isMouseDown = false;
|
|
534
|
+
let dragStart = 0;
|
|
535
|
+
|
|
536
|
+
main.addEventListener("mousedown", (e) => {
|
|
537
|
+
isMouseDown = true;
|
|
538
|
+
main.focus();
|
|
539
|
+
const range = document.caretRangeFromPoint(e.clientX, e.clientY);
|
|
540
|
+
if (!range) return;
|
|
541
|
+
|
|
542
|
+
let offset = 0;
|
|
543
|
+
let remaining = 0;
|
|
544
|
+
const walker = document.createTreeWalker(main, NodeFilter.SHOW_TEXT);
|
|
545
|
+
let node;
|
|
546
|
+
while ((node = walker.nextNode())) {
|
|
547
|
+
if (node === range.startContainer) {
|
|
548
|
+
offset = remaining + range.startOffset;
|
|
549
|
+
break;
|
|
550
|
+
}
|
|
551
|
+
remaining += node.textContent.length;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
dragStart = offset;
|
|
555
|
+
selStart = selEnd = offset;
|
|
556
|
+
editContext.updateSelection(selStart, selEnd);
|
|
557
|
+
caret.update(selStart);
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
main.addEventListener("mousemove", (e) => {
|
|
561
|
+
if (!isMouseDown) return;
|
|
562
|
+
const range = document.caretRangeFromPoint(e.clientX, e.clientY);
|
|
563
|
+
if (!range) return;
|
|
564
|
+
|
|
565
|
+
let offset = 0;
|
|
566
|
+
let remaining = 0;
|
|
567
|
+
const walker = document.createTreeWalker(main, NodeFilter.SHOW_TEXT);
|
|
568
|
+
let node;
|
|
569
|
+
while ((node = walker.nextNode())) {
|
|
570
|
+
if (node === range.startContainer) {
|
|
571
|
+
offset = remaining + range.startOffset;
|
|
572
|
+
break;
|
|
573
|
+
}
|
|
574
|
+
remaining += node.textContent.length;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
selStart = Math.min(dragStart, offset);
|
|
578
|
+
selEnd = Math.max(dragStart, offset);
|
|
579
|
+
editContext.updateSelection(selStart, selEnd);
|
|
580
|
+
caret.update(selStart);
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
document.addEventListener("mouseup", () => {
|
|
584
|
+
isMouseDown = false;
|
|
585
|
+
});
|
|
586
|
+
|
|
441
587
|
main.addEventListener("click", (e) => {
|
|
442
588
|
main.focus();
|
|
443
589
|
const range = document.caretRangeFromPoint(e.clientX, e.clientY);
|
|
@@ -482,6 +628,14 @@ async function createTextEditor(parent, content = "", id, options = {}) {
|
|
|
482
628
|
id: options.id,
|
|
483
629
|
onChange: (fn) => { onChangeFn = fn; },
|
|
484
630
|
isFocused: () => isFocused,
|
|
631
|
+
setTheme: async (name) => {
|
|
632
|
+
const validTheme = await isValidTheme(name);
|
|
633
|
+
themeLink.href = validTheme
|
|
634
|
+
? `https://esm.sh/@pfmcodes/highlight.js@1.0.0/styles/${name}.css`
|
|
635
|
+
: `https://esm.sh/@pfmcodes/highlight.js@1.0.0/styles/hybrid.css`;
|
|
636
|
+
if (onThemeChangeFn) onThemeChangeFn(validTheme ? name : "hybrid");
|
|
637
|
+
},
|
|
638
|
+
onThemeChange: (cb) => { onThemeChangeFn = cb; },
|
|
485
639
|
setLanguage: async (lang) => {
|
|
486
640
|
if (!languages.registeredLanguages.includes(lang)) {
|
|
487
641
|
const mod = await import(`https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/languages/${lang}.js`);
|
|
@@ -490,12 +644,16 @@ async function createTextEditor(parent, content = "", id, options = {}) {
|
|
|
490
644
|
language = lang;
|
|
491
645
|
render();
|
|
492
646
|
},
|
|
647
|
+
onCursorMove: (cb) => { onCursorMoveFn = cb },
|
|
493
648
|
delete: () => {
|
|
494
649
|
parent.removeChild(main);
|
|
495
650
|
parent.removeChild(lineCounter);
|
|
496
651
|
caret.destroy();
|
|
497
652
|
document.head.removeChild(themeLink);
|
|
498
653
|
parent.style = "";
|
|
654
|
+
delete window.caret.instances[id];
|
|
655
|
+
delete window.caret.undoStack[id];
|
|
656
|
+
delete window.caret.redoStack[id];
|
|
499
657
|
}
|
|
500
658
|
};
|
|
501
659
|
}
|