@codady/coax 0.0.3 → 0.0.4

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.
@@ -1,10 +1,9 @@
1
1
  /**
2
- * Last modified: 2026/01/12 09:40:20
2
+ * Last modified: 2026/01/12 14:09:42
3
+ * Coax - A custom web component for syntax highlighting, code display, and interactive features
4
+ *
3
5
  */
4
6
 
5
-
6
- import icaxCopy from "@codady/icax/src/icaxCopy";
7
- import icaxCheck from "@codady/icax/src/icaxCheck";
8
7
  import typeWriter from "@codady/utils/typeWriter";
9
8
  import parseClasses from "@codady/utils/parseClasses";
10
9
  import NAMESPACE from "@codady/utils/namespace";
@@ -15,37 +14,43 @@ import createEl from "@codady/utils/createEl";
15
14
 
16
15
  // Define the structure for the language configuration
17
16
  export interface LanguageRule {
18
- token: string;
19
- pattern: RegExp;
20
- light?: string;
21
- dark?: string;
17
+ token: string; // Token representing a specific syntax element (e.g., keyword, string, comment)
18
+ pattern: RegExp; // Regular expression used to match the syntax element
19
+ light?: string; // Optional CSS color for light mode
20
+ dark?: string; // Optional CSS color for dark mode
22
21
  }
23
22
 
24
23
  export interface LanguageConfig {
25
24
  rules: LanguageRule[]; // Array of syntax highlighting rules
26
- alias?: string; // Alias name
25
+ alias?: string; // Alias name for the language (e.g., 'JavaScript', 'JS', etc.)
27
26
  themeStyles?: string; // Optional internal CSS for language-specific styles
28
- cssPrefix?: string; // Optional CSS prefix for class names
27
+ cssPrefix?: string; // Optional CSS prefix for class names used in syntax highlighting
29
28
  }
30
29
 
