@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.
@@ -0,0 +1,95 @@
1
+ /**
2
+ * caret.js - Custom caret for EditContext based editors
3
+ */
4
+
5
+ /**
6
+ * createCaret {
7
+ * @param {HTMLElement} parent - the editor container
8
+ * @param {HTMLElement} main - the editor div
9
+ * @param {Object} options {
10
+ * @param {string} caretColor - caret color
11
+ * @param {number} caretWidth - caret width in px, default 2
12
+ * }
13
+ * @returns {{ update: (selStart, main) => void, show: () => void, hide: () => void, destroy: () => void }}
14
+ * }
15
+ */
16
+ function createCaret(parent, main, options = {}) {
17
+ const color = options.caretColor || options.focusColor || "#fff";
18
+ const width = options.caretWidth || 2;
19
+
20
+ const caretEl = document.createElement("div");
21
+ caretEl.className = "Caret-caret-" + options.id;
22
+ caretEl.style.position = "absolute";
23
+ caretEl.style.width = `${width}px`;
24
+ caretEl.style.background = color;
25
+ caretEl.style.pointerEvents = "none";
26
+ caretEl.style.zIndex = "10";
27
+ caretEl.style.opacity = "0";
28
+ caretEl.style.borderRadius = "1px";
29
+ caretEl.style.animation = "caret-blink 1s step-end infinite";
30
+
31
+ if (!document.getElementById("caret-blink-style")) {
32
+ const style = document.createElement("style");
33
+ style.id = "caret-blink-style";
34
+ style.textContent = `@keyframes caret-blink { 0%,100%{opacity:1} 50%{opacity:0} }`;
35
+ document.head.appendChild(style);
36
+ }
37
+
38
+ parent.appendChild(caretEl);
39
+
40
+ function update(selStart) {
41
+ // walk text nodes to find exact position
42
+ let remaining = selStart;
43
+ let targetNode = null;
44
+ let targetOffset = 0;
45
+
46
+ const walker = document.createTreeWalker(main, NodeFilter.SHOW_TEXT);
47
+ let node;
48
+ while ((node = walker.nextNode())) {
49
+ if (remaining <= node.textContent.length) {
50
+ targetNode = node;
51
+ targetOffset = remaining;
52
+ break;
53
+ }
54
+ remaining -= node.textContent.length;
55
+ }
56
+
57
+ if (!targetNode) {
58
+ caretEl.style.left = "5px";
59
+ caretEl.style.top = "5px";
60
+ caretEl.style.height = "21px";
61
+ return;
62
+ }
63
+
64
+ const range = document.createRange();
65
+ range.setStart(targetNode, targetOffset);
66
+ range.collapse(true);
67
+
68
+ const rect = range.getBoundingClientRect();
69
+ const parentRect = parent.getBoundingClientRect();
70
+
71
+ if (rect.width === 0 && rect.height === 0) return;
72
+
73
+ caretEl.style.left = rect.left - parentRect.left + parent.scrollLeft + "px";
74
+ caretEl.style.top = rect.top - parentRect.top + parent.scrollTop + "px";
75
+ caretEl.style.height = (rect.height || 21) + "px";
76
+ }
77
+
78
+ function show() {
79
+ caretEl.style.opacity = "1";
80
+ caretEl.style.animation = "caret-blink 1s step-end infinite";
81
+ }
82
+
83
+ function hide() {
84
+ caretEl.style.opacity = "0";
85
+ caretEl.style.animation = "none";
86
+ }
87
+
88
+ function destroy() {
89
+ caretEl.remove();
90
+ }
91
+
92
+ return { update, show, hide, destroy };
93
+ }
94
+
95
+ export { createCaret };
@@ -0,0 +1,16 @@
1
+ /**
2
+ * loadFont {
3
+ * @param {string} name - name of the font
4
+ * @param {string/url} url - url to external font
5
+ * @returns {void}
6
+ * }
7
+ */
8
+
9
+ // load the font dynamically
10
+ async function loadFont(name, url) {
11
+ const font = new FontFace(name, `url("${url}")`);
12
+ await font.load();
13
+ document.fonts.add(font);
14
+ }
15
+
16
+ export { loadFont }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * createLineCounter {
3
+ * @param {HTMLElement} parent - the parent element the line counter goes inside of
4
+ * @param {number} lines - number of lines in the code
5
+ * @param {string/number} id - id for sepration and distinguishtion of multiple instances
6
+ * @param {object} options {
7
+ * @param {string/color} focusColor - border color of the editor when its in use
8
+ * @param {string/color} background - background color of the line counter
9
+ * @param {boolean} dark - if the user has dark theme enabled or not
10
+ * @param {object} theme {
11
+ * @param {object} dark {
12
+ * @param {string/color} background - background color for the dark theme
13
+ * }
14
+ * @param {object} light {
15
+ * @param {string/color} background - background color for the light theme
16
+ * }
17
+ * }
18
+ * @param {object} font {
19
+ * @param {string} url - external font url or file path
20
+ * @param {string} name - font name, required for both internal and external custom font application
21
+ * }
22
+ * }
23
+ * }
24
+ */
25
+
26
+ import { loadFont } from "./font.js";
27
+
28
+ async function createLineCounter(parent, lines, id, options) {
29
+ let fontUrl;
30
+ let fontName;
31
+ const font = options.font || {};
32
+ if (!font.url && !font.name) {
33
+ fontName = "monospace";
34
+ parent.style.fontFamily = fontName;
35
+ }
36
+ else if (!font.url && font.name) {
37
+ parent.style.fontFamily = font.name;
38
+ }
39
+ else {
40
+ fontUrl = font.url;
41
+ fontName = font.name;
42
+ loadFont(fontName, fontUrl);
43
+ parent.style.fontFamily = fontName;
44
+ }
45
+ const lineCounter = document.createElement("div");
46
+ lineCounter.className = "lineCounter";
47
+ lineCounter.id = `lineCounter-${id}`;
48
+ const dark = options.dark || false;
49
+ const theme = options.theme || {};
50
+ lineCounter.style.height = "auto";
51
+ lineCounter.style.fontSize = "14px";
52
+ lineCounter.style.lineHeight = "1.5";
53
+ lineCounter.style.minWidth = "25px";
54
+ lineCounter.style.top = "0px";
55
+ lineCounter.style.borderRight = options.focusColor || "#fff";
56
+ if (Object.keys(theme).length === 0) {
57
+ lineCounter.style.color = dark ? "#fff" : "#111";
58
+ lineCounter.style.background = dark ? "#1e1e1e" : "#fff";
59
+ parent.style.background = dark ? "#1e1e1e" : "#fff";
60
+ } else {
61
+ lineCounter.style.color = dark ? theme.dark["color.lineCounter"] : theme.light["color.lineCounter"];
62
+ lineCounter.style.background = dark ? theme.dark["background.lineCounter"] : theme.light["background.lineCounter"];
63
+ parent.style.background = dark ? theme.dark["background.lineCounter"] : theme.light["background.lineCounter"];
64
+ }
65
+ for (let i = 0; i < lines; i++) {
66
+ const lineNumber = document.createElement("div");
67
+ lineNumber.className = "line-number";
68
+ lineNumber.id = `line-number-${i}-${id}`;
69
+ lineNumber.innerText = i + 1;
70
+ lineCounter.appendChild(lineNumber);
71
+ }
72
+
73
+ parent.appendChild(lineCounter);
74
+ return lineCounter; // 👈 return so you can update it later
75
+ }
76
+
77
+ function updateLineCounter(lineCounter, lines) {
78
+ lineCounter.innerHTML = "";
79
+ for (let i = 0; i < lines; i++) {
80
+ const lineNumber = document.createElement("div");
81
+ lineNumber.className = "line-number";
82
+ lineNumber.innerText = i + 1;
83
+ lineCounter.appendChild(lineNumber);
84
+ lineNumber.style.height = "calc(14px * 1.5)"; // font-size * line-height
85
+ lineNumber.style.display = "flex";
86
+ lineNumber.style.alignItems = "center";
87
+ lineNumber.style.margin = "auto";
88
+ }
89
+ }
90
+
91
+ export { createLineCounter, updateLineCounter };
@@ -0,0 +1,411 @@
1
+ /**
2
+ * createTextEditor {
3
+ * @param {string} content - Initial content of the editor
4
+ * @param {HTMLElement} parent - The element to append the editor to
5
+ * @param {Object} options - Optional configuration {
6
+ * @param {boolean} dark - dark theme is enabled or not, false by default
7
+ * @param {boolean} shadow - should boxShadow be enabled, true by default
8
+ * @param {string} focusColor - color of focus border, by default it is purple(#7c3aed)
9
+ * @param {string} shadowColor - only needed if the box shadow is enabled, default is black
10
+ * @param {boolean} lock - if enabled, makes the editor non editable, false by default
11
+ * @param {string} language - language for syntax highlighting, default is plaintext
12
+ * @param {string} hlTheme - highlight.js theme, default is hybrid
13
+ * @param {JSON} font {
14
+ * @param {string} url - optional, only needed if applying custom font
15
+ * @param {string} name - required if you want custom font with or without external font
16
+ * }
17
+ * @param {string/number} id - unique id for the editor for multiple instances
18
+ * @param {JSON} theme {
19
+ * @param {object} dark {
20
+ * @param {string/color} background - background color for the dark theme
21
+ * @param {string/color} color.editor - text color for dark theme
22
+ * @param {string/color} editor.caret - caret color for dark theme
23
+ * }
24
+ * @param {object} light {
25
+ * @param {string/color} background - background color for the light theme
26
+ * @param {string/color} color.editor - text color for light theme
27
+ * @param {string/color} editor.caret - caret color for light theme
28
+ * }
29
+ * }
30
+ * }
31
+ * @returns {object} {
32
+ * @return {string} getValue => () - returns the current value of editor
33
+ * @return {void} setValue => (@param {string} val) - sets the value of editor
34
+ * @return {void} onChange => (@param {function} fn) - fires when value changes
35
+ * @return {boolean} isFocused - tells if the editor is focused
36
+ * @return {void} setLanguage => (@param {string} lang) - changes highlight language
37
+ * }
38
+ * }
39
+ */
40
+ import { createLineCounter, updateLineCounter } from "./lineCounter.js";
41
+ import { loadFont } from "./font.js";
42
+ import { createCaret } from "./caret.js";
43
+ // @ts-ignore
44
+ import hljs from "https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/core.js";
45
+ import languages from "./languages.js";
46
+
47
+ languages.init();
48
+
49
+ async function createTextEditor(parent, content = "", id, options = {}) {
50
+ if (id === undefined || id === null || (typeof id !== "string" && typeof id !== "number")) {
51
+ console.error(`parameter 'id' of function createTextEditor must not be '${typeof id}', it must be a number or string`);
52
+ return;
53
+ }
54
+ if (!parent || !(parent instanceof HTMLElement)) {
55
+ console.error(`'parent' parameter of function 'createTextEditor' must be an HTMLElement`);
56
+ return;
57
+ }
58
+ if (!("EditContext" in window)) {
59
+ console.error("EditContext API is not supported in this browser");
60
+ return;
61
+ }
62
+
63
+ // undo/redo stack
64
+ if (!window.caret) window.caret = {};
65
+ window.caret[`undoStack.${id}`] = [{ content, cursor: 0 }];
66
+ window.caret[`redoStack.${id}`] = [];
67
+
68
+ const lock = options.lock || false;
69
+ const focusColor = options.focusColor || '#7c3aed';
70
+ const dark = options.dark || false;
71
+ const boxShadow = options.shadow ?? true;
72
+ const shadowColor = options.shadowColor || "#000";
73
+ const theme = options.theme;
74
+ const font = options.font || {};
75
+ let language = options.language || "plaintext";
76
+
77
+ // load hljs theme
78
+ const themeLink = document.createElement("link");
79
+ themeLink.rel = "stylesheet";
80
+ themeLink.id = `caret-theme-${id}`;
81
+ themeLink.href = options.hlTheme
82
+ ? `https://esm.sh/@pfmcodes/highlight.js@1.0.0/styles/${options.hlTheme}.css`
83
+ : `https://esm.sh/@pfmcodes/highlight.js@1.0.0/styles/hybrid.css`;
84
+ document.head.appendChild(themeLink);
85
+
86
+ // load language if not registered
87
+ if (!languages.registeredLanguages.includes(language)) {
88
+ const mod = await import(`https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/languages/${language}.js`);
89
+ languages.registerLanguage(language, mod.default);
90
+ }
91
+
92
+ let fontName;
93
+ if (!font.url && !font.name) {
94
+ fontName = "monospace";
95
+ parent.style.fontFamily = fontName;
96
+ } else if (!font.url && font.name) {
97
+ parent.style.fontFamily = font.name;
98
+ } else {
99
+ fontName = font.name;
100
+ loadFont(fontName, font.url);
101
+ parent.style.fontFamily = fontName;
102
+ }
103
+
104
+ // text model
105
+ let text = content;
106
+ let selStart = content.length;
107
+ let selEnd = content.length;
108
+ let isFocused = false;
109
+ let onChangeFn = null;
110
+
111
+ // EditContext
112
+ const editContext = new EditContext({
113
+ text,
114
+ selectionStart: selStart,
115
+ selectionEnd: selEnd
116
+ });
117
+
118
+ // main div
119
+ const main = document.createElement("div");
120
+ main.editContext = editContext;
121
+ main.tabIndex = 0;
122
+ main.style.whiteSpace = "pre";
123
+ main.style.height = "100%";
124
+ main.style.width = "100%";
125
+ main.style.minWidth = "50px";
126
+ main.style.minHeight = "25px";
127
+ main.style.fontSize = "14px";
128
+ main.style.lineHeight = "1.5";
129
+ main.style.outline = "none";
130
+ main.style.boxSizing = "border-box";
131
+ main.style.borderTopRightRadius = "5px";
132
+ main.style.borderBottomRightRadius = "5px";
133
+ main.style.transition = "all 0.2s ease-in-out";
134
+ main.style.display = "block";
135
+ main.style.paddingTop = "5px";
136
+ main.style.overflowX = "auto";
137
+ main.style.scrollbarWidth = "none";
138
+ main.style.cursor = "text";
139
+ main.style.userSelect = "none";
140
+ main.style.caretColor = "transparent";
141
+
142
+ if (boxShadow) main.style.boxShadow = `1px 1px 1px 1px ${shadowColor}`;
143
+
144
+ if (!theme) {
145
+ main.style.background = dark ? "#101010" : "#d4d4d4";
146
+ main.style.color = dark ? "#fff" : "#000";
147
+ } else {
148
+ main.style.background = dark ? theme.dark["background.editor"] : theme.light["background.editor"];
149
+ main.style.color = dark ? theme.dark["color.editor"] : theme.light["color.editor"];
150
+ }
151
+
152
+ // caret color
153
+ let caretColor;
154
+ if (options.theme) {
155
+ caretColor = dark ? options.theme.dark["editor.caret"] : options.theme.light["editor.caret"];
156
+ } else {
157
+ caretColor = "#fff";
158
+ }
159
+
160
+ // parent styles
161
+ parent.style.display = "flex";
162
+ parent.style.alignItems = "flex-start";
163
+ parent.style.border = "2px solid #0000";
164
+ parent.style.padding = "5px";
165
+ parent.style.position = "relative";
166
+
167
+ // line counter
168
+ let lineCounter;
169
+ lineCounter = await createLineCounter(parent, content.split("\n").length, id, options);
170
+
171
+ parent.appendChild(main);
172
+
173
+ // caret
174
+ const caret = createCaret(parent, main, { ...options, caretColor });
175
+
176
+ // --- render ---
177
+ function render() {
178
+ const highlighted = unescapeHTML(hljs.highlight(unescapeHTML(text), { language }).value);
179
+ main.innerHTML = highlighted;
180
+ updateLineCounter(lineCounter, text.trimEnd().split("\n").length);
181
+ caret.update(selStart);
182
+ if (onChangeFn) onChangeFn(text);
183
+ }
184
+
185
+ // --- undo/redo ---
186
+ function saveState() {
187
+ const stack = window.caret[`undoStack.${id}`];
188
+ if (text !== stack[stack.length - 1]?.content) {
189
+ stack.push({ content: text, cursor: selStart });
190
+ window.caret[`redoStack.${id}`] = [];
191
+ }
192
+ }
193
+
194
+ function undo() {
195
+ const stack = window.caret[`undoStack.${id}`];
196
+ const redoStack = window.caret[`redoStack.${id}`];
197
+ if (stack.length <= 1) return;
198
+ const current = stack.pop();
199
+ redoStack.push(current);
200
+ const prev = stack[stack.length - 1];
201
+ text = prev.content;
202
+ const diff = current.content.length - prev.content.length;
203
+ selStart = selEnd = Math.max(0, current.cursor - diff);
204
+ editContext.updateText(0, editContext.text.length, text);
205
+ editContext.updateSelection(selStart, selEnd);
206
+ render();
207
+ }
208
+
209
+ function redo() {
210
+ const stack = window.caret[`undoStack.${id}`];
211
+ const redoStack = window.caret[`redoStack.${id}`];
212
+ if (redoStack.length === 0) return;
213
+ const next = redoStack.pop();
214
+ stack.push(next);
215
+ text = next.content;
216
+ selStart = selEnd = next.cursor;
217
+ editContext.updateText(0, editContext.text.length, text);
218
+ editContext.updateSelection(selStart, selEnd);
219
+ render();
220
+ }
221
+
222
+ // --- EditContext events ---
223
+ editContext.addEventListener("textupdate", (e) => {
224
+ if (lock) return;
225
+ text = text.slice(0, e.updateRangeStart) + e.text + text.slice(e.updateRangeEnd);
226
+ selStart = selEnd = e.selectionStart;
227
+ editContext.updateText(0, editContext.text.length, text);
228
+ saveState();
229
+ render();
230
+ });
231
+
232
+ editContext.addEventListener("selectionchange", (e) => {
233
+ selStart = e.selectionStart;
234
+ selEnd = e.selectionEnd;
235
+ caret.update(selStart);
236
+ });
237
+
238
+ editContext.addEventListener("textformatupdate", () => {
239
+ // IME formatting — ignore for now
240
+ });
241
+
242
+ // --- keyboard events ---
243
+ main.addEventListener("keydown", (e) => {
244
+ if (lock) return;
245
+
246
+ if ((e.ctrlKey || e.metaKey) && !e.shiftKey && e.key === "z") {
247
+ e.preventDefault();
248
+ undo();
249
+ return;
250
+ }
251
+ if ((e.ctrlKey || e.metaKey) && e.key === "y") {
252
+ e.preventDefault();
253
+ redo();
254
+ return;
255
+ }
256
+ if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === "Z") {
257
+ e.preventDefault();
258
+ redo();
259
+ return;
260
+ }
261
+
262
+ // Tab
263
+ if (e.key === "Tab" && !e.shiftKey) {
264
+ e.preventDefault();
265
+ const indent = " ";
266
+ if (selStart !== selEnd) {
267
+ const before = text.slice(0, selStart);
268
+ const selected = text.slice(selStart, selEnd);
269
+ const after = text.slice(selEnd);
270
+ const indented = selected.split("\n").map(l => indent + l).join("\n");
271
+ text = before + indented + after;
272
+ selEnd = selStart + indented.length;
273
+ } else {
274
+ text = text.slice(0, selStart) + indent + text.slice(selStart);
275
+ selStart = selEnd = selStart + indent.length;
276
+ }
277
+ editContext.updateText(0, editContext.text.length, text);
278
+ editContext.updateSelection(selStart, selEnd);
279
+ saveState();
280
+ render();
281
+ return;
282
+ }
283
+
284
+ // Shift+Tab
285
+ if (e.shiftKey && e.key === "Tab") {
286
+ e.preventDefault();
287
+ if (selStart !== selEnd) {
288
+ const before = text.slice(0, selStart);
289
+ const selected = text.slice(selStart, selEnd);
290
+ const after = text.slice(selEnd);
291
+ const unindented = selected.split("\n").map(l =>
292
+ l.startsWith(" ") ? l.slice(4) :
293
+ l.startsWith("\t") ? l.slice(1) : l
294
+ ).join("\n");
295
+ text = before + unindented + after;
296
+ selEnd = selStart + unindented.length;
297
+ } else {
298
+ const lineStart = text.lastIndexOf("\n", selStart - 1) + 1;
299
+ const linePrefix = text.slice(lineStart, lineStart + 4);
300
+ if (linePrefix === " ") {
301
+ text = text.slice(0, lineStart) + text.slice(lineStart + 4);
302
+ selStart = selEnd = Math.max(lineStart, selStart - 4);
303
+ }
304
+ }
305
+ editContext.updateText(0, editContext.text.length, text);
306
+ editContext.updateSelection(selStart, selEnd);
307
+ saveState();
308
+ render();
309
+ return;
310
+ }
311
+ });
312
+
313
+ // paste
314
+ main.addEventListener("paste", (e) => {
315
+ if (lock) return;
316
+ e.preventDefault();
317
+ const pasteText = e.clipboardData.getData("text/plain");
318
+ text = text.slice(0, selStart) + pasteText + text.slice(selEnd);
319
+ selStart = selEnd = selStart + pasteText.length;
320
+ editContext.updateText(0, editContext.text.length, text);
321
+ editContext.updateSelection(selStart, selEnd);
322
+ saveState();
323
+ render();
324
+ });
325
+
326
+ // focus/blur
327
+ main.addEventListener("focus", () => {
328
+ isFocused = true;
329
+ parent.style.border = `2px solid ${focusColor}`;
330
+ parent.style.boxShadow = "none";
331
+ caret.show();
332
+ caret.update(selStart);
333
+ });
334
+
335
+ main.addEventListener("blur", () => {
336
+ isFocused = false;
337
+ parent.style.border = "2px solid #0000";
338
+ if (boxShadow) parent.style.boxShadow = `1px 1px 1px 1px ${shadowColor}`;
339
+ caret.hide();
340
+ });
341
+
342
+ // click to position cursor
343
+ main.addEventListener("click", (e) => {
344
+ main.focus();
345
+ const range = document.caretRangeFromPoint(e.clientX, e.clientY);
346
+ if (!range) return;
347
+
348
+ // walk text nodes to find offset
349
+ let offset = 0;
350
+ let remaining = 0;
351
+ const walker = document.createTreeWalker(main, NodeFilter.SHOW_TEXT);
352
+ let node;
353
+ while ((node = walker.nextNode())) {
354
+ if (node === range.startContainer) {
355
+ offset = remaining + range.startOffset;
356
+ break;
357
+ }
358
+ remaining += node.textContent.length;
359
+ }
360
+
361
+ selStart = selEnd = offset;
362
+ editContext.updateSelection(selStart, selEnd);
363
+ caret.update(selStart);
364
+ });
365
+
366
+ // initial render
367
+ render();
368
+
369
+ return {
370
+ getValue: () => text,
371
+ setValue: (val) => {
372
+ text = val;
373
+ selStart = selEnd = val.length;
374
+ editContext.updateText(0, editContext.text.length, text);
375
+ editContext.updateSelection(selStart, selEnd);
376
+ render();
377
+ },
378
+ id: options.id,
379
+ onChange: (fn) => { onChangeFn = fn; },
380
+ isFocused: () => isFocused,
381
+ setLanguage: async (lang) => {
382
+ if (!languages.registeredLanguages.includes(lang)) {
383
+ const mod = await import(`https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/languages/${lang}.js`);
384
+ languages.registerLanguage(lang, mod.default);
385
+ }
386
+ language = lang;
387
+ render();
388
+ },
389
+ delete: () => {
390
+ parent.removeChild(main);
391
+ parent.removeChild(lineCounter);
392
+ caret.destroy();
393
+ document.head.removeChild(themeLink);
394
+ parent.style = "";
395
+ }
396
+ };
397
+ }
398
+
399
+ function unescapeHTML(str) {
400
+ const entities = {
401
+ '&amp;': '&',
402
+ '&lt;': '<',
403
+ '&gt;': '>',
404
+ '&quot;': '"',
405
+ '&#39;': "'",
406
+ '&#039;': "'"
407
+ };
408
+ return str.replace(/&amp;|&lt;|&gt;|&quot;|&#39;|&#039;/g, tag => entities[tag] || tag);
409
+ }
410
+
411
+ export { createTextEditor }
package/index.js CHANGED
@@ -1,22 +1,7 @@
1
- import editor from "./editor.js";
2
- import theme from "./theme.js";
3
- import language from "./langauges.js";
4
-
1
+ import * as t from "./components/textEditor.js";
5
2
  const Caret = {
6
- editor,
7
- theme,
8
- language
3
+ createEditor: t.createTextEditor,
4
+ createTextEditor: t.createTextEditor
9
5
  }
10
- export default Caret;
11
6
 
12
- /*
13
- Caret.editor:
14
- createEditor() -> backbone of caret, handles ui and abstractions
15
- Caret.theme:
16
- setTheme() -> changes the current highlight.js theme
17
- Caret.langauge:
18
- init() -> initializes default avaible languages
19
- registerLanguage() -> registers a new languages
20
- registeredLangauges[List]: has all the langauges registered
21
- hljs: the highlight.js module
22
- */
7
+ export default Caret;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pfmcodes/caret",
3
- "version": "0.2.8",
3
+ "version": "0.3.0",
4
4
  "description": "The official code editor engine for lexius",
5
5
  "type": "module",
6
6
  "main": "./index.js",
package/utilities.js ADDED
@@ -0,0 +1,36 @@
1
+ export function intoString(a) {
2
+ return `${a}`;
3
+ }
4
+
5
+ export function isMoreThanOneChange(s1, s2) {
6
+ const len1 = s1.length;
7
+ const len2 = s2.length;
8
+
9
+ // 1. If length difference is > 1, they definitely have > 1 change
10
+ if (Math.abs(len1 - len2) > 1) return true;
11
+
12
+ let count = 0;
13
+ let i = 0, j = 0;
14
+
15
+ while (i < len1 && j < len2) {
16
+ if (s1[i] !== s2[j]) {
17
+ count++;
18
+ if (count > 1) return true; // Exit early
19
+
20
+ if (len1 > len2) {
21
+ i++; // Character deleted in s2
22
+ } else if (len1 < len2) {
23
+ j++; // Character added in s2
24
+ } else {
25
+ i++; j++; // Character replaced
26
+ }
27
+ } else {
28
+ i++; j++;
29
+ }
30
+ }
31
+
32
+ // Check for a trailing character difference at the end
33
+ if (i < len1 || j < len2) count++;
34
+
35
+ return count > 1;
36
+ }