diff --git a/change/@adaptive-web-adaptive-ui-4e314a96-e352-46f2-b2ab-cfd816afe5fe.json b/change/@adaptive-web-adaptive-ui-4e314a96-e352-46f2-b2ab-cfd816afe5fe.json new file mode 100644 index 0000000..4dbeb38 --- /dev/null +++ b/change/@adaptive-web-adaptive-ui-4e314a96-e352-46f2-b2ab-cfd816afe5fe.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "AUI: Add import capability to component anatomy definitions", + "packageName": "@adaptive-web/adaptive-ui", + "email": "47367562+bheston@users.noreply.github.com", + "dependentChangeType": "patch" +} diff --git a/package-lock.json b/package-lock.json index caad55a..1123336 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17978,6 +17978,14 @@ "node": ">=0.10.0" } }, + "node_modules/deepmerge-ts": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.3.tgz", + "integrity": "sha512-qCSH6I0INPxd9Y1VtAiLpnYvz5O//6rCfJXKk0z66Up9/VOSr+1yS8XSKA5IWRxjocFGlzPyaZYe+jxq7OOLtQ==", + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/default-browser-id": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-1.0.4.tgz", @@ -33059,6 +33067,7 @@ "@microsoft/fast-foundation": "3.0.0-alpha.31", "commander": "^12.0.0", "culori": "^3.2.0", + "deepmerge-ts": "^7.1.3", "glob": "^10.3.10", "matcher": "^5.0.0", "postcss": "^8.4.39", @@ -34857,6 +34866,7 @@ "chai": "^4.3.7", "commander": "^12.0.0", "culori": "^3.2.0", + "deepmerge-ts": "^7.1.3", "glob": "^10.3.10", "jsdom": "^16.2.2", "jsdom-global": "3.0.2", @@ -48926,6 +48936,11 @@ "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", "dev": true }, + "deepmerge-ts": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.3.tgz", + "integrity": "sha512-qCSH6I0INPxd9Y1VtAiLpnYvz5O//6rCfJXKk0z66Up9/VOSr+1yS8XSKA5IWRxjocFGlzPyaZYe+jxq7OOLtQ==" + }, "default-browser-id": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-1.0.4.tgz", diff --git a/packages/adaptive-ui/docs/api-report.md b/packages/adaptive-ui/docs/api-report.md index bc4114c..bf969b8 100644 --- a/packages/adaptive-ui/docs/api-report.md +++ b/packages/adaptive-ui/docs/api-report.md @@ -509,6 +509,12 @@ export interface SerializableAnatomy { styleRules: SerializableStyleRule[]; } +// @beta (undocumented) +export interface SerializableAnatomyWithImports extends SerializableAnatomy { + // (undocumented) + imports?: string[]; +} + // @beta (undocumented) export type SerializableBooleanCondition = string; diff --git a/packages/adaptive-ui/package.json b/packages/adaptive-ui/package.json index 0938c80..5091a1a 100644 --- a/packages/adaptive-ui/package.json +++ b/packages/adaptive-ui/package.json @@ -42,6 +42,7 @@ "@microsoft/fast-foundation": "3.0.0-alpha.31", "commander": "^12.0.0", "culori": "^3.2.0", + "deepmerge-ts": "^7.1.3", "glob": "^10.3.10", "matcher": "^5.0.0", "postcss": "^8.4.39", diff --git a/packages/adaptive-ui/src/bin/aui.ts b/packages/adaptive-ui/src/bin/aui.ts index 9f28571..bf82c01 100644 --- a/packages/adaptive-ui/src/bin/aui.ts +++ b/packages/adaptive-ui/src/bin/aui.ts @@ -8,6 +8,7 @@ import * as prettier from "prettier"; import { ComposableStyles, ElementStyles } from '@microsoft/fast-element'; import { CSSDesignToken } from "@microsoft/fast-foundation"; import { Command } from 'commander'; +import { deepmerge } from "deepmerge-ts"; import { glob } from "glob"; import postcss, { type Processor} from "postcss"; import postcssMergeLonghand from "postcss-merge-longhand"; @@ -19,6 +20,7 @@ import { ComponentConditions, ComponentParts, SerializableAnatomy, + SerializableAnatomyWithImports, SerializableBooleanCondition, SerializableStringCondition, SerializableStyleRule, @@ -84,11 +86,27 @@ program.command("compile-styles ") program.command("compile-json-anatomy ") .description("Compile a stylesheet from a JSON anatomy") - .action(async (path: string) => { - const data = (await fsp.readFile(path)).toString(); + .action(async (jsonPath: string) => { + const data = (await fsp.readFile(jsonPath)).toString(); await import("../reference/index.js"); - const jsonData = JSON.parse(data); + let jsonData = JSON.parse(data) as SerializableAnatomyWithImports; + + if (jsonData.imports) { + for (const imp of jsonData.imports) { + const impWithExt = imp.toLowerCase().endsWith(".json") ? imp : `${imp}.json`; + const impFilePath = path.format({ ...path.parse(path.join(path.parse(jsonPath).dir, impWithExt)) }); + const impData = (await fsp.readFile(impFilePath)).toString(); + const impJsonData = JSON.parse(impData); + // If `parts` are in the import, they are for validation/consistency of that file, but we want to use the parts + // list from the main anatomy definition. + // Consider extending this so imports can add their own known parts. + delete impJsonData.parts; + + jsonData = deepmerge(jsonData, impJsonData); + } + } + const compiler = new SheetCompilerImpl(); const sheet = jsonToAUIStyleSheet(jsonData); const compiledSheet = compiler.compile(sheet); diff --git a/packages/adaptive-ui/src/core/modules/types.ts b/packages/adaptive-ui/src/core/modules/types.ts index e89cc97..a995576 100644 --- a/packages/adaptive-ui/src/core/modules/types.ts +++ b/packages/adaptive-ui/src/core/modules/types.ts @@ -7,7 +7,7 @@ import { StyleRule } from "./styles.js"; * * @public */ -export type BooleanCondition = string; +export type BooleanCondition = string; /** * The state and selector for a multiple value condition. @@ -152,7 +152,7 @@ export const Interactivity = { * * For instance, a form control. */ - disabledAttribute: { + disabledAttribute: { interactive: ":not([disabled])", disabled: "[disabled]", } as InteractivityDefinition, @@ -162,7 +162,7 @@ export const Interactivity = { * * For instance, a form control. */ - disabledClass: { + disabledClass: { interactive: ":not(.disabled)", disabled: ".disabled", } as InteractivityDefinition, @@ -172,7 +172,7 @@ export const Interactivity = { * * For instance, an `` should style as plain text when it doesn't have an `href` attribute. */ - hrefAttribute: { + hrefAttribute: { interactive: "[href]", } as InteractivityDefinition, @@ -181,7 +181,7 @@ export const Interactivity = { * * For instance, cards or list items that are not able to be disabled. */ - always: { + always: { interactive: "", } as InteractivityDefinition, @@ -193,7 +193,7 @@ export const Interactivity = { * @remarks * This is an explicit value representing the default case. */ - never: { + never: { } as InteractivityDefinition, } as const; @@ -481,7 +481,7 @@ export type StyleRules = Array; /** * @beta */ -export type SerializableBooleanCondition = string; +export type SerializableBooleanCondition = string; /** * @beta @@ -506,7 +506,7 @@ export interface SerializableStyleRule { /** * @beta */ -export interface SerializableAnatomy{ +export interface SerializableAnatomy { name: string, context: string, conditions: Record, @@ -515,3 +515,10 @@ export interface SerializableAnatomy{ focus?: FocusDefinition, styleRules: SerializableStyleRule[] } + +/** + * @beta + */ +export interface SerializableAnatomyWithImports extends SerializableAnatomy { + imports?: string[] +}