mrmd-js 2.0.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.
- package/README.md +842 -0
- package/dist/index.cjs +7613 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +7530 -0
- package/dist/index.js.map +1 -0
- package/dist/mrmd-js.iife.js +7618 -0
- package/dist/mrmd-js.iife.js.map +1 -0
- package/package.json +47 -0
- package/src/analysis/format.js +371 -0
- package/src/analysis/index.js +18 -0
- package/src/analysis/is-complete.js +394 -0
- package/src/constants.js +44 -0
- package/src/execute/css.js +205 -0
- package/src/execute/html.js +162 -0
- package/src/execute/index.js +41 -0
- package/src/execute/interface.js +144 -0
- package/src/execute/javascript.js +197 -0
- package/src/execute/registry.js +245 -0
- package/src/index.js +136 -0
- package/src/lsp/complete.js +353 -0
- package/src/lsp/format.js +310 -0
- package/src/lsp/hover.js +126 -0
- package/src/lsp/index.js +55 -0
- package/src/lsp/inspect.js +466 -0
- package/src/lsp/parse.js +455 -0
- package/src/lsp/variables.js +283 -0
- package/src/runtime.js +518 -0
- package/src/session/console-capture.js +181 -0
- package/src/session/context/iframe.js +407 -0
- package/src/session/context/index.js +12 -0
- package/src/session/context/interface.js +38 -0
- package/src/session/context/main.js +357 -0
- package/src/session/index.js +16 -0
- package/src/session/manager.js +327 -0
- package/src/session/session.js +678 -0
- package/src/transform/async.js +133 -0
- package/src/transform/extract.js +251 -0
- package/src/transform/index.js +10 -0
- package/src/transform/persistence.js +176 -0
- package/src/types/analysis.js +24 -0
- package/src/types/capabilities.js +44 -0
- package/src/types/completion.js +47 -0
- package/src/types/execution.js +62 -0
- package/src/types/index.js +16 -0
- package/src/types/inspection.js +39 -0
- package/src/types/session.js +32 -0
- package/src/types/streaming.js +74 -0
- package/src/types/variables.js +54 -0
- package/src/utils/ansi-renderer.js +301 -0
- package/src/utils/css-applicator.js +149 -0
- package/src/utils/html-renderer.js +355 -0
- package/src/utils/index.js +25 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSS Applicator
|
|
3
|
+
*
|
|
4
|
+
* Utility for applying CSS displayData from cell execution.
|
|
5
|
+
* Manages stylesheet lifecycle and scoping.
|
|
6
|
+
*
|
|
7
|
+
* @module utils/css-applicator
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {Object} ApplyOptions
|
|
12
|
+
* @property {string} [id] - Style element ID for updates
|
|
13
|
+
* @property {string} [scope] - Scope selector to prefix rules
|
|
14
|
+
* @property {boolean} [append=false] - Append instead of replace
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @typedef {Object} ApplyResult
|
|
19
|
+
* @property {HTMLStyleElement} element - The style element
|
|
20
|
+
* @property {string} id - Style element ID
|
|
21
|
+
* @property {boolean} replaced - Whether an existing style was replaced
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { scopeStyles } from './html-renderer.js';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* CSS Applicator class
|
|
28
|
+
*/
|
|
29
|
+
export class CssApplicator {
|
|
30
|
+
/** @type {Map<string, HTMLStyleElement>} */
|
|
31
|
+
#styles = new Map();
|
|
32
|
+
|
|
33
|
+
/** @type {HTMLElement} */
|
|
34
|
+
#container;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Create CSS applicator
|
|
38
|
+
* @param {HTMLElement} [container=document.head] - Container for style elements
|
|
39
|
+
*/
|
|
40
|
+
constructor(container) {
|
|
41
|
+
this.#container = container ?? document.head;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Apply CSS string
|
|
46
|
+
*
|
|
47
|
+
* @param {string} css - CSS string
|
|
48
|
+
* @param {ApplyOptions} [options]
|
|
49
|
+
* @returns {ApplyResult}
|
|
50
|
+
*/
|
|
51
|
+
apply(css, options = {}) {
|
|
52
|
+
const id = options.id ?? `mrmd-style-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
53
|
+
|
|
54
|
+
// Scope CSS if requested
|
|
55
|
+
let processedCss = css;
|
|
56
|
+
if (options.scope) {
|
|
57
|
+
processedCss = scopeStyles(css, options.scope);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Check for existing style with same ID
|
|
61
|
+
let element = this.#styles.get(id);
|
|
62
|
+
let replaced = false;
|
|
63
|
+
|
|
64
|
+
if (element) {
|
|
65
|
+
if (options.append) {
|
|
66
|
+
element.textContent += '\n' + processedCss;
|
|
67
|
+
} else {
|
|
68
|
+
element.textContent = processedCss;
|
|
69
|
+
replaced = true;
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
element = document.createElement('style');
|
|
73
|
+
element.id = id;
|
|
74
|
+
element.textContent = processedCss;
|
|
75
|
+
this.#container.appendChild(element);
|
|
76
|
+
this.#styles.set(id, element);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return { element, id, replaced };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Apply CSS from displayData
|
|
84
|
+
*
|
|
85
|
+
* @param {import('../types/execution.js').DisplayData} displayData
|
|
86
|
+
* @param {ApplyOptions} [options]
|
|
87
|
+
* @returns {ApplyResult | null}
|
|
88
|
+
*/
|
|
89
|
+
applyDisplayData(displayData, options = {}) {
|
|
90
|
+
const css = displayData.data['text/css'];
|
|
91
|
+
if (!css) return null;
|
|
92
|
+
|
|
93
|
+
// Use metadata for options if available
|
|
94
|
+
const id = options.id ?? (displayData.metadata?.id ? String(displayData.metadata.id) : undefined);
|
|
95
|
+
const scope = options.scope ?? (displayData.metadata?.scope ? String(displayData.metadata.scope) : undefined);
|
|
96
|
+
|
|
97
|
+
return this.apply(css, { ...options, id, scope });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Remove a style by ID
|
|
102
|
+
* @param {string} id
|
|
103
|
+
* @returns {boolean}
|
|
104
|
+
*/
|
|
105
|
+
remove(id) {
|
|
106
|
+
const element = this.#styles.get(id);
|
|
107
|
+
if (!element) return false;
|
|
108
|
+
|
|
109
|
+
element.remove();
|
|
110
|
+
this.#styles.delete(id);
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Remove all managed styles
|
|
116
|
+
*/
|
|
117
|
+
clear() {
|
|
118
|
+
for (const element of this.#styles.values()) {
|
|
119
|
+
element.remove();
|
|
120
|
+
}
|
|
121
|
+
this.#styles.clear();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get all managed style IDs
|
|
126
|
+
* @returns {string[]}
|
|
127
|
+
*/
|
|
128
|
+
list() {
|
|
129
|
+
return Array.from(this.#styles.keys());
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Get a style element by ID
|
|
134
|
+
* @param {string} id
|
|
135
|
+
* @returns {HTMLStyleElement | undefined}
|
|
136
|
+
*/
|
|
137
|
+
get(id) {
|
|
138
|
+
return this.#styles.get(id);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Create a CSS applicator
|
|
144
|
+
* @param {HTMLElement} [container]
|
|
145
|
+
* @returns {CssApplicator}
|
|
146
|
+
*/
|
|
147
|
+
export function createCssApplicator(container) {
|
|
148
|
+
return new CssApplicator(container);
|
|
149
|
+
}
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML Renderer
|
|
3
|
+
*
|
|
4
|
+
* Utility for rendering HTML displayData from cell execution.
|
|
5
|
+
* Provides three rendering modes: direct, shadow, and scoped.
|
|
6
|
+
*
|
|
7
|
+
* @module utils/html-renderer
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {'direct' | 'shadow' | 'scoped'} RenderMode
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {Object} RenderOptions
|
|
16
|
+
* @property {RenderMode} [mode='direct'] - Rendering mode
|
|
17
|
+
* @property {string} [scopeClass] - Scope class for 'scoped' mode
|
|
18
|
+
* @property {boolean} [executeScripts=true] - Execute inline scripts
|
|
19
|
+
* @property {(error: Error, script: string) => void} [onScriptError] - Script error callback
|
|
20
|
+
* @property {boolean} [clear=true] - Clear container before rendering
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @typedef {Object} RenderResult
|
|
25
|
+
* @property {HTMLElement} container - Container element
|
|
26
|
+
* @property {ShadowRoot} [shadowRoot] - Shadow root if shadow mode
|
|
27
|
+
* @property {number} scriptsExecuted - Number of scripts executed
|
|
28
|
+
* @property {Error[]} scriptErrors - Script errors
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/** @type {Set<string>} */
|
|
32
|
+
const executedScripts = new Set();
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* HTML Renderer class for rendering displayData
|
|
36
|
+
*/
|
|
37
|
+
export class HtmlRenderer {
|
|
38
|
+
/** @type {Map<string, Set<string>>} */
|
|
39
|
+
#scriptHashes = new Map();
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Render HTML string into a container
|
|
43
|
+
*
|
|
44
|
+
* @param {string} html - HTML string to render
|
|
45
|
+
* @param {HTMLElement} container - Target container
|
|
46
|
+
* @param {RenderOptions} [options]
|
|
47
|
+
* @returns {RenderResult}
|
|
48
|
+
*/
|
|
49
|
+
render(html, container, options = {}) {
|
|
50
|
+
const mode = options.mode ?? 'direct';
|
|
51
|
+
|
|
52
|
+
switch (mode) {
|
|
53
|
+
case 'shadow':
|
|
54
|
+
return this.#renderShadow(html, container, options);
|
|
55
|
+
case 'scoped':
|
|
56
|
+
return this.#renderScoped(html, container, options);
|
|
57
|
+
case 'direct':
|
|
58
|
+
default:
|
|
59
|
+
return this.#renderDirect(html, container, options);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Render displayData into container
|
|
65
|
+
*
|
|
66
|
+
* @param {import('../types/execution.js').DisplayData} displayData
|
|
67
|
+
* @param {HTMLElement} container
|
|
68
|
+
* @param {RenderOptions} [options]
|
|
69
|
+
* @returns {RenderResult}
|
|
70
|
+
*/
|
|
71
|
+
renderDisplayData(displayData, container, options = {}) {
|
|
72
|
+
const html = displayData.data['text/html'];
|
|
73
|
+
if (!html) {
|
|
74
|
+
return {
|
|
75
|
+
container,
|
|
76
|
+
scriptsExecuted: 0,
|
|
77
|
+
scriptErrors: [],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Use scopeClass from metadata if available
|
|
82
|
+
const scopeClass = options.scopeClass ?? displayData.metadata?.scopeClass;
|
|
83
|
+
|
|
84
|
+
return this.render(html, container, {
|
|
85
|
+
...options,
|
|
86
|
+
scopeClass: typeof scopeClass === 'string' ? scopeClass : undefined,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Render in direct mode (no isolation)
|
|
92
|
+
* @param {string} html
|
|
93
|
+
* @param {HTMLElement} container
|
|
94
|
+
* @param {RenderOptions} options
|
|
95
|
+
* @returns {RenderResult}
|
|
96
|
+
*/
|
|
97
|
+
#renderDirect(html, container, options) {
|
|
98
|
+
if (options.clear !== false) {
|
|
99
|
+
container.innerHTML = '';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Extract scripts before setting innerHTML
|
|
103
|
+
const { content, scripts } = this.#extractScripts(html);
|
|
104
|
+
|
|
105
|
+
// Append content
|
|
106
|
+
const temp = document.createElement('div');
|
|
107
|
+
temp.innerHTML = content;
|
|
108
|
+
while (temp.firstChild) {
|
|
109
|
+
container.appendChild(temp.firstChild);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Execute scripts
|
|
113
|
+
const { executed, errors } = this.#executeScripts(scripts, container, options);
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
container,
|
|
117
|
+
scriptsExecuted: executed,
|
|
118
|
+
scriptErrors: errors,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Render in shadow mode (full isolation via Shadow DOM)
|
|
124
|
+
* @param {string} html
|
|
125
|
+
* @param {HTMLElement} container
|
|
126
|
+
* @param {RenderOptions} options
|
|
127
|
+
* @returns {RenderResult}
|
|
128
|
+
*/
|
|
129
|
+
#renderShadow(html, container, options) {
|
|
130
|
+
// Create or reuse shadow root
|
|
131
|
+
let shadowRoot = container.shadowRoot;
|
|
132
|
+
if (!shadowRoot) {
|
|
133
|
+
shadowRoot = container.attachShadow({ mode: 'open' });
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (options.clear !== false) {
|
|
137
|
+
shadowRoot.innerHTML = '';
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Extract scripts
|
|
141
|
+
const { content, scripts } = this.#extractScripts(html);
|
|
142
|
+
|
|
143
|
+
// Set content
|
|
144
|
+
const temp = document.createElement('div');
|
|
145
|
+
temp.innerHTML = content;
|
|
146
|
+
while (temp.firstChild) {
|
|
147
|
+
shadowRoot.appendChild(temp.firstChild);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Execute scripts in shadow context
|
|
151
|
+
const { executed, errors } = this.#executeScripts(scripts, shadowRoot, options);
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
container,
|
|
155
|
+
shadowRoot,
|
|
156
|
+
scriptsExecuted: executed,
|
|
157
|
+
scriptErrors: errors,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Render in scoped mode (CSS isolation via class prefixing)
|
|
163
|
+
* @param {string} html
|
|
164
|
+
* @param {HTMLElement} container
|
|
165
|
+
* @param {RenderOptions} options
|
|
166
|
+
* @returns {RenderResult}
|
|
167
|
+
*/
|
|
168
|
+
#renderScoped(html, container, options) {
|
|
169
|
+
const scopeClass = options.scopeClass ?? `mrmd-scope-${Date.now()}`;
|
|
170
|
+
|
|
171
|
+
// Add scope class to container
|
|
172
|
+
container.classList.add(scopeClass);
|
|
173
|
+
|
|
174
|
+
if (options.clear !== false) {
|
|
175
|
+
container.innerHTML = '';
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Extract scripts and styles
|
|
179
|
+
const { content, scripts, styles } = this.#extractScriptsAndStyles(html);
|
|
180
|
+
|
|
181
|
+
// Scope and append styles
|
|
182
|
+
for (const style of styles) {
|
|
183
|
+
const scopedCss = scopeStyles(style, `.${scopeClass}`);
|
|
184
|
+
const styleEl = document.createElement('style');
|
|
185
|
+
styleEl.textContent = scopedCss;
|
|
186
|
+
container.appendChild(styleEl);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Append content
|
|
190
|
+
const temp = document.createElement('div');
|
|
191
|
+
temp.innerHTML = content;
|
|
192
|
+
while (temp.firstChild) {
|
|
193
|
+
container.appendChild(temp.firstChild);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Execute scripts
|
|
197
|
+
const { executed, errors } = this.#executeScripts(scripts, container, options);
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
container,
|
|
201
|
+
scriptsExecuted: executed,
|
|
202
|
+
scriptErrors: errors,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Extract scripts from HTML
|
|
208
|
+
* @param {string} html
|
|
209
|
+
* @returns {{ content: string, scripts: string[] }}
|
|
210
|
+
*/
|
|
211
|
+
#extractScripts(html) {
|
|
212
|
+
const scripts = [];
|
|
213
|
+
const content = html.replace(/<script\b[^>]*>([\s\S]*?)<\/script>/gi, (match, code) => {
|
|
214
|
+
scripts.push(code.trim());
|
|
215
|
+
return '';
|
|
216
|
+
});
|
|
217
|
+
return { content, scripts };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Extract scripts and styles from HTML
|
|
222
|
+
* @param {string} html
|
|
223
|
+
* @returns {{ content: string, scripts: string[], styles: string[] }}
|
|
224
|
+
*/
|
|
225
|
+
#extractScriptsAndStyles(html) {
|
|
226
|
+
const scripts = [];
|
|
227
|
+
const styles = [];
|
|
228
|
+
|
|
229
|
+
let content = html.replace(/<script\b[^>]*>([\s\S]*?)<\/script>/gi, (match, code) => {
|
|
230
|
+
scripts.push(code.trim());
|
|
231
|
+
return '';
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
content = content.replace(/<style\b[^>]*>([\s\S]*?)<\/style>/gi, (match, css) => {
|
|
235
|
+
styles.push(css.trim());
|
|
236
|
+
return '';
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
return { content, scripts, styles };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Execute scripts
|
|
244
|
+
* @param {string[]} scripts
|
|
245
|
+
* @param {HTMLElement | ShadowRoot} context
|
|
246
|
+
* @param {RenderOptions} options
|
|
247
|
+
* @returns {{ executed: number, errors: Error[] }}
|
|
248
|
+
*/
|
|
249
|
+
#executeScripts(scripts, context, options) {
|
|
250
|
+
if (options.executeScripts === false) {
|
|
251
|
+
return { executed: 0, errors: [] };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
let executed = 0;
|
|
255
|
+
const errors = [];
|
|
256
|
+
|
|
257
|
+
for (const code of scripts) {
|
|
258
|
+
if (!code) continue;
|
|
259
|
+
|
|
260
|
+
// Skip already executed scripts (deduplication by content hash)
|
|
261
|
+
const hash = this.#hashCode(code);
|
|
262
|
+
if (executedScripts.has(hash)) {
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
executedScripts.add(hash);
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
// Create script element for proper execution
|
|
269
|
+
const script = document.createElement('script');
|
|
270
|
+
script.textContent = code;
|
|
271
|
+
|
|
272
|
+
// Add to document to execute
|
|
273
|
+
if (context instanceof ShadowRoot) {
|
|
274
|
+
context.appendChild(script);
|
|
275
|
+
} else {
|
|
276
|
+
context.appendChild(script);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
executed++;
|
|
280
|
+
} catch (err) {
|
|
281
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
282
|
+
errors.push(error);
|
|
283
|
+
options.onScriptError?.(error, code);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return { executed, errors };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Simple hash function for deduplication
|
|
292
|
+
* @param {string} str
|
|
293
|
+
* @returns {string}
|
|
294
|
+
*/
|
|
295
|
+
#hashCode(str) {
|
|
296
|
+
let hash = 0;
|
|
297
|
+
for (let i = 0; i < str.length; i++) {
|
|
298
|
+
const char = str.charCodeAt(i);
|
|
299
|
+
hash = ((hash << 5) - hash) + char;
|
|
300
|
+
hash = hash & hash;
|
|
301
|
+
}
|
|
302
|
+
return hash.toString(36);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Clear executed scripts cache (for re-execution)
|
|
307
|
+
* @param {string} [execId] - Clear specific execution, or all if not provided
|
|
308
|
+
*/
|
|
309
|
+
clearScripts(execId) {
|
|
310
|
+
if (execId) {
|
|
311
|
+
this.#scriptHashes.delete(execId);
|
|
312
|
+
} else {
|
|
313
|
+
this.#scriptHashes.clear();
|
|
314
|
+
executedScripts.clear();
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Scope CSS selectors with a prefix
|
|
321
|
+
* @param {string} css
|
|
322
|
+
* @param {string} scopeSelector
|
|
323
|
+
* @returns {string}
|
|
324
|
+
*/
|
|
325
|
+
export function scopeStyles(css, scopeSelector) {
|
|
326
|
+
// Simple CSS scoping - prefix each selector
|
|
327
|
+
// This is a basic implementation; a full parser would be more robust
|
|
328
|
+
|
|
329
|
+
return css.replace(
|
|
330
|
+
/([^\r\n,{}]+)(,(?=[^}]*{)|\s*{)/g,
|
|
331
|
+
(match, selector, suffix) => {
|
|
332
|
+
// Don't scope @-rules
|
|
333
|
+
if (selector.trim().startsWith('@')) {
|
|
334
|
+
return match;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Don't scope :root, html, body
|
|
338
|
+
const trimmed = selector.trim();
|
|
339
|
+
if (trimmed === ':root' || trimmed === 'html' || trimmed === 'body') {
|
|
340
|
+
return `${scopeSelector}${suffix}`;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Scope the selector
|
|
344
|
+
return `${scopeSelector} ${selector.trim()}${suffix}`;
|
|
345
|
+
}
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Create a new HTML renderer
|
|
351
|
+
* @returns {HtmlRenderer}
|
|
352
|
+
*/
|
|
353
|
+
export function createHtmlRenderer() {
|
|
354
|
+
return new HtmlRenderer();
|
|
355
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client Utilities
|
|
3
|
+
*
|
|
4
|
+
* Utilities for rendering execution output in browser environments.
|
|
5
|
+
*
|
|
6
|
+
* @module utils
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
HtmlRenderer,
|
|
11
|
+
createHtmlRenderer,
|
|
12
|
+
scopeStyles,
|
|
13
|
+
} from './html-renderer.js';
|
|
14
|
+
|
|
15
|
+
export {
|
|
16
|
+
CssApplicator,
|
|
17
|
+
createCssApplicator,
|
|
18
|
+
} from './css-applicator.js';
|
|
19
|
+
|
|
20
|
+
export {
|
|
21
|
+
AnsiRenderer,
|
|
22
|
+
ansiToHtml,
|
|
23
|
+
stripAnsi,
|
|
24
|
+
createAnsiRenderer,
|
|
25
|
+
} from './ansi-renderer.js';
|