diff --git a/.gitignore b/.gitignore
index 576b4157aa..6d55785571 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,6 +16,7 @@
/.astro
/dist
/public/assets/code-viewer
+/public/ig-themes
# browser auto-generated files:
/browser/node_modules
diff --git a/astro.config.mjs b/astro.config.mjs
index b10a500b68..2e6e511ddb 100644
--- a/astro.config.mjs
+++ b/astro.config.mjs
@@ -40,26 +40,44 @@ function stripSampleInstantiation() {
}
/**
- * Vite plugin: convert local CSS/SCSS imports in sample index.ts files to
- * `?inline` imports with dynamic `
+ )}
diff --git a/src/pages/[...slug].astro b/src/pages/[...slug].astro
index d1f7efa702..2a3b86c810 100644
--- a/src/pages/[...slug].astro
+++ b/src/pages/[...slug].astro
@@ -13,7 +13,7 @@
*/
import SampleLayout from '../layouts/SampleLayout.astro';
-import { keyToSlug, slugToInfo, extractSampleHtml } from '../utils/samples';
+import { keyToSlug, slugToInfo, extractSampleHtml, resolveSampleStyles } from '../utils/samples';
// All sample index.html files (raw HTML for server-side rendering)
// Path is relative to THIS file: src/pages/[...slug].astro
@@ -24,6 +24,23 @@ const htmlModules = import.meta.glob('../../samples/**/index.html', {
eager: true,
}) as Record;
+// Raw entry-module source for every sample. Used only to learn which
+// stylesheets each sample imports, and in what order.
+const tsSources = import.meta.glob('../../samples/**/src/index.ts', {
+ query: '?raw',
+ import: 'default',
+ eager: true,
+}) as Record;
+
+// Every sample-local stylesheet, compiled to plain CSS text (SCSS included).
+// These are inlined into so the sample is styled at first paint instead
+// of after its (multi-megabyte) JS chunk finally evaluates.
+const sampleCss = import.meta.glob('../../samples/**/src/*.{css,scss}', {
+ query: '?inline',
+ import: 'default',
+ eager: true,
+}) as Record;
+
const PKG_PREFIX = '../../samples/';
const HTML_SUFFIX = '/index.html';
@@ -55,10 +72,26 @@ const info = slugToInfo(slug)!;
const rawHtml = htmlModules[`${PKG_PREFIX}${slug}${HTML_SUFFIX}`] ?? '';
const sampleHtml = extractSampleHtml(rawHtml);
+// Resolve this sample's stylesheets so SampleLayout can put them in .
+// Theme sheets become s to public/ig-themes/; sample-local files are
+// inlined. Anything that fails to resolve is dropped here and left to the
+// sample module's own import, so a miss degrades to the old behaviour rather
+// than losing the styles.
+const base = import.meta.env.BASE_URL.replace(/\/$/, '');
+const styles = resolveSampleStyles(tsSources[`${PKG_PREFIX}${slug}/src/index.ts`] ?? '', slug, base)
+ .map(style =>
+ style.kind === 'link'
+ ? style
+ : sampleCss[style.key] !== undefined
+ ? { kind: 'inline' as const, css: sampleCss[style.key] }
+ : null,
+ )
+ .filter(v => v !== null);
+
const pageTitle = `${info.displayName} | ${info.componentName} | Ignite UI Web Components`;
---
-
+
@@ -73,9 +106,19 @@ const pageTitle = `${info.displayName} | ${info.componentName} | Ignite UI Web C
// Vite resolves this glob at build time → one lazy chunk per sample
const sampleModules = import.meta.glob('../../samples/**/src/index.ts');
+ // Lifts the layout.css guard that hides not-yet-upgraded custom elements.
+ // Must run on EVERY exit path, including failures — a sample that throws
+ // should still show whatever it managed to render, not stay blank.
+ function markReady() {
+ document.documentElement.dataset.sampleReady = '';
+ }
+
function loadSample() {
const slug = document.documentElement.dataset.sampleSlug;
- if (!slug) return;
+ if (!slug) {
+ markReady();
+ return;
+ }
const moduleKey = `../../samples/${slug}/src/index.ts`;
@@ -91,10 +134,11 @@ const pageTitle = `${info.displayName} | ${info.componentName} | Ignite UI Web C
}
}).catch((err: unknown) => {
console.error(`[astro] failed to load sample module: ${moduleKey}`, err);
- });
+ }).finally(markReady);
} else {
console.warn(`[astro] no sample module found for slug "${slug}". Key: ${moduleKey}`);
console.info('[astro] available keys:', Object.keys(sampleModules).slice(0, 5));
+ markReady();
}
}
diff --git a/src/utils/samples.ts b/src/utils/samples.ts
index 6600543424..4e79e5da6a 100644
--- a/src/utils/samples.ts
+++ b/src/utils/samples.ts
@@ -129,3 +129,70 @@ export function buildNavTree(samples: SampleInfo[]): NavGroup[] {
return [...groupMap.values()];
}
+
+// ---------------------------------------------------------------------------
+// Sample stylesheet resolution
+// ---------------------------------------------------------------------------
+
+/**
+ * A stylesheet a sample entry module pulls in, resolved to how the page should
+ * put it in .
+ *
+ * - `link` → an Ignite UI theme, served from public/ig-themes/ (cached across
+ * every sample page, and re-pointable by the docs theming widget).
+ * - `glob` → a sample-local file, whose compiled text the page inlines.
+ */
+export type SampleStyle =
+ | { kind: 'link'; href: string; pkg: 'webcomponents' | 'grids'; variant: string; theme: string }
+ | { kind: 'glob'; key: string };
+
+/** `import 'x.css';` / `import "./y.scss";` — the same shape the build plugin strips. */
+export const CSS_IMPORT_RE = /^import\s+['"]([^'"]+\.(?:css|scss))['"];?\s*$/gm;
+
+/** Bare theme specifiers, e.g. `igniteui-webcomponents-grids/grids/themes/light/material.css` */
+const THEME_SPEC_RE =
+ /^igniteui-webcomponents(-grids\/grids)?\/themes\/(light|dark)\/(material|bootstrap|fluent|indigo)\.css$/;
+
+/**
+ * Read a sample entry module's source and return, in source order, every
+ * stylesheet it imports.
+ *
+ * Order matters: it is the cascade order, so the page must emit these into
+ * exactly as they appear here.
+ *
+ * @param source raw text of the sample's src/index.ts
+ * @param slug e.g. "inputs/button-group/overview"
+ * @param base site base path, '' or e.g. '/webcomponents-demos'
+ */
+export function resolveSampleStyles(source: string, slug: string, base: string): SampleStyle[] {
+ const styles: SampleStyle[] = [];
+ CSS_IMPORT_RE.lastIndex = 0;
+
+ for (const match of source.matchAll(CSS_IMPORT_RE)) {
+ const spec = match[1];
+
+ const theme = THEME_SPEC_RE.exec(spec);
+ if (theme) {
+ const pkg = theme[1] ? 'grids' : 'webcomponents';
+ styles.push({
+ kind: 'link',
+ href: `${base}/ig-themes/${pkg}/${theme[2]}/${theme[3]}.css`,
+ pkg,
+ variant: theme[2],
+ theme: theme[3],
+ });
+ continue;
+ }
+
+ // Relative to the sample's src/ directory. Every sample-local stylesheet
+ // sits next to index.ts, so `./name.css` is the only relative shape used.
+ if (spec.startsWith('./')) {
+ styles.push({ kind: 'glob', key: `../../samples/${slug}/src/${spec.slice(2)}` });
+ continue;
+ }
+
+ console.warn(`[samples] ${slug}: unrecognised stylesheet import "${spec}" — not inlined`);
+ }
+
+ return styles;
+}