diff --git a/apps/documentation/project.json b/apps/documentation/project.json
index 78fe860..488b276 100644
--- a/apps/documentation/project.json
+++ b/apps/documentation/project.json
@@ -11,7 +11,10 @@
"executor": "@analogjs/platform:vite",
"outputs": ["{options.outputPath}"],
"defaultConfiguration": "production",
- "syncGenerators": ["@mgremy/nx-source-plugin:component-metadata-generator"],
+ "syncGenerators": [
+ "@mgremy/nx-source-plugin:component-metadata-generator",
+ "@mgremy/nx-source-plugin:component-css-generator"
+ ],
"options": {
"configFile": "apps/documentation/vite.config.ts",
"main": "apps/documentation/src/main.ts",
diff --git a/apps/documentation/src/app/components/app-css-content.ts b/apps/documentation/src/app/components/app-css-content.ts
index 63c471e..dc47e35 100644
--- a/apps/documentation/src/app/components/app-css-content.ts
+++ b/apps/documentation/src/app/components/app-css-content.ts
@@ -1,32 +1,29 @@
import { NgClass } from '@angular/common';
-import {
- ChangeDetectionStrategy,
- Component,
- computed,
- effect,
- inject,
- input,
- signal,
- untracked,
-} from '@angular/core';
+import { ChangeDetectionStrategy, Component, effect, inject, input, signal } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { codeToHtml } from 'shiki';
+type ComponentGroup = {
+ name: string;
+ content: string;
+};
+
@Component({
selector: 'app-css-content',
imports: [NgClass],
template: `
- @if (availableTabs().size > 1) {
+ @if (selectedMetadata() && selectedMetadata()!.length > 1) {
- @for (name of availableTabs(); track name) {
+ @for (componentGroup of selectedMetadata(); track componentGroup.name) {
}
@@ -48,92 +45,87 @@ import { codeToHtml } from 'shiki';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppCssContent {
- private readonly _sanitizer = inject(DomSanitizer);
+ private readonly sanitizer = inject(DomSanitizer);
- private readonly styles = import.meta.glob
(
- '../../../../../packages/*/_theme/components/**/*.css',
+ private readonly metadatas = import.meta.glob(
+ '../../../../../tmp/packages/**/css/*.json',
{
import: 'default',
- query: '?raw',
- eager: true,
+ query: '?source',
+ eager: false,
}
);
readonly name = input.required();
- readonly path = input<'ng-primitives' | 'extended'>('ng-primitives');
+ readonly isLoading = signal(false);
readonly isOpen = signal(false);
- readonly selectedTab = signal('');
+ readonly selectedMetadata = signal(undefined);
+ readonly selectedStyle = signal('');
readonly style = signal('');
- readonly availableTabs = computed(() => {
- const currentName = this.name();
- if (!currentName) return new Set();
-
- const detectedTabs = new Set();
- const styleKeys = Object.keys(this.styles);
- const nameRegexPattern = new RegExp(`/${currentName}/([a-zA-Z0-9-]+)\\.css$`);
-
- for (const key of styleKeys) {
- // Add css entrypoint for the current component
- if (key.endsWith(`/${currentName}.css`)) {
- detectedTabs.add(currentName);
- continue;
- }
-
- const match = key.match(nameRegexPattern);
-
- if (match && match[1]) {
- detectedTabs.add(match[1]);
- }
- }
-
- return detectedTabs;
- });
-
constructor() {
- effect(() => {
- const currentName = this.name();
- if (!currentName) return;
+ effect(async () => {
+ const name = this.name();
+ if (!name) return;
- this.selectedTab.set(currentName);
+ await this.loadMetadata(name);
});
effect(async () => {
- const currentName = untracked(this.name);
- const selectedTab = this.selectedTab();
- if (!selectedTab) return;
+ const selectedMetadata = this.selectedMetadata();
+ const selectedStyle = this.selectedStyle();
+ if (!selectedMetadata || !selectedStyle) return;
- await this.loadStyle(currentName, selectedTab);
+ await this.generateStyle();
});
}
- private async loadStyle(name: string, tab: string): Promise {
- const styleKeys = Object.keys(this.styles);
- let nameRegexPattern = new RegExp(`/${name}/${tab}\\.css$`);
-
- if (name === tab) {
- nameRegexPattern = new RegExp(`/${name}\\.css$`);
+ private async loadMetadata(name: string): Promise {
+ this.isLoading.set(true);
+
+ const metadatas = Object.entries(this.metadatas);
+
+ for (const metadata of metadatas) {
+ if (metadata[0].endsWith(`${name}.json`)) {
+ await metadata[1]()
+ .then((x) => JSON.parse(x) as ComponentGroup[])
+ .then((x) =>
+ x.sort((y, z) =>
+ // If y.name === name, first
+ // Else If z.name === name, first
+ // Else compare string
+ y.name === name ? -1 : z.name === name ? 1 : y.name.localeCompare(z.name)
+ )
+ )
+ .then((x) => this.selectedMetadata.set(x))
+ .then(() => this.isLoading.set(false));
+
+ // Default set the selected style to the metadata file name (without extension)
+ this.selectedStyle.set(name);
+
+ return;
+ }
}
- for (const key of styleKeys) {
- const match = key.match(nameRegexPattern);
-
- if (match) {
- const selectedStyle = this.styles[key];
-
- await codeToHtml(selectedStyle.trim(), {
- lang: 'postcss',
- themes: {
- light: 'material-theme-lighter',
- dark: 'material-theme-darker',
- },
- })
- .then((x) => this._sanitizer.bypassSecurityTrustHtml(x))
- .then((x) => this.style.set(x));
+ this.isLoading.set(false);
+ }
- break;
- }
- }
+ private async generateStyle(): Promise {
+ const selectedStyle = this.selectedStyle();
+ const selectedMetadata = this.selectedMetadata();
+ const componentGroup = selectedMetadata?.find((x) => x.name === selectedStyle);
+
+ if (!componentGroup) return;
+
+ this.style.set(
+ await codeToHtml(componentGroup.content.trim(), {
+ lang: 'postcss',
+ themes: {
+ light: 'material-theme-lighter',
+ dark: 'material-theme-darker',
+ },
+ }).then(this.sanitizer.bypassSecurityTrustHtml)
+ );
}
}
diff --git a/apps/documentation/src/app/components/app-metadata.ts b/apps/documentation/src/app/components/app-metadata.ts
index fe7fd18..5640407 100644
--- a/apps/documentation/src/app/components/app-metadata.ts
+++ b/apps/documentation/src/app/components/app-metadata.ts
@@ -13,6 +13,10 @@ type ComponentGroup = {
type: 'component' | 'directive';
selector: string;
exportAs: string;
+ host: {
+ name: string;
+ value: string;
+ }[];
inputs: {
name: string;
type: string;
@@ -160,6 +164,25 @@ export class AppMetadata {
}
}
}
+
+ markdown.push('#### CSS classes');
+ if (!directive.host || directive.host.length === 0) {
+ markdown.push('-----');
+ } else {
+ markdown.push('| class | custom class |');
+ markdown.push('| --- | --- |');
+
+ for (const hostDefinition of directive.host) {
+ if (hostDefinition.name !== 'class') continue;
+
+ const value = hostDefinition.value.replaceAll("'", '');
+
+ const clazz = value.split(' ').find((x) => !x.includes('-c-'));
+ const customClazz = value.split(' ').find((x) => x.startsWith('mgnp-c-'));
+
+ markdown.push(`| ${clazz} | ${customClazz} |`);
+ }
+ }
}
this.markdown.set(await marked(markdown.join('\n')));
diff --git a/apps/documentation/src/content/documentation/ng-primitives/accordion.md b/apps/documentation/src/content/documentation/ng-primitives/accordion.md
index 618e2f7..253cb69 100644
--- a/apps/documentation/src/content/documentation/ng-primitives/accordion.md
+++ b/apps/documentation/src/content/documentation/ng-primitives/accordion.md
@@ -21,12 +21,3 @@ Display a series of panels that can be expanded or collapsed.
## Metadata
-
-## CSS
-
-| directive | class | custom class |
-| -------------------- | ---------------------- | ------------------------ |
-| MgnpAccordion | mgnp-accordion | mgnp-c-accordion |
-| MgnpAccordionContent | mgnp-accordion-content | mgnp-c-accordion-content |
-| MgnpAccordionItem | mgnp-accordion-item | mgnp-c-accordion-item |
-| MgnpAccordionTrigger | mgnp-accordion-trigger | mgnp-c-accordion-trigger |
diff --git a/apps/documentation/src/content/documentation/ng-primitives/breadcrumb.md b/apps/documentation/src/content/documentation/ng-primitives/breadcrumb.md
index 13a929b..ff4c8df 100644
--- a/apps/documentation/src/content/documentation/ng-primitives/breadcrumb.md
+++ b/apps/documentation/src/content/documentation/ng-primitives/breadcrumb.md
@@ -21,15 +21,3 @@ Help users understand their location within a hierarchy with a fully accessible
## Metadata
-
-## CSS
-
-| directive | class | custom class |
-| ----------------------- | ------------------------- | --------------------------- |
-| MgnpBreadcrumb | mgnp-breadcrumb | mgnp-c-breadcrumb |
-| MgnpBreadcrumbEllipsis | mgnp-breadcrumb-ellipsis | mgnp-c-breadcrumb-ellipsis |
-| MgnpBreadcrumbItem | mgnp-breadcrumb-item | mgnp-c-breadcrumb-item |
-| MgnpBreadcrumbLink | mgnp-breadcrumb-link | mgnp-c-breadcrumb-link |
-| MgnpBreadcrumbList | mgnp-breadcrumb-list | mgnp-c-breadcrumb-list |
-| MgnpBreadcrumbPage | mgnp-breadcrumb-page | mgnp-c-breadcrumb-page |
-| MgnpBreadcrumbSeparator | mgnp-breadcrumb-separator | mgnp-c-breadcrumb-separator |
diff --git a/apps/documentation/src/content/documentation/ng-primitives/button.md b/apps/documentation/src/content/documentation/ng-primitives/button.md
index 39b4c29..72801ce 100644
--- a/apps/documentation/src/content/documentation/ng-primitives/button.md
+++ b/apps/documentation/src/content/documentation/ng-primitives/button.md
@@ -23,9 +23,3 @@ focus.
## Metadata
-
-## CSS
-
-| directive | class | custom class |
-| ---------- | ----------- | ------------- |
-| MgnpButton | mgnp-button | mgnp-c-button |
diff --git a/apps/documentation/src/content/documentation/ng-primitives/checkbox.md b/apps/documentation/src/content/documentation/ng-primitives/checkbox.md
index fde1cb7..3a1d9b8 100644
--- a/apps/documentation/src/content/documentation/ng-primitives/checkbox.md
+++ b/apps/documentation/src/content/documentation/ng-primitives/checkbox.md
@@ -21,9 +21,3 @@ Perform state toggling.
## Metadata
-
-## CSS
-
-| directive | class | custom class |
-| ------------ | ------------- | --------------- |
-| MgnpCheckbox | mgnp-checkbox | mgnp-c-checkbox |
diff --git a/apps/documentation/src/content/documentation/ng-primitives/combobox.md b/apps/documentation/src/content/documentation/ng-primitives/combobox.md
index bde63d2..6d2551d 100644
--- a/apps/documentation/src/content/documentation/ng-primitives/combobox.md
+++ b/apps/documentation/src/content/documentation/ng-primitives/combobox.md
@@ -22,13 +22,3 @@ from a list of options while filtering the list based on their input.
## Metadata
-
-## CSS
-
-| directive | class | custom class |
-| -------------------- | ---------------------- | ------------------------ |
-| MgnpCombobox | mgnp-combobox | mgnp-c-combobox |
-| MgnpComboboxButton | mgnp-combobox-button | mgnp-c-combobox-button |
-| MgnpComboboxDropdown | mgnp-combobox-dropdown | mgnp-c-combobox-dropdown |
-| MgnpComboboxInput | mgnp-combobox-input | mgnp-c-combobox-input |
-| MgnpComboboxOption | mgnp-combobox-option | mgnp-c-combobox-option |
diff --git a/apps/documentation/src/content/documentation/ng-primitives/dialog.md b/apps/documentation/src/content/documentation/ng-primitives/dialog.md
index 712e590..a9a3556 100644
--- a/apps/documentation/src/content/documentation/ng-primitives/dialog.md
+++ b/apps/documentation/src/content/documentation/ng-primitives/dialog.md
@@ -21,12 +21,3 @@ A dialog is a floating window that can be used to display information or prompt
## Metadata
-
-## CSS
-
-| directive | class | custom class |
-| --------------------- | ----------------------- | ------------------------- |
-| MgnpDialog | mgnp-dialog | mgnp-c-dialog |
-| MgnpDialogTitle | mgnp-dialog-title | mgnp-c-dialog-title |
-| MgnpDialogDescription | mgnp-dialog-description | mgnp-c-dialog-description |
-| MgnpDialogOverlay | mgnp-dialog-overlay | mgnp-c-dialog-overlay |
diff --git a/apps/documentation/src/content/documentation/ng-primitives/extended/loader.md b/apps/documentation/src/content/documentation/ng-primitives/extended/loader.md
index 5cc47b0..daa56e0 100644
--- a/apps/documentation/src/content/documentation/ng-primitives/extended/loader.md
+++ b/apps/documentation/src/content/documentation/ng-primitives/extended/loader.md
@@ -20,9 +20,3 @@ Display a loader indicator
## Metadata
-
-## CSS
-
-| directive | class | custom class |
-| ---------- | ----------- | ------------- |
-| MgnpLoader | mgnp-loader | mgnp-c-loader |
diff --git a/apps/documentation/src/content/documentation/ng-primitives/extended/navbar.md b/apps/documentation/src/content/documentation/ng-primitives/extended/navbar.md
index e16ab06..4ff3190 100644
--- a/apps/documentation/src/content/documentation/ng-primitives/extended/navbar.md
+++ b/apps/documentation/src/content/documentation/ng-primitives/extended/navbar.md
@@ -20,9 +20,3 @@ Display a navbar
## Metadata
-
-## CSS
-
-| directive | class | custom class |
-| ---------- | ----------- | ------------- |
-| MgnpNavbar | mgnp-navbar | mgnp-c-navbar |
diff --git a/apps/documentation/src/content/documentation/ng-primitives/extended/table.md b/apps/documentation/src/content/documentation/ng-primitives/extended/table.md
index ae062e8..25a0aed 100644
--- a/apps/documentation/src/content/documentation/ng-primitives/extended/table.md
+++ b/apps/documentation/src/content/documentation/ng-primitives/extended/table.md
@@ -20,9 +20,3 @@ Display a pre-styled table
## Metadata
-
-## CSS
-
-| directive | class | custom class |
-| --------- | ---------- | ------------ |
-| MgnpTable | mgnp-table | mgnp-c-table |
diff --git a/apps/documentation/src/content/documentation/ng-primitives/form-field.md b/apps/documentation/src/content/documentation/ng-primitives/form-field.md
index 7453984..a402cbf 100644
--- a/apps/documentation/src/content/documentation/ng-primitives/form-field.md
+++ b/apps/documentation/src/content/documentation/ng-primitives/form-field.md
@@ -21,15 +21,3 @@ The Form Field primitive is used to create interactive forms, with state and err
## Metadata
-
-## CSS
-
-| directive | class | custom class |
-| ------------------- | ---------------------- | ------------------------ |
-| MgnpDescription | mgnp-description | mgnp-c-description |
-| MgnpError | mgnp-error | mgnp-c-error |
-| MgnpFormControl | mgnp-form-control | mgnp-c-form-control |
-| MgnpFormField | mgnp-form-field | mgnp-c-form-field |
-| MgnpInputGroup | mgnp-input-group | mgnp-c-input-group |
-| MgnpInputGroupAddon | mgnp-input-group-addon | mgnp-c-input-group-addon |
-| MgnpLabel | mgnp-label | mgnp-c-label |
diff --git a/apps/documentation/src/content/documentation/ng-primitives/input.md b/apps/documentation/src/content/documentation/ng-primitives/input.md
index 0a70a7e..4d987a6 100644
--- a/apps/documentation/src/content/documentation/ng-primitives/input.md
+++ b/apps/documentation/src/content/documentation/ng-primitives/input.md
@@ -22,9 +22,3 @@ consistent interaction handling for hover, focus, press and autofill states.
## Metadata
-
-## CSS
-
-| directive | class | custom class |
-| --------- | ---------- | ------------ |
-| MgnpInput | mgnp-input | mgnp-c-input |
diff --git a/apps/documentation/src/content/documentation/ng-primitives/menu.md b/apps/documentation/src/content/documentation/ng-primitives/menu.md
index 35af5fe..55741e1 100644
--- a/apps/documentation/src/content/documentation/ng-primitives/menu.md
+++ b/apps/documentation/src/content/documentation/ng-primitives/menu.md
@@ -21,16 +21,3 @@ A menu is a list of options or commands presented to the user in a dropdown list
## Metadata
-
-## CSS
-
-| directive | class | custom class |
-| ---------------------- | -------------------------- | ---------------------------- |
-| MgnpMenu | mgnp-menu | mgnp-c-menu |
-| MgnpMenuItem | mgnp-menu-item | mgnp-c-menu-item |
-| MgnpMenuItemCheckbox | mgnp-menu-item-checkbox | mgnp-c-menu-item-checkbox |
-| MgnpMenuItemIndicator | mgnp-menu-item-indicator | mgnp-c-menu-item-indicator |
-| MgnpMenuItemRadio | mgnp-menu-item-radio | mgnp-c-menu-item-radio |
-| MgnpMenuItemRadioGroup | mgnp-menu-item-radio-group | mgnp-c-menu-item-radio-group |
-| MgnpMenuTrigger | mgnp-menu-trigger | mgnp-c-menu-trigger |
-| MgnpMenuSubmenuTrigger | mgnp-menu-submenu-trigger | mgnp-c-menu-submenu-trigger |
diff --git a/apps/documentation/src/content/documentation/ng-primitives/pagination.md b/apps/documentation/src/content/documentation/ng-primitives/pagination.md
index ed8617b..60c3595 100644
--- a/apps/documentation/src/content/documentation/ng-primitives/pagination.md
+++ b/apps/documentation/src/content/documentation/ng-primitives/pagination.md
@@ -22,14 +22,3 @@ control is used to navigate through a set of data that is split into multiple pa
## Metadata
-
-## CSS
-
-| directive | class | custom class |
-| ---------------------- | ------------------------ | -------------------------- |
-| MgnpPagination | mgnp-pagination | mgnp-c-pagination |
-| MgnpPaginationButton | mgnp-pagination-button | mgnp-c-pagination-button |
-| MgnpPaginationFirst | mgnp-pagination-first | mgnp-c-pagination-first |
-| MgnpPaginationLast | mgnp-pagination-last | mgnp-c-pagination-last |
-| MgnpPaginationNext | mgnp-pagination-next | mgnp-c-pagination-next |
-| MgnpPaginationPrevious | mgnp-pagination-previous | mgnp-c-pagination-previous |
diff --git a/apps/documentation/src/content/documentation/ng-primitives/switch.md b/apps/documentation/src/content/documentation/ng-primitives/switch.md
index 3a3ff85..386b6fb 100644
--- a/apps/documentation/src/content/documentation/ng-primitives/switch.md
+++ b/apps/documentation/src/content/documentation/ng-primitives/switch.md
@@ -21,10 +21,3 @@ Perform state toggling.
## Metadata
-
-## CSS
-
-| directive | class | custom class |
-| --------------- | ----------------- | ------------------- |
-| MgnpSwitch | mgnp-switch | mgnp-c-switch |
-| MgnpSwitchThumb | mgnp-switch-thumb | mgnp-c-switch-thumb |
diff --git a/apps/documentation/src/content/documentation/ng-primitives/toast.md b/apps/documentation/src/content/documentation/ng-primitives/toast.md
index 219ca78..3f87909 100644
--- a/apps/documentation/src/content/documentation/ng-primitives/toast.md
+++ b/apps/documentation/src/content/documentation/ng-primitives/toast.md
@@ -22,9 +22,3 @@ the user.
## Metadata
-
-## CSS
-
-| directive | class | custom class |
-| --------- | ---------- | ------------ |
-| MgnpToast | mgnp-toast | mgnp-c-toast |
diff --git a/apps/documentation/src/content/documentation/ng-primitives/tooltip.md b/apps/documentation/src/content/documentation/ng-primitives/tooltip.md
index bf91f50..3bed83a 100644
--- a/apps/documentation/src/content/documentation/ng-primitives/tooltip.md
+++ b/apps/documentation/src/content/documentation/ng-primitives/tooltip.md
@@ -21,11 +21,3 @@ Display additional information on hover.
## Metadata
-
-## CSS
-
-| directive | class | custom class |
-| ------------------ | -------------------- | ---------------------- |
-| MgnpTooltip | mgnp-tooltip | mgnp-c-tooltip |
-| MgnpTooltipArrow | mgnp-tooltip-arrow | mgnp-c-tooltip-arrow |
-| MgnpTooltipTrigger | mgnp-tooltip-trigger | mgnp-c-tooltip-trigger |
diff --git a/tools/nx-source-plugin/generators.json b/tools/nx-source-plugin/generators.json
index 2cdb9d2..5aed04c 100644
--- a/tools/nx-source-plugin/generators.json
+++ b/tools/nx-source-plugin/generators.json
@@ -4,6 +4,11 @@
"factory": "./src/generators/component-metadata-generator/component-metadata-generator",
"schema": "./src/generators/component-metadata-generator/schema.json",
"description": "component-metadata-generator generator"
+ },
+ "component-css-generator": {
+ "factory": "./src/generators/component-css-generator/component-css-generator",
+ "schema": "./src/generators/component-css-generator/schema.json",
+ "description": "component-css-generator generator"
}
}
}
diff --git a/tools/nx-source-plugin/src/generators/component-css-generator/component-css-generator.ts b/tools/nx-source-plugin/src/generators/component-css-generator/component-css-generator.ts
new file mode 100644
index 0000000..23d8022
--- /dev/null
+++ b/tools/nx-source-plugin/src/generators/component-css-generator/component-css-generator.ts
@@ -0,0 +1,106 @@
+import type { ComponentCssGeneratorGeneratorSchema } from './schema';
+
+import { createProjectGraphAsync, joinPathFragments, type Tree } from '@nx/devkit';
+import { SyncGeneratorResult } from 'nx/src/utils/sync-generators';
+
+import { existsSync, readdirSync, readFileSync, statSync } from 'fs';
+import { extname, join, relative, sep } from 'path';
+
+/**
+ * Recursivly find all .css files in a directory
+ * @param dir Directory to scan
+ * @returns The list of CSS files path
+ */
+function findCssFiles(dir: string): string[] {
+ const files: string[] = [];
+
+ if (!existsSync(dir)) return files;
+
+ const entries = readdirSync(dir);
+
+ for (const entry of entries) {
+ const fullPath = join(dir, entry);
+ const stat = statSync(fullPath);
+
+ if (stat.isDirectory()) {
+ if (entry === 'node_modules' || entry.startsWith('.')) continue;
+ files.push(...findCssFiles(fullPath));
+ } else if (extname(fullPath) === '.css') {
+ files.push(fullPath);
+ }
+ }
+
+ return files;
+}
+
+export async function componentCssGeneratorGenerator(
+ tree: Tree,
+ options: ComponentCssGeneratorGeneratorSchema
+): Promise {
+ const projectGraph = await createProjectGraphAsync();
+ const ngPrimitives = projectGraph.nodes['ng-primitives'];
+ const ngPrimitivesExtended = projectGraph.nodes['ng-primitives-extended'];
+
+ if (!ngPrimitives || !ngPrimitivesExtended) {
+ console.warn('Could not fund ng-primitives or ng-primitives-extended in project graph');
+ return {
+ outOfSyncMessage: 'Could not fund ng-primitives or ng-primitives-extended in project graph',
+ };
+ }
+
+ const workspaceRoot = tree.root;
+ const projects = [
+ { name: 'ng-primitives', data: ngPrimitives.data },
+ { name: 'ng-primitives-extended', data: ngPrimitivesExtended.data },
+ ];
+
+ for (const project of projects) {
+ const projectRoot = join(workspaceRoot, project.data.root);
+ const cssFiles = findCssFiles(projectRoot);
+
+ const componentGroups = new Map();
+
+ for (const filePath of cssFiles) {
+ const relativePath = relative(projectRoot, filePath);
+ let componentGroup: string;
+
+ // If file is direct child of '_theme', then its group is its name
+ // Otherwise, its group is its parent
+ const filePathSplit = relativePath.split(sep);
+ if (['_theme', 'components'].includes(filePathSplit[filePathSplit.length - 2])) {
+ componentGroup = filePathSplit[filePathSplit.length - 1].replace('.css', '');
+ } else {
+ componentGroup = filePathSplit[filePathSplit.length - 2];
+ }
+
+ if (!componentGroups.has(componentGroup)) {
+ componentGroups.set(componentGroup, []);
+ }
+
+ componentGroups.get(componentGroup)?.push(filePath);
+ }
+
+ for (const [componentName, files] of componentGroups) {
+ const entries: { name: string; content: string }[] = [];
+
+ for (const file of files) {
+ const fileSplit = file.split(sep);
+ entries.push({
+ name: fileSplit[fileSplit.length - 1].replace('.css', ''),
+ content: readFileSync(file, 'utf-8'),
+ });
+ }
+
+ const outputPath = joinPathFragments(
+ 'tmp',
+ project.data.root,
+ 'css',
+ `${componentName}.json`
+ );
+
+ tree.write(outputPath, JSON.stringify(entries, null, 2));
+ }
+ }
+}
+
+export default componentCssGeneratorGenerator;
diff --git a/tools/nx-source-plugin/src/generators/component-css-generator/schema.d.ts b/tools/nx-source-plugin/src/generators/component-css-generator/schema.d.ts
new file mode 100644
index 0000000..007f6b5
--- /dev/null
+++ b/tools/nx-source-plugin/src/generators/component-css-generator/schema.d.ts
@@ -0,0 +1 @@
+export interface ComponentCssGeneratorGeneratorSchema {}
diff --git a/tools/nx-source-plugin/src/generators/component-css-generator/schema.json b/tools/nx-source-plugin/src/generators/component-css-generator/schema.json
new file mode 100644
index 0000000..9da433b
--- /dev/null
+++ b/tools/nx-source-plugin/src/generators/component-css-generator/schema.json
@@ -0,0 +1,8 @@
+{
+ "$schema": "https://json-schema.org/schema",
+ "$id": "ComponentCssGenerator",
+ "title": "",
+ "type": "object",
+ "properties": {},
+ "required": []
+}
diff --git a/tools/nx-source-plugin/src/generators/component-metadata-generator/component-metadata-generator.ts b/tools/nx-source-plugin/src/generators/component-metadata-generator/component-metadata-generator.ts
index 1b38194..4950764 100644
--- a/tools/nx-source-plugin/src/generators/component-metadata-generator/component-metadata-generator.ts
+++ b/tools/nx-source-plugin/src/generators/component-metadata-generator/component-metadata-generator.ts
@@ -33,6 +33,11 @@ interface ParsedOutput {
fromHostDirective?: string;
}
+interface HostEntry {
+ name: string;
+ value: string;
+}
+
interface HostDirectiveEntry {
directive: string;
inputs: string[];
@@ -44,6 +49,7 @@ interface ParsedDirective {
type: 'component' | 'directive';
selector: string;
exportAs: string;
+ host: HostEntry[];
inputs: ParsedInput[];
outputs: ParsedOutput[];
hostDirectives: HostDirectiveEntry[];
@@ -57,7 +63,7 @@ interface ComponentGroup {
}
/**
- * Recursivly finds all .ts files in a directory, exluding text and story files.
+ * Recursivly finds all .ts files in a directory, exluding test and story files.
* @param dir Directory to scan
* @returns The list of TS file path
*/
@@ -87,6 +93,42 @@ function findTsFiles(dir: string): string[] {
return files;
}
+/**
+ * Parse the `host` property of a `@Component`/`@Directive` decorator.
+ *
+ * Handle the shape:
+ * host: {
+ * class: 'mgnp-accordion mgnp-c-accordion',
+ * 'data-mgnp-accordion': '',
+ * '[attr.data-mgnp-accordion-color]': 'color()',
+ * }
+ * @param prop The host definition
+ * @returns The parsed host entries
+ */
+function parseHost(prop: PropertyAssignment): HostEntry[] {
+ const initializer = prop.getInitializer();
+
+ if (!initializer?.isKind(SyntaxKind.ObjectLiteralExpression)) return [];
+
+ const results: HostEntry[] = [];
+
+ for (const property of initializer.getProperties()) {
+ if (!property.isKind(SyntaxKind.PropertyAssignment)) continue;
+
+ const obj = property as PropertyAssignment;
+
+ const name = obj.getName();
+ const value = obj.getInitializer()?.getText() ?? '';
+
+ results.push({
+ name,
+ value,
+ });
+ }
+
+ return results;
+}
+
/**
* Parses the `hostDirectives` property of a `@Component`/`@Directive` decorator.
*
@@ -189,6 +231,9 @@ function parseFile(filePath: string): ParsedDirective[] {
const exportAsProp = decoratorObj.getProperty('exportAs') as PropertyAssignment;
const exportAs = exportAsProp?.getInitializer()?.getText().replace(/['"`]/g, '') ?? '';
+ const hostProp = decoratorObj.getProperty('host');
+ const host: HostEntry[] = hostProp ? parseHost(hostProp as PropertyAssignment) : [];
+
const hostDirectivesProp = decoratorObj.getProperty('hostDirectives');
const hostDirectives: HostDirectiveEntry[] = hostDirectivesProp
? parseHostDirectives(hostDirectivesProp as PropertyAssignment)
@@ -321,6 +366,7 @@ function parseFile(filePath: string): ParsedDirective[] {
type: decoratorType,
selector,
exportAs,
+ host,
inputs,
outputs,
hostDirectives,