chore: add svelte component layer with monaco editor
This commit is contained in:
1 parent
8071085e94
commit
54f7f81160
55 files changed
+20563
-2043
No files matched your search
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* Theme custom CSS is global in the launcher. The advanced editor renders a
|
||||
* single 1000 x 650 canvas at a time, so previews are scoped to that canvas
|
||||
* instead of restyling the editor's own chrome.
|
||||
*/
|
||||
|
||||
const ROOT_SELECTORS = /(^|[\s>+~,(])(?::root|html|body)(?=$|[\s>+~.#:[\],)])/g;
|
||||
|
||||
const splitSelectorList = (selectorText: string): string[] => {
|
||||
const selectors: string[] = [];
|
||||
let start = 0;
|
||||
let depth = 0;
|
||||
let quote = '';
|
||||
let escaped = false;
|
||||
|
||||
for (let index = 0; index < selectorText.length; index += 1) {
|
||||
const character = selectorText[index];
|
||||
|
||||
if (escaped) {
|
||||
escaped = false;
|
||||
continue;
|
||||
}
|
||||
if (character === '\\') {
|
||||
escaped = true;
|
||||
continue;
|
||||
}
|
||||
if (quote) {
|
||||
if (character === quote) quote = '';
|
||||
continue;
|
||||
}
|
||||
if (character === '"' || character === "'") {
|
||||
quote = character;
|
||||
continue;
|
||||
}
|
||||
if (character === '(' || character === '[') {
|
||||
depth += 1;
|
||||
continue;
|
||||
}
|
||||
if ((character === ')' || character === ']') && depth > 0) {
|
||||
depth -= 1;
|
||||
continue;
|
||||
}
|
||||
if (character === ',' && depth === 0) {
|
||||
selectors.push(selectorText.slice(start, index));
|
||||
start = index + 1;
|
||||
}
|
||||
}
|
||||
|
||||
selectors.push(selectorText.slice(start));
|
||||
return selectors;
|
||||
};
|
||||
|
||||
const scopeSelector = (selector: string, scope: string): string => {
|
||||
const trimmed = selector.trim();
|
||||
if (!trimmed || trimmed.startsWith(scope)) return trimmed;
|
||||
|
||||
const rooted = trimmed.replace(
|
||||
ROOT_SELECTORS,
|
||||
(_match, boundary: string) => `${boundary}${scope}`
|
||||
);
|
||||
if (rooted.startsWith(scope)) return rooted;
|
||||
return `${scope} ${rooted}`;
|
||||
};
|
||||
|
||||
/** Prefix every selector in a comma-separated list with the editor canvas scope. */
|
||||
export const scopeThemeSelectors = (selectorText: string, scope: string): string =>
|
||||
splitSelectorList(selectorText)
|
||||
.map((selector) => scopeSelector(selector, scope))
|
||||
.filter(Boolean)
|
||||
.join(', ');
|
||||
|
||||
const scopeRule = (rule: CSSRule, scope: string) => {
|
||||
if (rule.type === 1) {
|
||||
const styleRule = rule as CSSStyleRule;
|
||||
styleRule.selectorText = scopeThemeSelectors(styleRule.selectorText, scope);
|
||||
return;
|
||||
}
|
||||
|
||||
const grouping = rule as CSSGroupingRule;
|
||||
if (grouping.cssRules) {
|
||||
for (const child of Array.from(grouping.cssRules)) scopeRule(child, scope);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse custom CSS with the browser and scope its selectors to the preview
|
||||
* canvas. `@font-face`, keyframes and at-rule blocks are preserved.
|
||||
*/
|
||||
export const scopeThemeCss = (css: string | undefined, scope: string): string => {
|
||||
const source = css?.trim();
|
||||
if (!source || typeof document === 'undefined') return '';
|
||||
|
||||
const style = document.createElement('style');
|
||||
style.media = 'not all';
|
||||
style.textContent = source;
|
||||
document.head.appendChild(style);
|
||||
|
||||
try {
|
||||
const sheet = style.sheet;
|
||||
if (!sheet) return '';
|
||||
const rules = Array.from(sheet.cssRules);
|
||||
for (const rule of rules) scopeRule(rule, scope);
|
||||
return rules.map((rule) => rule.cssText).join('\n');
|
||||
} catch {
|
||||
return '';
|
||||
} finally {
|
||||
style.remove();
|
||||
}
|
||||
};
|
||||
Reference in new issue
Block a user