juxscript 1.1.42 → 1.1.44

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.
@@ -2,22 +2,48 @@ import { BaseComponent } from './base/BaseComponent.js';
2
2
  export interface CodeOptions {
3
3
  content?: string;
4
4
  language?: string;
5
+ showLineNumbers?: boolean;
6
+ startLine?: number;
7
+ highlightLines?: number[];
5
8
  style?: string;
6
9
  class?: string;
7
10
  }
8
11
  type CodeState = {
9
12
  content: string;
10
13
  language: string;
14
+ showLineNumbers: boolean;
15
+ startLine: number;
16
+ highlightLines: number[];
11
17
  style: string;
12
18
  class: string;
13
19
  };
14
20
  export declare class Code extends BaseComponent<CodeState> {
21
+ private _codeContainer;
15
22
  constructor(id: string, options?: CodeOptions);
16
23
  protected getTriggerEvents(): readonly string[];
17
24
  protected getCallbackEvents(): readonly string[];
18
25
  update(prop: string, value: any): void;
19
26
  content(value: string): this;
20
27
  language(value: string): this;
28
+ showLineNumbers(value: boolean): this;
29
+ startLine(value: number): this;
30
+ highlightLines(lines: number[]): this;
31
+ /**
32
+ * Parse code content into individual lines
33
+ */
34
+ private _parseLines;
35
+ /**
36
+ * Rebuild all line elements
37
+ */
38
+ private _rebuildLines;
39
+ /**
40
+ * Create a single line element
41
+ */
42
+ private _createLineElement;
43
+ /**
44
+ * Update highlighted lines without full rebuild
45
+ */
46
+ private _updateHighlights;
21
47
  render(targetId?: string | HTMLElement | BaseComponent<any>): this;
22
48
  }
23
49
  export declare function code(id: string, options?: CodeOptions): Code;
