@codady/coax 0.0.3 → 0.0.5

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