31
- class CoaxElem extends HTMLElement {
30
+ class Coax extends HTMLElement {
32
31
  // A static Map to hold the configuration for different languages
33
32
  static languages = new Map<string, LanguageConfig>();
33
+
34
+ // A static array to hold the tools registered with the component
34
35
  static tools: any[] = [];
35
- private source: string;
36
- private _renderQueued = false;
37
- private baseStylesEl!: HTMLStyleElement;
38
- private themeStylesEl!: HTMLStyleElement;
39
- private dynamicStylesEl!: HTMLStyleElement;
40
- private highlightedCodeEl!: HTMLElement;
41
- private headerEl!: HTMLElement;
42
- private codeNameEl!: HTMLElement;
43
- private codeToolsEl!: HTMLElement;
44
- private codeBodyEl!: HTMLElement;
45
- public alias: string;
46
- public lineString: string;
47
- public speed: number;
48
- public autoScroll: boolean;
36
+
37
+ private source: string; // Source code content to be highlighted
38
+ private _renderQueued = false; // Flag to prevent multiple render requests
39
+ private baseStylesEl!: HTMLStyleElement; // Element for base styles
40
+ private themeStylesEl!: HTMLStyleElement; // Element for theme styles
41
+ private dynamicStylesEl!: HTMLStyleElement; // Element for dynamic styles
42
+ private highlightedCodeEl!: HTMLElement; // Element that holds the highlighted code
43
+ private headerEl!: HTMLElement; // Header element (for code name, tools, etc.)
44
+ private codeNameEl!: HTMLElement; // Code name element (shows language or alias)
45
+ private codeToolsEl!: HTMLElement; // Code tools element (for interactive tools like copy)
46
+ private codeBodyEl!: HTMLElement; // Code body element (the container for the code)
47
+
48
+ public lang: string = 'plain'; // Language of the code (default is plain text)
49
+ public alias: string = 'Plain Text'; // Alias name for the language (default is 'Plain Text')
50
+ public lineString: string = ''; // The current line's string being typed
51
+ public lastLineString: string = ''; // The last line's string
52
+ public speed: number = 5; // Speed of the typing effect (higher is slower)
53
+ public autoScroll: boolean = true; // Flag to enable/disable auto-scrolling
49
54
 
50
55
  constructor() {
51
56
  super();
@@ -53,12 +58,7 @@ class CoaxElem extends HTMLElement {
53
58
  this.attachShadow({ mode: 'open' });
54
59
  // Remove leading/trailing whitespace from the raw code content
55
60
  this.source = this.textContent?.replace(/^\s*\n|\n\s*$/g, '') || '';
56
- this.lang = 'plain';
57
- this.alias = 'Plain Text';
58
- this.lineString = '';
59
- this.speed = 5;
60
- this.autoScroll = true;
61
- // 1. 初始化基础骨架
61
+ // Initialize the basic structure of the component
62
62
  (this.shadowRoot as any).innerHTML = `
63
63
  <style id="base-styles">
64
64
  :host {
@@ -192,7 +192,7 @@ class CoaxElem extends HTMLElement {
192
192
  </div>
193
193
  `;
194
194
 
195
- // 缓存引用
195
+ // Cache references to various elements
196
196
  this.baseStylesEl = getEl('#base-styles', this.shadowRoot) as HTMLStyleElement;
197
197
  this.themeStylesEl = getEl('#theme-styles', this.shadowRoot) as HTMLStyleElement;
198
198
  this.dynamicStylesEl = getEl('#dynamic-styles', this.shadowRoot) as HTMLStyleElement;
@@ -204,24 +204,30 @@ class CoaxElem extends HTMLElement {
204
204
 
205
205
  this.codeBodyEl.addEventListener('scroll', () => {
206
206
  let flag = this.codeBodyEl.scrollTop + this.codeBodyEl.clientHeight < this.codeBodyEl.scrollHeight;
207
- // 检测是否是用户手动滚动
207
+ // Check if the user manually scrolled
208
208
  this.autoScroll = !flag;
209
209
  });
210
210
  }
211
211
  /**
212
- * Registers a new language with a set of syntax highlighting rules.
213
- * @param name - The name of the programming language.
214
- * @param config - Configuration for the language, including rules, theme, and optional CSS.
215
- */
212
+ * Registers a new language with a set of syntax highlighting rules.
213
+ * @param name - The name of the programming language (e.g., 'javascript', 'html', etc.)
214
+ * @param config - Configuration for the language, including rules, theme, and optional CSS.
215
+ */
216
216
  static register(name: string, config: LanguageConfig): void {
217
217
  // Store the language configuration in the static map
218
218
  this.languages.set(name, { ...config });
219
219
  }
220
- //注册工具箱
220
+ /**
221
+ * Registers tools that can be used with the code editor (e.g., copy, download, etc.).
222
+ * @param items - An array of tool items to register.
223
+ */
221
224
  static addTools(items: toolsItem[]): void {
222
- CoaxElem.tools = items;
225
+ Coax.tools = items;
223
226
  }
224
- //加入工具箱
227
+ /**
228
+ * Mounts the tools to the code tools container.
229
+ * @param toolItems - An array of tool items to be added to the tools container.
230
+ */
225
231
  mountTools(toolItems: any[]) {
226
232
  requestAnimationFrame(() => {
227
233
  this.codeToolsEl.innerHTML = '';
@@ -232,17 +238,24 @@ class CoaxElem extends HTMLElement {
232
238
  this.codeToolsEl.appendChild(createTools(items));
233
239
  });
234
240
  }
235
- // Called when the element is connected to the DOM
241
+ /**
242
+ * Called when the element is connected to the DOM.
243
+ */
236
244
  connectedCallback() {
237
245
  this.render();
238
246
  }
239
247
 
240
- // Observed attributes for changes
248
+ /**
249
+ * Observed attributes for changes. These include the language, height, tools, and speed.
250
+ */
241
251
  static get observedAttributes() { return ['lang', 'height', 'max-height', 'tools', 'speed']; }
242
252
 
243
253
  /**
244
- * Called when any of the observed attributes change.
245
- */
254
+ * Called when any of the observed attributes change.
255
+ * @param name - The name of the changed attribute.
256
+ * @param oldVal - The old value of the attribute.
257
+ * @param newVal - The new value of the attribute.
258
+ */
246
259
  attributeChangedCallback(name: string, oldVal: string, newVal: string) {
247
260
  if (oldVal === newVal) return;
248
261
  if (name === 'height' || name === 'max-height') {
@@ -250,11 +263,12 @@ class CoaxElem extends HTMLElement {
250
263
  }
251
264
 
252
265
  if (name === 'speed') {
266
+ // Convert to integer (0 or 1)
253
267
  this.speed = ~~(!!newVal);
254
268
  }
255
269
 
256
270
  if (name === 'lang') {
257
- //更新当前语言
271
+ // Update the language and re-render
258
272
  this.lang = newVal;
259
273
  this.render();
260
274
  }
@@ -262,14 +276,16 @@ class CoaxElem extends HTMLElement {
262
276
  if (name === 'tools') {
263
277
  if (!newVal) this.codeToolsEl.innerHTML = '';
264
278
  const tools = parseClasses(newVal),
265
- toolItems = CoaxElem.tools.filter(k => tools.includes(k.name));
279
+ toolItems = Coax.tools.filter(k => tools.includes(k.name));
266
280
  if (!toolItems.length) return;
267
281
  this.mountTools(toolItems);
268
282
  }
269
283
  }
270
284
  /**
271
- * 使用正则替换基础样式表中的特定属性,避免操作行内样式
272
- */
285
+ * Updates the base style by replacing specific CSS properties using a regular expression.
286
+ * @param prop - The CSS property name to update (e.g., 'height', 'max-height').
287
+ * @param value - The new value for the property.
288
+ */
273
289
  private updateStyleByRegExp(prop: string, value: string) {
274
290
  // 构建正则:匹配属性名后面跟着冒号,直到分号或换行
275
291
  // 例如:height:\s*[^;]+;
@@ -277,11 +293,22 @@ class CoaxElem extends HTMLElement {
277
293
  // 替换为新的属性值
278
294
  this.baseStylesEl.textContent = this.baseStylesEl.textContent.replace(regex, `;\n${prop}: ${value};`);
279
295
  }
296
+ /**
297
+ * Retrieves the CSS prefix for the language configuration.
298
+ * @param config - The language configuration object.
299
+ * @returns The CSS prefix.
300
+ */
280
301
  getCssPrefix(config: LanguageConfig) {
281
302
  return (config?.cssPrefix || this.lang).replace(/[^a-zA-Z0-9_-]/g, '\\$&');
282
303
  }
304
+ /**
305
+ * Highlights a given string according to the language configuration.
306
+ * @param string - The string to highlight.
307
+ * @param config - The language configuration object.
308
+ * @returns The highlighted string with HTML spans.
309
+ */
283
310
  getHighLightString(string: string, config?: LanguageConfig) {
284
- config = config || CoaxElem.languages.get(this.lang);
311
+ config = config || Coax.languages.get(this.lang);
285
312
  if (!config) return string;
286
313
  // 如果找到了配置,则进行高亮处理
287
314
  const cssPrefix = this.getCssPrefix(config),
@@ -292,6 +319,12 @@ class CoaxElem extends HTMLElement {
292
319
  return i !== -1 && config.rules[i] ? `<span class="${NAMESPACE}-${cssPrefix}-${config.rules[i].token}">${match}</span>` : match;
293
320
  });
294
321
  }
322
+ /**
323
+ * Creates a wrapper element for a line of code.
324
+ * @param index - The line index to assign.
325
+ * @param startIndex - The starting index for the line.
326
+ * @returns A div element wrapping the line of code.
327
+ */
295
328
  createLineWrap(index?: number, startIndex?: number) {
296
329
  let dataIndex = 0;
297
330
  if (index == null && startIndex == null) {
@@ -302,18 +335,24 @@ class CoaxElem extends HTMLElement {
302
335
  }
303
336
  return createEl('div', { 'data-index': dataIndex }, '<div></div>');
304
337
  }
338
+ /**
339
+ * Fills a line of code with highlighted content.
340
+ * @param codeWrap - The element that will contain the highlighted line of code.
341
+ * @param line - The line of code to highlight.
342
+ * @param config - The language configuration object.
343
+ */
305
344
  getLineToFill(codeWrap: Element, line: string, config?: LanguageConfig) {
306
- config = config || CoaxElem.languages.get(this.lang);
345
+ config = config || Coax.languages.get(this.lang);
307
346
  let highlightedLine = this.getHighLightString(line, config);
308
347
  // 将高亮后的内容填充到 div 中
309
348
  codeWrap.innerHTML = highlightedLine;
310
349
  };
311
350
  /**
312
- * 处理新源码,按行高亮并追加到高亮区域
313
- * @param newCode - 新的源码片段
351
+ * Highlights new source code and appends it to the code body.
352
+ * @param newCode - The new source code to highlight and append.
314
353
  */
315
354
  async highlight(newCode: string) {
316
- const config = CoaxElem.languages.get(this.lang),
355
+ const config = Coax.languages.get(this.lang),
317
356
  startIndex = this.highlightedCodeEl.children.length,
318
357
  // 将新源码按行分割
319
358
  newCodeLines = newCode ? newCode.split('\n') : [];
@@ -328,7 +367,7 @@ class CoaxElem extends HTMLElement {
328
367
  const lineWrap = this.createLineWrap(index, startIndex),
329
368
  codeWrap = lineWrap.lastElementChild as Element;
330
369
  //标记完成
331
- this.closeLine(lineWrap);
370
+ (lineWrap as any).completed = true;
332
371
 
333
372
  if (this.hasAttribute('speed')) {
334
373
  this.highlightedCodeEl.appendChild(lineWrap);
@@ -351,13 +390,20 @@ class CoaxElem extends HTMLElement {
351
390
  this.autoScrollCode();
352
391
  return true;
353
392
  }
393
+
394
+ /**
395
+ * Automatically scrolls the code body to the bottom.
396
+ */
354
397
  autoScrollCode() {
355
398
  if (this.autoScroll) {
356
399
  this.codeBodyEl.scrollTop = this.codeBodyEl.scrollHeight;
357
400
  }
358
401
  }
402
+ /**
403
+ * Injects the theme styles for syntax highlighting (light/dark modes).
404
+ */
359
405
  injectThemeStyles() {
360
- const config = CoaxElem.languages.get(this.lang);
406
+ const config = Coax.languages.get(this.lang);
361
407
  if (!config) return;
362
408
 
363
409
  // Get language name, fallback to 'js' if not provided
@@ -382,7 +428,6 @@ class CoaxElem extends HTMLElement {
382
428
  .${NAMESPACE}-${cssPrefix}-${rule.token} { color: var(--${NAMESPACE}-${cssPrefix}-${rule.token},${rule.dark}); }` : ``} `).join('\n');
383
429
  schemeStyles += `}`;
384
430
  // Set the inner HTML of the shadow root, including styles and highlighted code
385
- // 2. 精确更新 DOM 节点而非重写 ShadowRoot
386
431
  this.dynamicStylesEl.textContent = lightStyles + darkStyles + schemeStyles;
387
432
 
388
433
  //附加主题样式
@@ -392,6 +437,10 @@ class CoaxElem extends HTMLElement {
392
437
 
393
438
 
394
439
  }
440
+ /**
441
+ * Updates the alias name for the language based on the configuration.
442
+ * @param config - The language configuration object.
443
+ */
395
444
  updateName(config: any) {
396
445
  if (this.hasAttribute('unnamed')) return;
397
446
  if (!config) {
@@ -403,11 +452,10 @@ class CoaxElem extends HTMLElement {
403
452
  }
404
453
  }
405
454
  /**
406
- * Renders the highlighted code within the shadow DOM.
407
- */
455
+ * Renders the highlighted code within the shadow DOM.
456
+ */
408
457
  render(code = this.source) {
409
458
  //同时多次改变属性,只执行一次
410
- // 如果已经在队列中,则直接返回
411
459
  if (this._renderQueued) return;
412
460
  this._renderQueued = true;
413
461
  // 使用 requestAnimationFrame 将渲染推迟到下一帧
@@ -419,20 +467,23 @@ class CoaxElem extends HTMLElement {
419
467
  this._renderQueued = false;
420
468
  });
421
469
  }
470
+ /**
471
+ * Clears the current content and resets the state.
472
+ */
422
473
  clear() {
423
474
  this.highlightedCodeEl.innerHTML = this.source = this.lineString = '';
424
475
  }
425
476
  /**
426
- * 替换现有的源码并重新渲染
427
- * @param newCode - 新的源码
428
- */
477
+ * Replaces the existing code with new source code and re-renders.
478
+ * @param newCode - The new source code to replace the existing code.
479
+ */
429
480
  async replace(newCode: string) {
430
481
  this.clear();
431
482
  await this.highlight(newCode);
432
483
  }
433
484
  /**
434
- * 末尾新增源码,仅高亮新增的部分
435
- * @param newCode - 新的源码片段
485
+ * Appends new source code to the current content and highlights only the new portion.
486
+ * @param newCode - The new source code to append and highlight.
436
487
  */
437
488
  async append(newCode: string) {
438
489
  // 将新的代码追加到现有代码末尾
@@ -440,7 +491,11 @@ class CoaxElem extends HTMLElement {
440
491
  // 高亮新的部分
441
492
  await this.highlight(newCode);
442
493
  }
443
- getActiveCodeWrap() {
494
+ /**
495
+ * Retrieves the last line of code that was rendered.
496
+ * @returns An object containing the line wrapper and code wrapper for the last line.
497
+ */
498
+ getLastLine() {
444
499
  const lastChild = this.highlightedCodeEl.lastElementChild,
445
500
  lastLine = !lastChild || (this.highlightedCodeEl.lastElementChild as any)?.completed ?
446
501
  this.createLineWrap() : lastChild;
@@ -449,40 +504,53 @@ class CoaxElem extends HTMLElement {
449
504
  codeWrap: lastLine.lastElementChild as Element
450
505
  }
451
506
  }
452
- closeLine(lineWrap?: Element | null) {
453
- lineWrap = lineWrap || this.highlightedCodeEl.lastElementChild;
454
- lineWrap && ((lineWrap as any).completed = true);
507
+ /**
508
+ * Marks the current line as completed and updates the displayed code.
509
+ */
510
+ close() {
511
+ const lineWrap = this.highlightedCodeEl.lastElementChild;
512
+ if (!lineWrap) return;
513
+ (lineWrap as any).completed = true;
514
+ //行结束前保存
515
+ this.lastLineString = this.lineString;
516
+ this.getLineToFill(lineWrap?.lastElementChild as Element, this.lineString);
517
+ //一行结束,清空临时行文本
518
+ this.lineString = '';
455
519
  }
456
- openLine(lineWrap?: Element | null) {
457
- lineWrap = lineWrap || this.highlightedCodeEl.lastElementChild;
458
- lineWrap && ((lineWrap as any).completed = false);
520
+ /**
521
+ * Reopens the last closed line and restores its original content.
522
+ */
523
+ open() {
524
+ const lineWrap = this.highlightedCodeEl.lastElementChild;
525
+ if (!lineWrap) return;
526
+ (lineWrap as any).completed = false;
527
+ //恢复最后一行字符串
528
+ (lineWrap?.lastElementChild as any).textContent = this.lineString = this.lastLineString;
459
529
  }
460
- stream(str: string, forceEnd: boolean = false) {
461
- const { lineWrap, codeWrap } = this.getActiveCodeWrap();
530
+ /**
531
+ * Streams a string of code into the component, either appending or closing the current line.
532
+ * @param str - The code string to stream into the component.
533
+ * @param forceClose - Forcefully close the line if set to `true`.
534
+ */
535
+ stream(str: string, forceClose: boolean = false) {
536
+ const { lineWrap, codeWrap } = this.getLastLine();
462
537
  this.highlightedCodeEl.appendChild(lineWrap);
463
538
  //汇集
464
539
  this.source += str;
465
540
 
466
541
  //如果没有遇到换行符号,也可以强制结束
467
- forceEnd && this.closeLine(lineWrap);
468
-
469
- if (str.startsWith('\n') || str.startsWith('\r')) {
542
+ if (forceClose || (str.startsWith('\n') || str.endsWith('\n'))) {
470
543
  //标记完成
471
- this.closeLine(lineWrap);
472
- //渲染
473
- this.getLineToFill(codeWrap, this.lineString);
474
- //一行结束,清空临时行文本
475
- this.lineString = '';
544
+ this.close();
476
545
  } else {
477
546
  //插入文本
478
547
  codeWrap.innerHTML += str;
479
548
  //临时保存行文本
480
549
  this.lineString += str;
481
-
482
550
  }
483
551
  //滚动到最底部
484
552
  this.autoScrollCode();
485
553
  }
486
554
  }
487
555
 
488
- export default CoaxElem;
556
+ export default Coax;
package/src/modules.js CHANGED
@@ -1,12 +1,12 @@
1
1
  /**
2
- * @since Last modified: 2026/01/12 08:58:23
2
+ * @since Last modified: 2026/01/12 14:10:29
3
3
  */
4
4
  'use strict';
5
- import CoaxElem from './components/CoaxElem.js';
5
+ import Coax from './components/Coax.js';
6
6
  import css from './rules/css.js';
7
7
  import html from './rules/html.js';
8
8
  import javascript from './rules/javascript.js';
9
9
  import markdown from './rules/markdown.js';
10
10
  import typescript from './rules/typescript.js';
11
11
  import copy from './tools/copy.js';
12
- export { CoaxElem, copy, css, html, javascript, typescript, markdown, };
12
+ export { Coax, copy, css, html, javascript, typescript, markdown, };
package/src/modules.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  /**
2
- * @since Last modified: 2026/01/12 08:58:23
2
+ * @since Last modified: 2026/01/12 14:10:29
3
3
  */
4
4
  'use strict'
5
5
 
6
- import CoaxElem from './components/CoaxElem.js';
6
+ import Coax from './components/Coax.js';
7
7
  import css from './rules/css.js';
8
8
  import html from './rules/html.js';
9
9
  import javascript from './rules/javascript.js';
@@ -12,7 +12,7 @@ import typescript from './rules/typescript.js';
12
12
  import copy from './tools/copy.js';
13
13
 
14
14
  export {
15
- CoaxElem,
15
+ Coax,
16
16
  copy,
17
17
  css,
18
18
  html,