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,