@@ -1 +1 @@
1
- {"version":3,"file":"code.d.ts","sourceRoot":"","sources":["code.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAMxD,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,KAAK,SAAS,GAAG;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,qBAAa,IAAK,SAAQ,aAAa,CAAC,SAAS,CAAC;gBACpC,EAAE,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB;IASjD,SAAS,CAAC,gBAAgB,IAAI,SAAS,MAAM,EAAE;IAI/C,SAAS,CAAC,iBAAiB,IAAI,SAAS,MAAM,EAAE;IAIhD,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,IAAI;IAQtC,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAK5B,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAS7B,MAAM,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,aAAa,CAAC,GAAG,CAAC,GAAG,IAAI;CA4DnE;AAED,wBAAgB,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,IAAI,CAEhE"}
1
+ {"version":3,"file":"code.d.ts","sourceRoot":"","sources":["code.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAMxD,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,KAAK,SAAS,GAAG;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,OAAO,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,qBAAa,IAAK,SAAQ,aAAa,CAAC,SAAS,CAAC;IAChD,OAAO,CAAC,cAAc,CAA4B;gBAEtC,EAAE,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB;IAYjD,SAAS,CAAC,gBAAgB,IAAI,SAAS,MAAM,EAAE;IAI/C,SAAS,CAAC,iBAAiB,IAAI,SAAS,MAAM,EAAE;IAIhD,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,IAAI;IAsBtC,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAK5B,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAK7B,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAKrC,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAK9B,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI;IASrC;;OAEG;IACH,OAAO,CAAC,WAAW;IAInB;;OAEG;IACH,OAAO,CAAC,aAAa;IAyBrB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IA0B1B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAezB,MAAM,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,aAAa,CAAC,GAAG,CAAC,GAAG,IAAI;CA6EnE;AAED,wBAAgB,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,IAAI,CAEhE"}
@@ -7,9 +7,13 @@ export class Code extends BaseComponent {
7
7
  super(id, {
8
8
  content: options.content ?? '',
9
9
  language: options.language ?? 'javascript',
10
+ showLineNumbers: options.showLineNumbers ?? true,
11
+ startLine: options.startLine ?? 1,
12
+ highlightLines: options.highlightLines ?? [],
10
13
  style: options.style ?? '',
11
14
  class: options.class ?? ''
12
15
  });
16
+ this._codeContainer = null;
13
17
  }
14
18
  getTriggerEvents() {
15
19
  return TRIGGER_EVENTS;
@@ -18,7 +22,20 @@ export class Code extends BaseComponent {
18
22
  return CALLBACK_EVENTS;
19
23
  }
20
24
  update(prop, value) {
21
- // No reactive updates needed
25
+ super.update(prop, value);
26
+ if (!this._codeContainer)
27
+ return;
28
+ switch (prop) {
29
+ case 'content':
30
+ this._rebuildLines();
31
+ break;
32
+ case 'showLineNumbers':
33
+ this._codeContainer.classList.toggle('jux-code-no-numbers', !value);
34
+ break;
35
+ case 'highlightLines':
36
+ this._updateHighlights();
37
+ break;
38
+ }
22
39
  }
23
40
  /* ═════════════════════════════════════════════════════════════════
24
41
  * FLUENT API
@@ -31,23 +48,115 @@ export class Code extends BaseComponent {
31
48
  this.state.language = value;
32
49
  return this;
33
50
  }
51
+ showLineNumbers(value) {
52
+ this.state.showLineNumbers = value;
53
+ return this;
54
+ }
55
+ startLine(value) {
56
+ this.state.startLine = value;
57
+ return this;
58
+ }
59
+ highlightLines(lines) {
60
+ this.state.highlightLines = lines;
61
+ return this;
62
+ }
63
+ /* ═════════════════════════════════════════════════════════════════
64
+ * PARSING & RENDERING
65
+ * ═════════════════════════════════════════════════════════════════ */
66
+ /**
67
+ * Parse code content into individual lines
68
+ */
69
+ _parseLines() {
70
+ return this.state.content.split('\n');
71
+ }
72
+ /**
73
+ * Rebuild all line elements
74
+ */
75
+ _rebuildLines() {
76
+ if (!this._codeContainer)
77
+ return;
78
+ const codeEl = this._codeContainer.querySelector('.jux-code-lines');
79
+ if (!codeEl)
80
+ return;
81
+ codeEl.innerHTML = '';
82
+ const lines = this._parseLines();
83
+ const { startLine, highlightLines, showLineNumbers } = this.state;
84
+ lines.forEach((lineContent, index) => {
85
+ const lineNumber = startLine + index;
86
+ const isHighlighted = highlightLines.includes(lineNumber);
87
+ const lineEl = this._createLineElement(lineContent, lineNumber, isHighlighted);
88
+ codeEl.appendChild(lineEl);
89
+ });
90
+ // Re-run Prism if available
91
+ if (window.Prism && this._codeContainer) {
92
+ window.Prism.highlightAllUnder(this._codeContainer);
93
+ }
94
+ }
95
+ /**
96
+ * Create a single line element
97
+ */
98
+ _createLineElement(content, lineNumber, highlighted) {
99
+ const lineEl = document.createElement('div');
100
+ lineEl.className = 'jux-code-line';
101
+ lineEl.setAttribute('data-line', String(lineNumber));
102
+ if (highlighted) {
103
+ lineEl.classList.add('jux-code-line-highlight');
104
+ }
105
+ // Line number
106
+ if (this.state.showLineNumbers) {
107
+ const lineNum = document.createElement('span');
108
+ lineNum.className = 'jux-code-line-number';
109
+ lineNum.textContent = String(lineNumber);
110
+ lineEl.appendChild(lineNum);
111
+ }
112
+ // Line content
113
+ const lineCode = document.createElement('span');
114
+ lineCode.className = `jux-code-line-content language-${this.state.language}`;
115
+ lineCode.textContent = content || ' '; // Preserve empty lines
116
+ lineEl.appendChild(lineCode);
117
+ return lineEl;
118
+ }
119
+ /**
120
+ * Update highlighted lines without full rebuild
121
+ */
122
+ _updateHighlights() {
123
+ if (!this._codeContainer)
124
+ return;
125
+ const lines = this._codeContainer.querySelectorAll('.jux-code-line');
126
+ lines.forEach((line) => {
127
+ const lineNum = parseInt(line.getAttribute('data-line') || '0', 10);
128
+ const isHighlighted = this.state.highlightLines.includes(lineNum);
129
+ line.classList.toggle('jux-code-line-highlight', isHighlighted);
130
+ });
131
+ }
34
132
  /* ═════════════════════════════════════════════════════════════════
35
133
  * RENDER
36
134
  * ═════════════════════════════════════════════════════════════════ */
37
135
  render(targetId) {
38
136
  const container = this._setupContainer(targetId);
39
- const { content, language, style, class: className } = this.state;
137
+ const { language, showLineNumbers, style, class: className } = this.state;
40
138
  const wrapper = document.createElement('div');
41
139
  wrapper.className = 'jux-code';
42
140
  wrapper.id = this._id;
141
+ if (!showLineNumbers)
142
+ wrapper.classList.add('jux-code-no-numbers');
43
143
  if (className)
44
144
  wrapper.className += ` ${className}`;
45
145
  if (style)
46
146
  wrapper.setAttribute('style', style);
47
147
  const pre = document.createElement('pre');
148
+ pre.className = `language-${language}`;
48
149
  const codeEl = document.createElement('code');
49
- codeEl.className = `language-${language}`;
50
- codeEl.textContent = content;
150
+ codeEl.className = 'jux-code-lines';
151
+ // Build initial lines
152
+ const lines = this._parseLines();
153
+ const { startLine, highlightLines } = this.state;
154
+ lines.forEach((lineContent, index) => {
155
+ const lineNumber = startLine + index;
156
+ const isHighlighted = highlightLines.includes(lineNumber);
157
+ const lineEl = this._createLineElement(lineContent, lineNumber, isHighlighted);
158
+ codeEl.appendChild(lineEl);
159
+ });
51
160
  pre.appendChild(codeEl);
52
161
  wrapper.appendChild(pre);
53
162
  this._wireStandardEvents(wrapper);
@@ -57,29 +166,33 @@ export class Code extends BaseComponent {
57
166
  const transform = toComponent || ((v) => String(v));
58
167
  stateObj.subscribe((val) => {
59
168
  const transformed = transform(val);
60
- codeEl.textContent = transformed;
61
169
  this.state.content = transformed;
62
- if (window.Prism) {
63
- window.Prism.highlightElement(codeEl);
64
- }
170
+ this._rebuildLines();
65
171
  });
66
172
  }
67
173
  else if (property === 'language') {
68
174
  const transform = toComponent || ((v) => String(v));
69
175
  stateObj.subscribe((val) => {
70
176
  const transformed = transform(val);
71
- codeEl.className = `language-${transformed}`;
72
177
  this.state.language = transformed;
73
- if (window.Prism) {
74
- window.Prism.highlightElement(codeEl);
75
- }
178
+ pre.className = `language-${transformed}`;
179
+ this._rebuildLines();
180
+ });
181
+ }
182
+ else if (property === 'highlightLines') {
183
+ const transform = toComponent || ((v) => (Array.isArray(v) ? v : []));
184
+ stateObj.subscribe((val) => {
185
+ const transformed = transform(val);
186
+ this.state.highlightLines = transformed;
187
+ this._updateHighlights();
76
188
  });
77
189
  }
78
190
  });
79
191
  container.appendChild(wrapper);
192
+ this._codeContainer = wrapper;
80
193
  requestAnimationFrame(() => {
81
194
  if (window.Prism) {
82
- window.Prism.highlightElement(codeEl);
195
+ window.Prism.highlightAllUnder(wrapper);
83
196
  }
84
197
  });
85
198
  return this;
@@ -7,6 +7,9 @@ const CALLBACK_EVENTS = [] as const;
7
7
  export interface CodeOptions {
8
8
  content?: string;
9
9
  language?: string;
10
+ showLineNumbers?: boolean;
11
+ startLine?: number;
12
+ highlightLines?: number[];
10
13
  style?: string;
11
14
  class?: string;
12
15
  }
@@ -14,15 +17,23 @@ export interface CodeOptions {
14
17
  type CodeState = {
15
18
  content: string;
16
19
  language: string;
20
+ showLineNumbers: boolean;
21
+ startLine: number;
22
+ highlightLines: number[];
17
23
  style: string;
18
24
  class: string;
19
25
  };
20
26
 
21
27
  export class Code extends BaseComponent<CodeState> {
28
+ private _codeContainer: HTMLElement | null = null;
29
+
22
30
  constructor(id: string, options: CodeOptions = {}) {
23
31
  super(id, {
24
32
  content: options.content ?? '',
25
33
  language: options.language ?? 'javascript',
34
+ showLineNumbers: options.showLineNumbers ?? true,
35
+ startLine: options.startLine ?? 1,
36
+ highlightLines: options.highlightLines ?? [],
26
37
  style: options.style ?? '',
27
38
  class: options.class ?? ''
28
39
  });
@@ -37,7 +48,21 @@ export class Code extends BaseComponent<CodeState> {
37
48
  }
38
49
 
39
50
  update(prop: string, value: any): void {
40
- // No reactive updates needed
51
+ super.update(prop, value);
52
+
53
+ if (!this._codeContainer) return;
54
+
55
+ switch (prop) {
56
+ case 'content':
57
+ this._rebuildLines();
58
+ break;
59
+ case 'showLineNumbers':
60
+ this._codeContainer.classList.toggle('jux-code-no-numbers', !value);
61
+ break;
62
+ case 'highlightLines':
63
+ this._updateHighlights();
64
+ break;
65
+ }
41
66
  }
42
67
 
43
68
  /* ═════════════════════════════════════════════════════════════════
@@ -54,6 +79,103 @@ export class Code extends BaseComponent<CodeState> {
54
79
  return this;
55
80
  }
56
81
 
82
+ showLineNumbers(value: boolean): this {
83
+ this.state.showLineNumbers = value;
84
+ return this;
85
+ }
86
+
87
+ startLine(value: number): this {
88
+ this.state.startLine = value;
89
+ return this;
90
+ }
91
+
92
+ highlightLines(lines: number[]): this {
93
+ this.state.highlightLines = lines;
94
+ return this;
95
+ }
96
+
97
+ /* ═════════════════════════════════════════════════════════════════
98
+ * PARSING & RENDERING
99
+ * ═════════════════════════════════════════════════════════════════ */
100
+
101
+ /**
102
+ * Parse code content into individual lines
103
+ */
104
+ private _parseLines(): string[] {
105
+ return this.state.content.split('\n');
106
+ }
107
+
108
+ /**
109
+ * Rebuild all line elements
110
+ */
111
+ private _rebuildLines(): void {
112
+ if (!this._codeContainer) return;
113
+
114
+ const codeEl = this._codeContainer.querySelector('.jux-code-lines');
115
+ if (!codeEl) return;
116
+
117
+ codeEl.innerHTML = '';
118
+
119
+ const lines = this._parseLines();
120
+ const { startLine, highlightLines, showLineNumbers } = this.state;
121
+
122
+ lines.forEach((lineContent, index) => {
123
+ const lineNumber = startLine + index;
124
+ const isHighlighted = highlightLines.includes(lineNumber);
125
+
126
+ const lineEl = this._createLineElement(lineContent, lineNumber, isHighlighted);
127
+ codeEl.appendChild(lineEl);
128
+ });
129
+
130
+ // Re-run Prism if available
131
+ if ((window as any).Prism && this._codeContainer) {
132
+ (window as any).Prism.highlightAllUnder(this._codeContainer);
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Create a single line element
138
+ */
139
+ private _createLineElement(content: string, lineNumber: number, highlighted: boolean): HTMLElement {
140
+ const lineEl = document.createElement('div');
141
+ lineEl.className = 'jux-code-line';
142
+ lineEl.setAttribute('data-line', String(lineNumber));
143
+
144
+ if (highlighted) {
145
+ lineEl.classList.add('jux-code-line-highlight');
146
+ }
147
+
148
+ // Line number
149
+ if (this.state.showLineNumbers) {
150
+ const lineNum = document.createElement('span');
151
+ lineNum.className = 'jux-code-line-number';
152
+ lineNum.textContent = String(lineNumber);
153
+ lineEl.appendChild(lineNum);
154
+ }
155
+
156
+ // Line content
157
+ const lineCode = document.createElement('span');
158
+ lineCode.className = `jux-code-line-content language-${this.state.language}`;
159
+ lineCode.textContent = content || ' '; // Preserve empty lines
160
+ lineEl.appendChild(lineCode);
161
+
162
+ return lineEl;
163
+ }
164
+
165
+ /**
166
+ * Update highlighted lines without full rebuild
167
+ */
168
+ private _updateHighlights(): void {
169
+ if (!this._codeContainer) return;
170
+
171
+ const lines = this._codeContainer.querySelectorAll('.jux-code-line');
172
+ lines.forEach((line) => {
173
+ const lineNum = parseInt(line.getAttribute('data-line') || '0', 10);
174
+ const isHighlighted = this.state.highlightLines.includes(lineNum);
175
+ line.classList.toggle('jux-code-line-highlight', isHighlighted);
176
+ });
177
+ }
178
+
57
179
  /* ═════════════════════════════════════════════════════════════════
58
180
  * RENDER
59
181
  * ═════════════════════════════════════════════════════════════════ */
@@ -61,18 +183,32 @@ export class Code extends BaseComponent<CodeState> {
61
183
  render(targetId?: string | HTMLElement | BaseComponent<any>): this {
62
184
  const container = this._setupContainer(targetId);
63
185
 
64
- const { content, language, style, class: className } = this.state;
186
+ const { language, showLineNumbers, style, class: className } = this.state;
65
187
 
66
188
  const wrapper = document.createElement('div');
67
189
  wrapper.className = 'jux-code';
68
190
  wrapper.id = this._id;
191
+ if (!showLineNumbers) wrapper.classList.add('jux-code-no-numbers');
69
192
  if (className) wrapper.className += ` ${className}`;
70
193
  if (style) wrapper.setAttribute('style', style);
71
194
 
72
195
  const pre = document.createElement('pre');
196
+ pre.className = `language-${language}`;
197
+
73
198
  const codeEl = document.createElement('code');
74
- codeEl.className = `language-${language}`;
75
- codeEl.textContent = content;
199
+ codeEl.className = 'jux-code-lines';
200
+
201
+ // Build initial lines
202
+ const lines = this._parseLines();
203
+ const { startLine, highlightLines } = this.state;
204
+
205
+ lines.forEach((lineContent, index) => {
206
+ const lineNumber = startLine + index;
207
+ const isHighlighted = highlightLines.includes(lineNumber);
208
+ const lineEl = this._createLineElement(lineContent, lineNumber, isHighlighted);
209
+ codeEl.appendChild(lineEl);
210
+ });
211
+
76
212
  pre.appendChild(codeEl);
77
213
  wrapper.appendChild(pre);
78
214
 
@@ -85,12 +221,8 @@ export class Code extends BaseComponent<CodeState> {
85
221
 
86
222
  stateObj.subscribe((val: any) => {
87
223
  const transformed = transform(val);
88
- codeEl.textContent = transformed;
89
224
  this.state.content = transformed;
90
-
91
- if ((window as any).Prism) {
92
- (window as any).Prism.highlightElement(codeEl);
93
- }
225
+ this._rebuildLines();
94
226
  });
95
227
  }
96
228
  else if (property === 'language') {
@@ -98,21 +230,28 @@ export class Code extends BaseComponent<CodeState> {
98
230
 
99
231
  stateObj.subscribe((val: any) => {
100
232
  const transformed = transform(val);
101
- codeEl.className = `language-${transformed}`;
102
233
  this.state.language = transformed;
234
+ pre.className = `language-${transformed}`;
235
+ this._rebuildLines();
236
+ });
237
+ }
238
+ else if (property === 'highlightLines') {
239
+ const transform = toComponent || ((v: any) => (Array.isArray(v) ? v : []));
103
240
 
104
- if ((window as any).Prism) {
105
- (window as any).Prism.highlightElement(codeEl);
106
- }
241
+ stateObj.subscribe((val: any) => {
242
+ const transformed = transform(val);
243
+ this.state.highlightLines = transformed;
244
+ this._updateHighlights();
107
245
  });
108
246
  }
109
247
  });
110
248
 
111
249
  container.appendChild(wrapper);
250
+ this._codeContainer = wrapper;
112
251
 
113
252
  requestAnimationFrame(() => {
114
253
  if ((window as any).Prism) {
115
- (window as any).Prism.highlightElement(codeEl);
254
+ (window as any).Prism.highlightAllUnder(wrapper);
116
255
  }
117
256
  });
118
257
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juxscript",
3
- "version": "1.1.42",
3
+ "version": "1.1.44",
4
4
  "type": "module",
5
5
  "description": "A JavaScript UX authorship platform",
6
6
  "main": "index.js",