From 6c665f89dd1de84139ff0b37e7caf5b4dd1496ba Mon Sep 17 00:00:00 2001 From: "sakarora@adobe.com" Date: Mon, 23 Feb 2026 18:25:45 +0530 Subject: [PATCH 1/2] Core component form theme to theme editor theme skill. --- .../skills/css-to-theme-content-xml/SKILL.md | 430 ++++++++++++++++++ .../component-selectors.md | 300 ++++++++++++ .../config-file-index.md | 104 +++++ scripts/css_to_theme_xml.py | 332 ++++++++++++++ 4 files changed, 1166 insertions(+) create mode 100644 .cursor/skills/css-to-theme-content-xml/SKILL.md create mode 100644 .cursor/skills/css-to-theme-content-xml/component-selectors.md create mode 100644 .cursor/skills/css-to-theme-content-xml/config-file-index.md create mode 100644 scripts/css_to_theme_xml.py diff --git a/.cursor/skills/css-to-theme-content-xml/SKILL.md b/.cursor/skills/css-to-theme-content-xml/SKILL.md new file mode 100644 index 0000000000..39aeb7d439 --- /dev/null +++ b/.cursor/skills/css-to-theme-content-xml/SKILL.md @@ -0,0 +1,430 @@ +--- +name: css-to-theme-content-xml +description: Reverse-engineer AEM Adaptive Forms _cq_styleConfig and _cq_themeConfig content.xml files from CSS. Use when the user provides a CSS file (or CSS selectors) and wants to generate the corresponding JCR content.xml for styleConfig or themeConfig, or when working with AEM Core Forms Components theming, styling, or theme-to-XML conversion. +--- + +# CSS to _cq_styleConfig / _cq_themeConfig Generator + +Generate AEM Adaptive Forms `_cq_styleConfig/.content.xml` and `_cq_themeConfig/.content.xml` from CSS. + +## Authoritative References + +The actual `_cq_styleConfig` and `_cq_themeConfig` files in this repository are the source of truth. **Always read the relevant config file before generating XML.** See [config-file-index.md](config-file-index.md) for the full index of all 35 config files organized by component and pattern type. + +See [component-selectors.md](component-selectors.md) for the complete CSS selector to XML node mapping. + +## Super Resource Type Inheritance + +Components use `sling:resourceSuperType` to inherit `_cq_styleConfig` from parent components. **When a component does not define its own `_cq_styleConfig`, it inherits from the nearest ancestor that does.** + +### Full Inheritance Hierarchy + +``` +base/v1/base (has _cq_styleConfig) +├── textinput/v1/textinput (has _cq_styleConfig — overrides base) +│ ├── telephoneinput/v1/telephoneinput (has _cq_styleConfig — overrides textinput) +│ └── emailinput/v1/emailinput (has _cq_styleConfig — overrides textinput) +├── numberinput/v1/numberinput (has _cq_styleConfig) +├── datepicker/v1/datepicker (has _cq_styleConfig) +├── datetime/v1/datetime (has _cq_styleConfig) +├── dropdown/v1/dropdown (has _cq_styleConfig) +├── checkbox/v1/checkbox (has _cq_styleConfig) +│ └── switch/v1/switch (has _cq_styleConfig — overrides checkbox entirely) +├── checkboxgroup/v1/checkboxgroup (has _cq_styleConfig) +│ ├── checkboxgroup/v2/checkboxgroup (NO own config — inherits v1) +│ └── toggleablelink/v1/toggleablelink (NO own config — inherits checkboxgroup) +├── radiobutton/v1/radiobutton (has _cq_styleConfig) +│ └── radiobutton/v2/radiobutton (NO own config — inherits v1) +├── button/v1/button (NO own config — inherits base) +│ └── button/v2/button (has _cq_styleConfig — overrides base chain) +│ ├── submit/v2/submit (has _cq_styleConfig) +│ └── reset/v2/reset (has _cq_styleConfig) +├── recaptcha/v1/recaptcha (has _cq_styleConfig) +│ ├── hcaptcha/v1/hcaptcha (has _cq_styleConfig — overrides recaptcha) +│ └── turnstile/v1/turnstile (NO own config — inherits recaptcha) +├── fileinput/v1/fileinput (NO own config — inherits base) +│ └── fileinput/v2 → v3 (has _cq_styleConfig) → v4 (has _cq_styleConfig) +├── image/v1/image (has _cq_styleConfig) +├── text/v1/text (has _cq_styleConfig) +└── scribble/v1/scribble (has _cq_styleConfig) + +panelcontainer/v1/panelcontainer (has _cq_styleConfig) [super: responsivegrid] +├── fragment/v1/fragment (NO own config — inherits panelcontainer) +└── termsandconditions/v1 (has _cq_styleConfig) + +container/v1/container [super: responsivegrid] +└── container/v2/container (has _cq_themeConfig — aggregator) + └── fragmentcontainer/v1 (NO own config — inherits container) + +accordion/v1/accordion (has _cq_styleConfig) [super: wcm accordion] +tabsontop/v1/tabsontop (has _cq_styleConfig) [super: wcm tabs] +verticaltabs/v1/verticaltabs (has _cq_styleConfig) [super: wcm panelcontainer] +wizard/v1/wizard [super: wcm panelcontainer] +└── wizard/v2/wizard (has _cq_styleConfig) + +title/v1/title [super: wcm title] +└── title/v2/title (has _cq_styleConfig) +``` + +### Config Resolution for CSS Selectors + +When a CSS selector targets a component, resolve the config file using this algorithm: + +1. **Extract component name** from CSS selector (e.g., `.cmp-adaptiveform-telephoneinput__widget` → `telephoneinput`) +2. **Check if the component has its own `_cq_styleConfig`** in the config-file-index +3. **If yes**: use that config as template +4. **If no**: walk the `sling:resourceSuperType` chain until finding a component with its own `_cq_styleConfig` +5. **Use the inherited config** but note that CSS selectors in the output should match the ACTUAL component, not the super type + +### Override Patterns + +Components that define their own `_cq_styleConfig` while having a super type follow one of these patterns: + +#### Pattern 1: Same Structure, Different Selectors +Components like `telephoneinput` and `emailinput` mirror their super type (`textinput`) node-for-node, but: +- Replace all CSS selectors with their own prefix (e.g., `.cmp-adaptiveform-telephoneinput__*` instead of `.cmp-adaptiveform-textinput__*`) +- Replace node names with component-specific names (e.g., `telephoneinputLabel` instead of `textinputLabel`) +- Replace IDs (e.g., `af2_telephoneinputlabel` instead of `af2_textinputlabel`) +- Use `sling:hideResource="{Boolean}true"` to hide inherited nodes that don't apply (e.g., telephoneinput hides `textAreaWidgetAndText`) + +#### Pattern 2: Completely Different Structure +Components like `switch` (super: `checkbox`) define an entirely different node hierarchy. Switch has `switchContainer`, `switchOption`, `switchWidget`, `switchHandle`, `switchSlider` — none of which exist in checkbox. + +#### Pattern 3: Version Upgrade +Components like `button/v2` (super: `button/v1`) add or modify nodes from the previous version. + +### sling:hideResource + +When a component inherits from a super type but needs to hide certain inherited nodes, use: +```xml + +``` +This prevents the inherited node from appearing in the resolved config. Common usage: +- `telephoneinput` and `emailinput` both hide `textAreaWidgetAndText` inherited from `textinput` + +### secondarySelectors and themeConfig Resolution + +The `secondarySelectors` attribute on a node (e.g., `secondarySelectors="af2_guideContainer:af2_widgetAndText"`) maps component-specific nodes to generic selectors from the `_cq_themeConfig`. This enables the theme editor to apply styles from the generic widget node to the component-specific widget node. + +The `_cq_themeConfig` `target` references always point to the **component that owns the `_cq_styleConfig`**, resolved via `/mnt/override/libs/` path. For components that inherit (like `turnstile` from `recaptcha`), the themeConfig references `recaptcha` directly — there is no separate themeConfig entry for `turnstile`. + +--- + +## Two Config Types + +### _cq_styleConfig (per-component) + +Defines the structural hierarchy of styleable elements for a single component: which CSS selectors map to which parts, their nesting, and available states. One per component, 32 files total. + +### _cq_themeConfig (aggregator) + +Lives on container/header/footer. Aggregates multiple component styleConfigs via `target` references. 3 files total. Uses `componentId` and `label` instead of `jcr:title` on root. + +## Workflow + +### Step 1: Parse CSS + +For each CSS rule, extract: +- **Selector**: e.g., `.cmp-adaptiveform-textinput__widget` +- **Pseudo-class/data-attribute state**: `:focus`, `:hover`, `[data-cmp-valid='false']`, etc. +- **Media query breakpoint**: `@media (max-width: 768px)` → phone +- **All CSS property-value pairs** + +### Step 2: Identify Components from Selectors + +| Selector Pattern | Component Type | Config File to Read | Super Type | +|-----------------|----------------|-------------------|------------| +| `.cmp-adaptiveform-textinput*` | Text Input | `textinput/v1/textinput/_cq_styleConfig` | `base/v1/base` | +| `.cmp-adaptiveform-numberinput*` | Number Input | `numberinput/v1/numberinput/_cq_styleConfig` | `base/v1/base` | +| `.cmp-adaptiveform-emailinput*` | Email Input | `emailinput/v1/emailinput/_cq_styleConfig` | `textinput/v1/textinput` | +| `.cmp-adaptiveform-telephoneinput*` | Telephone Input | `telephoneinput/v1/telephoneinput/_cq_styleConfig` | `textinput/v1/textinput` | +| `.cmp-adaptiveform-datepicker*` | Date Picker | `datepicker/v1/datepicker/_cq_styleConfig` | `base/v1/base` | +| `.cmp-adaptiveform-datetime*` | DateTime | `datetime/v1/datetime/_cq_styleConfig` | `base/v1/base` | +| `.cmp-adaptiveform-dropdown*` | Dropdown | `dropdown/v1/dropdown/_cq_styleConfig` | `base/v1/base` | +| `.cmp-adaptiveform-button*` | Button | `button/v2/button/_cq_styleConfig` | `button/v1/button` → `base/v1/base` | +| `.cmp-adaptiveform-checkbox*` | Checkbox | `checkbox/v1/checkbox/_cq_styleConfig` | `base/v1/base` | +| `.cmp-adaptiveform-checkboxgroup*` | CheckboxGroup | `checkboxgroup/v1/checkboxgroup/_cq_styleConfig` | `base/v1/base` | +| `.cmp-adaptiveform-radiobutton*` | Radio Button | `radiobutton/v1/radiobutton/_cq_styleConfig` | `base/v1/base` | +| `.cmp-adaptiveform-switch*` | Switch | `switch/v1/switch/_cq_styleConfig` | `checkbox/v1/checkbox` | +| `.cmp-adaptiveform-container` | Form Container | `container/v2/container/_cq_themeConfig` | `container/v1/container` | +| `.cmp-container*` | Panel | `panelcontainer/v1/panelcontainer/_cq_styleConfig` | `responsivegrid` | +| `.cmp-accordion*` | Accordion | `accordion/v1/accordion/_cq_styleConfig` | `wcm accordion` | +| `.cmp-tabs*` | Tabs on Top | `tabsontop/v1/tabsontop/_cq_styleConfig` | `wcm tabs` | +| `.cmp-verticaltabs*` | Vertical Tabs | `verticaltabs/v1/verticaltabs/_cq_styleConfig` | `wcm panelcontainer` | +| `.cmp-adaptiveform-wizard*` | Wizard | `wizard/v2/wizard/_cq_styleConfig` | `wizard/v1/wizard` | +| `.cmp-image*` | Image | `image/v1/image/_cq_styleConfig` | `base/v1/base` | +| `.cmp-title*` | Title | `title/v2/title/_cq_styleConfig` | `title/v1/title` → `wcm title` | +| `.cmp-adaptiveform-text*` | Text | `text/v1/text/_cq_styleConfig` | `base/v1/base` | +| `.cmp-adaptiveform-fileinput*` | File Input | `fileinput/v3/fileinput/_cq_styleConfig` | `fileinput/v2` → `v1` → `base` | +| `.cmp-adaptiveform-termsandconditions*` | T&C | `termsandconditions/v1/termsandconditions/_cq_styleConfig` | `responsivegrid` | +| `.cmp-adaptiveform-scribble*` | Scribble | `scribble/v1/scribble/_cq_styleConfig` | `base/v1/base` | +| `.cmp-adaptiveform-recaptcha*` | reCAPTCHA | `recaptcha/v1/recaptcha/_cq_styleConfig` | `base/v1/base` | +| `.cmp-adaptiveform-hcaptcha*` | hCaptcha | `hcaptcha/v1/hcaptcha/_cq_styleConfig` | `recaptcha/v1/recaptcha` | + +**Components that inherit config via super type (no own `_cq_styleConfig`):** + +| Component | Inherits Config From | Super Type | +|-----------|---------------------|------------| +| `turnstile/v1` | `recaptcha/v1/recaptcha/_cq_styleConfig` | `recaptcha/v1/recaptcha` | +| `toggleablelink/v1` | `checkboxgroup/v1/checkboxgroup/_cq_styleConfig` | `checkboxgroup/v1/checkboxgroup` | +| `fragment/v1` | `panelcontainer/v1/panelcontainer/_cq_styleConfig` | `panelcontainer/v1/panelcontainer` | +| `fragmentcontainer/v1` | `container/v2/container/_cq_themeConfig` | `container/v2/container` | +| `checkboxgroup/v2` | `checkboxgroup/v1/checkboxgroup/_cq_styleConfig` | `checkboxgroup/v1/checkboxgroup` | +| `radiobutton/v2` | `radiobutton/v1/radiobutton/_cq_styleConfig` | `radiobutton/v1/radiobutton` | +| `submit/v1` | `button/v1/button` (→ `base/v1/base/_cq_styleConfig`) | `button/v1/button` | +| `reset/v1` | `button/v1/button` (→ `base/v1/base/_cq_styleConfig`) | `button/v1/button` | + +All paths relative to `ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/`. + +### Step 3: Read the Matching Config File + +**Read the actual file** from the repository. Use it as the authoritative template for: +- XML node hierarchy and nesting +- Node names and attribute patterns +- Which `id`, `cssSelector`, `longTitle`, `propertySheet`, `secondarySelectors` values to use +- Which states each element supports +- Namespace declarations + +### Step 4: Generate XML + +Copy the structure from the template config file. For each CSS rule: + +1. **Find the node** whose `cssSelector` matches the CSS selector +2. **Embed CSS property values** on that node as a bracket-encoded attribute: + - Attribute name: `{breakpoint}_x0023_{state}` where `_x0023_` encodes `#` + - Attribute value: `[prop1:val1,prop2:val2,...]` +3. **Add state nodes** if the CSS has pseudo-classes not already in the template +4. **Preserve all structural attributes** (`jcr:primaryType`, `id`, `cssSelector`, `propertySheet`, etc.) + +### Breakpoint and State Encoding + +`_x0023_` is JCR encoding for `#`. Combine breakpoint + state: + +| CSS context | Attribute name | +|------------|---------------| +| No media query, no pseudo-class | `default_x0023_default` | +| No media query, `:focus` | `default_x0023_focus` | +| No media query, `:hover` | `default_x0023_hover` | +| No media query, `:active` | `default_x0023_active` | +| No media query, `:disabled` | `default_x0023_disabled` | +| No media query, `:checked` | `default_x0023_checked` | +| No media query, `[data-cmp-valid='false']` | `default_x0023_error` | +| No media query, `[data-cmp-valid='true']` | `default_x0023_success` | +| `@media` phone breakpoint, no pseudo-class | `phone_x0023_default` | +| `@media` tablet breakpoint, no pseudo-class | `tablet_x0023_default` | + +### Bracket Value Format + +``` +[prop1:val1,prop2:val2,prop3:val3] +``` + +Rules: +- No spaces around colons or commas (except within values) +- Commas inside values escaped with `\,` — e.g. `rgba(0\,0\,0\,0.5)` +- CSS shorthands must be expanded to individual properties (e.g. `padding: 1px` → `padding-right:1px,padding-bottom:1px,padding-left:1px,padding-top:1px`) +- Special properties: `cssOverride` (raw CSS), `addonCss` (JSON with `"` and `\,`), `beforePseudoElement`, `afterPseudoElement` + +### UI Metadata Companion + +For every `{breakpoint}_x0023_{state}` attribute, also generate a `{breakpoint}_x0023_{state}_x0023_ui` companion that stores editor metadata. Common UI properties: + +- `marginLock` / `paddingLock` / `borderWidthLock` / `borderRadiusLock`: `link` if all sides equal, `linkOff` if sides differ +- `marginPopover` / `paddingPopover` / `borderWidthPopover` / `borderRadiusPopover`: shorthand display values, `—` for unchanged sides +- `backgroundColor`: color value for the color picker +- Use `Set` as placeholder for unset popover values + +### Example + +CSS input: +```css +.cmp-adaptiveform-textinput__widget { + padding-right: 1px; + padding-bottom: 1px; + padding-left: 1px; + padding-top: 1px; + margin-right: 2px; + margin-bottom: 2px; + margin-left: 2px; + margin-top: 2px; + height: 30px; + width: 50% +} +``` + +Find node with `cssSelector=".cmp-adaptiveform-textinput__widget"` in textinput _cq_styleConfig → `textinputWidgetAndText`. Add bracket attributes: + +```xml + +``` + +--- + +## XML Structure Rules + +### Namespace Declarations + +Always include on `` (copy from the template file): + +```xml +xmlns:sling="http://sling.apache.org/jcr/sling/1.0" +xmlns:granite="http://www.adobe.com/jcr/granite/1.0" +xmlns:cq="http://www.day.com/jcr/cq/1.0" +xmlns:jcr="http://www.jcp.org/jcr/1.0" +xmlns:nt="http://www.jcp.org/jcr/nt/1.0" +``` + +### Required Attributes on Styleable Nodes + +| Attribute | Value | Required | +|-----------|-------|----------| +| `jcr:primaryType` | `"nt:unstructured"` | Always | +| `jcr:title` | Short display title | Always | +| `id` | `"af2_{componentpart}"` (lowercase, no separators) | Always | +| `cssSelector` | The CSS class selector | Always | +| `longTitle` | Extended display title | Always | +| `propertySheet` | `"/mnt/overlay/fd/af/components/stylePropertySheet/common"` | Always | +| `secondarySelectors` | `"af2_guideContainer:af2_{inheritId}"` | Only for widget/help-icon inheritance | + +### Containers + +- Children wrapped in `` +- States wrapped in `` + +### ID Convention + +`af2_` prefix + lowercase component part, no separators: +- `af2_textbox`, `af2_textboxwidgetandtext`, `af2_textinputlabel` +- `af2_button`, `af2_buttontext`, `af2_buttonhelpicon` +- `af2_panel`, `af2_accordionpanel`, `af2_accordionpanelheader` + +--- + +## State Mapping + +| CSS Pattern | State Node Name | `jcr:title` | Outer wrapper selector | Inner element selector | +|-------------|----------------|-------------|----------------------|----------------------| +| `:focus` | `focus` | Focus | `.cmp-...{comp}:focus` | `.cmp-...{el}:focus` | +| `:hover` | `hover` | Hover | `.cmp-...{comp}:hover` | `.cmp-...{el}:hover` | +| `:disabled` | `disabled` | Disabled | `[data-cmp-enabled='false']` | `:disabled` | +| `:active` | `active` | Down / Active | `:active` | `:active` | +| `:required` | `mandatory` | Mandatory | `[data-cmp-required='true']` | `:required` | +| `:checked` | `checked` | Selected | — | `:checked` | +| `[data-cmp-valid='false']` | `error` | Error | `[data-cmp-valid='false']` | — | +| `[data-cmp-valid='true']` | `success` | Success | `[data-cmp-valid='true']` | — | + +**Outer wrapper** = the main `.cmp-adaptiveform-{component}` element (uses data-attribute selectors for disabled/mandatory). +**Inner element** = sub-elements like `__widget`, `__label` (uses pseudo-class selectors). + +--- + +## themeConfig Target References + +The container `_cq_themeConfig` aggregates component styleConfigs via `target`: + +```xml + +``` + +Target path: `/mnt/override/libs/core/fd/components/form/{component}/{version}/{component}/cq:styleConfig/items/{itemName}` + +Read `container/v2/container/_cq_themeConfig/.content.xml` for the complete set of existing target references. + +--- + +## File Placement + +``` +ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/ +├── {component}/{version}/{component}/ +│ ├── _cq_styleConfig/ +│ │ └── .content.xml ← per-component style config +│ └── _cq_themeConfig/ +│ └── .content.xml ← only on container, header, footer +``` + +--- + +## theme.structure Output Format (Single Unified content.xml) + +When the user asks for a **single unified content.xml** (e.g., "generate content.xml", "theme.structure format", "one file for all components"), use the `theme.structure` format instead of per-component `_cq_styleConfig` files. + +### Structure + +```xml + + + + + + + + + + + + + + +``` + +### Rules for theme.structure Output + +1. **Root**: `jcr:primaryType="sling:Folder"`, child `jcr:content` with `rendition.handler.id="theme.structure"` +2. **Container**: `af2_guideContainer` with `component="core/fd/components/form/container/v2/container"` +3. **Node names**: Use the **id** from the matching config node (e.g., `af2_numberinputwidgetandtext`, `af2_form`, `af2_textinputwidgetandtext`) as the XML element name +4. **Selector → ID mapping**: Resolve each CSS selector to its config node, then use that node's `id` as the child element name under `af2_guideContainer` +5. **assetLibrary**: Include empty `` at end of `jcr:content` + +### Color Conversion for theme.structure + +Hex colors in bracket values must be converted to `rgb(r,g,b)` with commas escaped: + +| CSS | Bracket value | +|-----|---------------| +| `#ec7f7f` | `rgb(236\,127\,127)` | +| `#7f7fe1` | `rgb(127\,127\,225)` | +| `#ffffff` | `rgb(255\,255\,255)` | + +Conversion: `#RRGGBB` → `rgb(R,G,B)` where R,G,B are decimal (0–255). Commas inside `rgb()` escaped as `\,`. + +### UI Metadata for theme.structure (Simplified) + +Use popover values only when applicable; omit Lock values if not needed: +- `backgroundColor`: color value (use rgb format for hex) +- `borderWidthPopover`, `borderRadiusPopover`: when border props present +- `paddingPopover`, `marginPopover`: when padding/margin present + +--- + +## Validation Checklist + +- [ ] Read the actual matching config file from the repository before generating +- [ ] If component has no own `_cq_styleConfig`, resolve via `sling:resourceSuperType` chain +- [ ] If component overrides super type, check for `sling:hideResource` nodes that hide inherited elements +- [ ] Every node has `jcr:primaryType="nt:unstructured"` +- [ ] Every styleable node has `id`, `cssSelector`, `longTitle`, `propertySheet` +- [ ] States use correct selector suffix (pseudo-class for inner, data-attr for outer) +- [ ] `` wrapper around all child collections +- [ ] `` wrapper around all state nodes +- [ ] IDs use `af2_` prefix and are unique within the file +- [ ] XML well-formed with proper encoding declaration and namespace declarations +- [ ] Structure matches the pattern from the actual template file diff --git a/.cursor/skills/css-to-theme-content-xml/component-selectors.md b/.cursor/skills/css-to-theme-content-xml/component-selectors.md new file mode 100644 index 0000000000..95c702f096 --- /dev/null +++ b/.cursor/skills/css-to-theme-content-xml/component-selectors.md @@ -0,0 +1,300 @@ +# Component Selector Reference + +Complete mapping of CSS selectors to _cq_styleConfig XML nodes for all AEM Core Forms Components. + +## Table of Contents + +- [Input Fields (Pattern A)](#input-fields-pattern-a) +- [Button (Pattern B)](#button-pattern-b) +- [Checkbox / Radio / CheckboxGroup (Pattern C)](#checkbox--radio--checkboxgroup-pattern-c) +- [Switch](#switch) +- [Panel Types (Pattern D)](#panel-types-pattern-d) +- [Simple Components (Pattern E)](#simple-components-pattern-e) +- [Container / ThemeConfig](#container--themeconfig) +- [Common Sub-Element Suffixes](#common-sub-element-suffixes) + +--- + +## Input Fields (Pattern A) + +Components: `textinput`, `numberinput`, `emailinput`, `telephoneinput`, `datepicker`, `datetime`, `dropdown` + +All follow the same structure. Replace `{comp}` with the component name. + +**Inheritance notes:** +- `emailinput` and `telephoneinput` extend `textinput` via `sling:resourceSuperType` — they mirror textinput's structure but use their own CSS selectors and hide `textAreaWidgetAndText` with `sling:hideResource="{Boolean}true"` +- `numberinput`, `datepicker`, `datetime`, `dropdown` extend `base/v1/base` directly — each defines its own `_cq_styleConfig` + +| XML Node | cssSelector | id pattern | secondarySelectors | +|----------|-------------|------------|-------------------| +| widgetAndText (root wrapper) | `.cmp-adaptiveform-{comp}` | `af2_{comp}widgetandtext` | `af2_guideContainer:af2_widgetAndText` | +| labelContainer | `.cmp-adaptiveform-{comp}__label-container` | `af2_{comp}labelcontainer` | — | +| label | `.cmp-adaptiveform-{comp}__label` | `af2_{comp}label` | — | +| helpIcon | `.cmp-adaptiveform-{comp}__questionmark` | `af2_{comp}helpicon` | `af2_guideContainer:af2_helpicon` | +| widget | `.cmp-adaptiveform-{comp}__widget` | `af2_{comp}widgetandtext` | `af2_guideContainer:af2_widgetAndText` | +| shortDescription | `.cmp-adaptiveform-{comp}__shortdescription` | `af2_{comp}descriptionshort` | — | +| longDescription | `.cmp-adaptiveform-{comp}__longdescription` | `af2_{comp}descriptionlong` | — | +| errorMessage | `.cmp-adaptiveform-{comp}__errormessage` | `af2_{comp}errormessage` | — | + +### Outer states (on widgetAndText): +- focus: `.cmp-adaptiveform-{comp}:focus` +- disabled: `.cmp-adaptiveform-{comp}[data-cmp-enabled='false']` +- hover: `.cmp-adaptiveform-{comp}:hover` +- mandatory: `.cmp-adaptiveform-{comp}[data-cmp-required='true']` +- error: `.cmp-adaptiveform-{comp}[data-cmp-valid='false']` +- success: `.cmp-adaptiveform-{comp}[data-cmp-valid='true']` + +### Inner widget states: +- focus: `.cmp-adaptiveform-{comp}__widget:focus` +- hover: `.cmp-adaptiveform-{comp}__widget:hover` +- disabled: `.cmp-adaptiveform-{comp}__widget:disabled` +- mandatory: `.cmp-adaptiveform-{comp}__widget:required` + +### Specific component names: + +| Component | {comp} value | Root id | jcr:title | +|-----------|-------------|---------|-----------| +| Text Input | `textinput` | `af2_textbox` | `Base` | +| Number Input | `numberinput` | `af2_numericbox` | `Base` | +| Email Input | `emailinput` | `af2_emailinput` | `Base` | +| Telephone Input | `telephoneinput` | `af2_telephoneinput` | `Base` | +| Date Picker | `datepicker` | `af2_datepicker` | `Base` | +| DateTime | `datetime` | `af2_datetime` | `Base` | +| Dropdown | `dropdown` | `af2_dropdownlist` | `Base` | + +### Text Input special: textarea variant + +Text Input has an additional `textAreaWidgetAndText` node: + +| XML Node | cssSelector | id | +|----------|-------------|-----| +| textAreaWidgetAndText | `textarea.cmp-adaptiveform-textinput__widget` | `af2_textareawidgetandtext` | + +With states: focus, disabled, hover, mandatory (all on `textarea.cmp-adaptiveform-textinput__widget:{state}`). + +--- + +## Button (Pattern B) + +Path: `button/v2/button/_cq_styleConfig` + +| XML Node | cssSelector | id | +|----------|-------------|-----| +| button (root) | `.cmp-adaptiveform-button__widget` | `af2_button` | +| buttonText | `.cmp-adaptiveform-button__text` | `af2_buttontext` | +| buttonDescriptionShort | `.cmp-adaptiveform-button__shortdescription` | `af2_buttondescriptionshort` | +| buttonDescriptionLong | `.cmp-adaptiveform-button__longdescription` | `af2_buttondescriptionlong` | +| buttonHelpContainer | `.cmp-adaptiveform-button__help-container` | `af2_buttonhelpcontainer` | +| buttonHelpIcon | `.cmp-adaptiveform-button__questionmark` | `af2_buttonhelpicon` | + +Root states: focus, active (Down), disabled, hover, error, success. +Button text states: focus, active (Down), disabled, hover. + +Submit and Reset buttons reuse the same pattern at: +- `actions/submit/v2/submit/_cq_styleConfig` (super: `button/v2/button`) +- `actions/reset/v2/reset/_cq_styleConfig` (super: `button/v2/button`) +- v1 submit/reset have no own config — they inherit from `button/v1/button` → `base/v1/base` + +--- + +## Checkbox / Radio / CheckboxGroup (Pattern C) + +### Checkbox (`checkbox/v1/checkbox`) + +| XML Node | cssSelector | id | +|----------|-------------|-----| +| widgetAndText | `.cmp-adaptiveform-checkbox` | `af2_checkboxfieldwidgetandtext` | +| checkBoxItem | `.cmp-adaptiveform-checkbox__widget-container` | `af2_checkboxitem` | +| checkBoxLabel | `.cmp-adaptiveform-checkbox__label` | `af2_checkboxlabel` | +| checkBoxWidgetAndText | `.cmp-adaptiveform-checkbox__widget` | `af2_checkboxwidgetandtext` | +| checkBoxDescriptionShort | `.cmp-adaptiveform-checkbox__shortdescription` | `af2_checkboxdescriptionshort` | +| checkBoxDescriptionLong | `.cmp-adaptiveform-checkbox__longdescription` | `af2_checkboxdescriptionlong` | +| checkBoxHelpContainer | `.cmp-adaptiveform-checkbox__help-container` | `af2_checkboxhelpcontainer` | +| checkBoxHelpIcon | `.cmp-adaptiveform-checkbox__questionmark` | `af2_checkboxhelpicon` | +| checkBoxErrorMessage | `.cmp-adaptiveform-checkbox__errormessage` | `af2_checkboxerrormessage` | + +Widget states include `checked` (`:checked`). +Outer states: hover, focus, disabled (`[data-cmp-enabled='false']`), error, success. + +### CheckboxGroup (`checkboxgroup/v1/checkboxgroup`) + +Same pattern as Checkbox, replace `checkbox` with `checkboxgroup` in selectors and ids. +- `checkboxgroup/v2` has no own config — inherits from `checkboxgroup/v1` +- `toggleablelink/v1` also inherits from `checkboxgroup/v1` (no own config) + +### RadioButton (`radiobutton/v1/radiobutton`) + +Same pattern as Checkbox, replace `checkbox` with `radiobutton` in selectors and ids. +- `radiobutton/v2` has no own config — inherits from `radiobutton/v1` + +--- + +## Switch + +Path: `switch/v1/switch/_cq_styleConfig` (super: `checkbox/v1/checkbox` — but **completely different structure**) + +Unique elements beyond the standard pattern: + +| XML Node | cssSelector | id | +|----------|-------------|-----| +| widgetAndText (root) | `.cmp-adaptiveform-switch` | `af2_switchwidgetandtext` | +| switchContainer | `.cmp-adaptiveform-switch__container` | `af2_switchwidgetcontainer` | +| switchOption | `.cmp-adaptiveform-switch__option` | `af2_switchoption` | +| switchWidget | `.adaptiveform-switch__widget-label` | `af2_switchwidget` | +| switchHandle | `.cmp-adaptiveform-switch__circle-indicator` | `af2_switchhandle` | +| switchSlider | `.cmp-adaptiveform-switch__widget-slider` | `af2_switchonlabel` | + +Switch handle and slider use adjacent sibling selectors for states: +- `.cmp-adaptiveform-switch__widget:focus + .cmp-adaptiveform-switch__widget-slider .cmp-adaptiveform-switch__circle-indicator` +- `.cmp-adaptiveform-switch__widget:checked + .cmp-adaptiveform-switch__widget-slider` + +--- + +## Panel Types (Pattern D) + +### Responsive Panel (`panelcontainer/v1/panelcontainer`) + +| XML Node | cssSelector | id | +|----------|-------------|-----| +| responsivePanel | `.cmp-container` | `af2_panel` | +| panelLabelContainer | `.cmp-container__label-container` | `af2_panellabelcontainer` | +| panelLabel | `.cmp-container__label` | `af2_panellabel` | +| panelHelpIcon | `.cmp-container__questionmark` | `af2_panelhelpicon` | +| panelDescriptionShort | `.cmp-container__shortdescription` | `af2_paneldescriptionshort` | +| panelDescriptionLong | `.cmp-container__longdescription` | `af2_paneldescriptionlong` | + +### Accordion (`accordion/v1/accordion`) + +Root selector: `.cmp-accordion` (id: `af2_accordionpanel`) + +Additional accordion-specific items: + +| XML Node | cssSelector | id | +|----------|-------------|-----| +| accordionPanelItem | `.cmp-accordion__item` | `af2_accordionpanelitem` | +| accordionHeader | `.cmp-accordion__header` | `af2_accordionpanelheader` | +| accordionHeaderButton | `.cmp-accordion__button` | `af2_accordionheaderbutton` | +| accordionHeaderTitle | `.cmp-accordion__title` | `af2_accordionheadertitle` | +| accordionHeaderIcon | `.cmp-accordion__icon` | `af2_accordionheadericon` | +| accordionPanelWidget | `.cmp-accordion__panel` | `af2_accordionpanelwidget` | + +Panel item states use `:active` and `:hover` (not `:focus`/`:disabled`). + +### Tabs On Top (`tabsontop/v1/tabsontop`) + +Root selector: `.cmp-tabs` (id: `af2_tabsontoppanel`) + +### Vertical Tabs (`verticaltabs/v1/verticaltabs`) + +Root selector: `.cmp-verticaltabs` (id: `af2_tabsonleftpanel`) + +### Wizard (`wizard/v2/wizard`) + +Root selector: `.cmp-adaptiveform-wizard` (id: `af2_wizardpanel`) + +--- + +## Simple Components (Pattern E) + +### Image (`image/v1/image`) + +```xml + +``` + +### Title (`title/v2/title`) + +Root selector: `.cmp-title` or component-specific selector. + +### Text (`text/v1/text`) + +Root selector: `.cmp-adaptiveform-text` or `.cmp-text`. + +--- + +## Container / ThemeConfig + +### Container _cq_themeConfig (`container/v2/container`) + +The container themeConfig aggregates ALL component styleConfigs. It uses: +- `componentId="af2_guideContainer"` on the root +- `label="CC Container"` on the root +- Direct `target` references for delegation + +Key groupings: + +``` +items +├── afPage (body selector, id: af2_page) +├── form (.cmp-adaptiveform-container, id: af2_form) +├── base (generic .base field, id: af2_base) +│ └── items: fieldLabel, widgetAndText (with many target overrides) +├── button (grouping for all buttons via targets) +├── panel (grouping for all panel types via targets) +├── Image (target → image styleConfig) +├── hcaptcha (target → hcaptcha styleConfig) +└── recaptcha (target → recaptcha styleConfig) +``` + +### Header/Footer _cq_themeConfig + +These use `componentId` and reference their own `_cq_styleConfig` via `target`: + +```xml + + +
+ + +``` + +--- + +## Common Sub-Element Suffixes + +These BEM element suffixes appear consistently across components: + +| Suffix | Purpose | Typical States | +|--------|---------|---------------| +| `__widget` | The actual input/control | focus, hover, disabled, mandatory, checked | +| `__label` | Field label text | hover, focus | +| `__label-container` | Wrapper around label + help icon | disabled, hover, focus | +| `__questionmark` | Help/info icon | focus, hover, disabled | +| `__shortdescription` | Brief help text | (none) | +| `__longdescription` | Extended help text | focus, hover | +| `__errormessage` | Validation error text | (none) | +| `__help-container` | Wrapper for help content | focus, hover | +| `__widget-container` | Wrapper for checkbox/radio items | focus, hover, disabled | +| `__text` | Button label text | focus, active, disabled, hover | +| `__widget-slider` | Switch slider track | focus, disabled, hover, checked | +| `__circle-indicator` | Switch handle circle | focus, disabled, hover, checked | + +## Inheritance-Only Components (No Own _cq_styleConfig) + +These components inherit their `_cq_styleConfig` entirely from their `sling:resourceSuperType` and have no file to read — use the super type's config instead: + +| Component | Inherits From | Notes | +|-----------|--------------|-------| +| `turnstile/v1` | `recaptcha/v1/recaptcha` | Cloudflare Turnstile — uses reCAPTCHA config | +| `toggleablelink/v1` | `checkboxgroup/v1/checkboxgroup` | Uses checkbox group config | +| `fragment/v1` | `panelcontainer/v1/panelcontainer` | Form fragment — uses panel config | +| `fragmentcontainer/v1` | `container/v2/container` | Fragment container — uses container themeConfig | +| `checkboxgroup/v2` | `checkboxgroup/v1/checkboxgroup` | Version upgrade — no override needed | +| `radiobutton/v2` | `radiobutton/v1/radiobutton` | Version upgrade — no override needed | + +--- + +## Base / Generic Field Selectors + +The `base/v1/base/_cq_styleConfig` defines fallback selectors for all fields: + +| Selector | Purpose | +|----------|---------| +| `.cmp-adaptiveform-container .base` | Generic field wrapper | +| `.cmp-adaptiveform-container input,select,textarea` | All native inputs | +| `.cmp-adaptiveform-container label` | All labels | +| `.cmp-adaptiveform-container [class$='__errormessage']` | All error messages | diff --git a/.cursor/skills/css-to-theme-content-xml/config-file-index.md b/.cursor/skills/css-to-theme-content-xml/config-file-index.md new file mode 100644 index 0000000000..956d367528 --- /dev/null +++ b/.cursor/skills/css-to-theme-content-xml/config-file-index.md @@ -0,0 +1,104 @@ +# Config File Index + +All `_cq_styleConfig` and `_cq_themeConfig` content.xml files in this repository, organized by component type. Use these as authoritative templates when generating new config files. + +Base path: `ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/` + +## _cq_styleConfig Files (32 files) + +### Input Fields (Pattern A) + +| Component | Path | Pattern | sling:resourceSuperType | +|-----------|------|---------|------------------------| +| Text Input | `textinput/v1/textinput/_cq_styleConfig/.content.xml` | A (includes textarea variant) | `base/v1/base` | +| Number Input | `numberinput/v1/numberinput/_cq_styleConfig/.content.xml` | A | `base/v1/base` | +| Email Input | `emailinput/v1/emailinput/_cq_styleConfig/.content.xml` | A (hides textarea) | `textinput/v1/textinput` | +| Telephone Input | `telephoneinput/v1/telephoneinput/_cq_styleConfig/.content.xml` | A (hides textarea) | `textinput/v1/textinput` | +| Date Picker | `datepicker/v1/datepicker/_cq_styleConfig/.content.xml` | A | `base/v1/base` | +| DateTime | `datetime/v1/datetime/_cq_styleConfig/.content.xml` | A | `base/v1/base` | +| Dropdown | `dropdown/v1/dropdown/_cq_styleConfig/.content.xml` | A | `base/v1/base` | + +### Buttons (Pattern B) + +| Component | Path | Pattern | sling:resourceSuperType | +|-----------|------|---------|------------------------| +| Button | `button/v2/button/_cq_styleConfig/.content.xml` | B | `button/v1/button` → `base/v1/base` | +| Submit | `actions/submit/v2/submit/_cq_styleConfig/.content.xml` | B | `button/v2/button` | +| Reset | `actions/reset/v2/reset/_cq_styleConfig/.content.xml` | B | `button/v2/button` | + +### Checkbox / Radio (Pattern C) + +| Component | Path | Pattern | sling:resourceSuperType | +|-----------|------|---------|------------------------| +| Checkbox | `checkbox/v1/checkbox/_cq_styleConfig/.content.xml` | C | `base/v1/base` | +| CheckboxGroup | `checkboxgroup/v1/checkboxgroup/_cq_styleConfig/.content.xml` | C | `base/v1/base` | +| RadioButton | `radiobutton/v1/radiobutton/_cq_styleConfig/.content.xml` | C | `base/v1/base` | + +### Switch + +| Component | Path | sling:resourceSuperType | +|-----------|------|------------------------| +| Switch | `switch/v1/switch/_cq_styleConfig/.content.xml` | `checkbox/v1/checkbox` (completely different structure) | + +### Panels (Pattern D) + +| Component | Path | Pattern | sling:resourceSuperType | +|-----------|------|---------|------------------------| +| Panel Container | `panelcontainer/v1/panelcontainer/_cq_styleConfig/.content.xml` | D | `responsivegrid` | +| Accordion | `accordion/v1/accordion/_cq_styleConfig/.content.xml` | D | `wcm accordion` | +| Tabs on Top | `tabsontop/v1/tabsontop/_cq_styleConfig/.content.xml` | D | `wcm tabs` | +| Vertical Tabs | `verticaltabs/v1/verticaltabs/_cq_styleConfig/.content.xml` | D | `wcm panelcontainer` | +| Wizard | `wizard/v2/wizard/_cq_styleConfig/.content.xml` | D | `wizard/v1/wizard` | + +### Simple / Other (Pattern E and unique) + +| Component | Path | Pattern | sling:resourceSuperType | +|-----------|------|---------|------------------------| +| Image | `image/v1/image/_cq_styleConfig/.content.xml` | E | `base/v1/base` | +| Title | `title/v2/title/_cq_styleConfig/.content.xml` | E-variant | `title/v1/title` → `wcm title` | +| Text | `text/v1/text/_cq_styleConfig/.content.xml` | E-variant | `base/v1/base` | +| Base (generic field) | `base/v1/base/_cq_styleConfig/.content.xml` | Shared base | (root — no super) | +| File Input v3 | `fileinput/v3/fileinput/_cq_styleConfig/.content.xml` | Unique | `fileinput/v2` → `v1` → `base` | +| File Input v4 | `fileinput/v4/fileinput/_cq_styleConfig/.content.xml` | Unique | `fileinput/v3` | +| Scribble | `scribble/v1/scribble/_cq_styleConfig/.content.xml` | Unique | `base/v1/base` | +| Terms & Conditions | `termsandconditions/v1/termsandconditions/_cq_styleConfig/.content.xml` | Unique | `responsivegrid` | +| hCaptcha | `hcaptcha/v1/hcaptcha/_cq_styleConfig/.content.xml` | Unique | `recaptcha/v1/recaptcha` | +| reCAPTCHA | `recaptcha/v1/recaptcha/_cq_styleConfig/.content.xml` | Unique | `base/v1/base` | +| Review | `review/v1/review/_cq_styleConfig/.content.xml` | Unique | — | +| Footer | `footer/v1/footer/_cq_styleConfig/.content.xml` | Header/Footer | — | +| Page Header | `pageheader/v1/pageheader/_cq_styleConfig/.content.xml` | Header/Footer | — | + +### Components Without Own _cq_styleConfig (inherit from super type) + +| Component | Inherits Config From | sling:resourceSuperType | +|-----------|---------------------|------------------------| +| `turnstile/v1/turnstile` | `recaptcha/v1/recaptcha/_cq_styleConfig` | `recaptcha/v1/recaptcha` | +| `toggleablelink/v1/toggleablelink` | `checkboxgroup/v1/checkboxgroup/_cq_styleConfig` | `checkboxgroup/v1/checkboxgroup` | +| `fragment/v1/fragment` | `panelcontainer/v1/panelcontainer/_cq_styleConfig` | `panelcontainer/v1/panelcontainer` | +| `fragmentcontainer/v1/fragmentcontainer` | `container/v2/container/_cq_themeConfig` | `container/v2/container` | +| `checkboxgroup/v2/checkboxgroup` | `checkboxgroup/v1/checkboxgroup/_cq_styleConfig` | `checkboxgroup/v1/checkboxgroup` | +| `radiobutton/v2/radiobutton` | `radiobutton/v1/radiobutton/_cq_styleConfig` | `radiobutton/v1/radiobutton` | +| `submit/v1/submit` | `button/v1/button` → `base/v1/base/_cq_styleConfig` | `button/v1/button` | +| `reset/v1/reset` | `button/v1/button` → `base/v1/base/_cq_styleConfig` | `button/v1/button` | + +## _cq_themeConfig Files (3 files) + +| Component | Path | Purpose | +|-----------|------|---------| +| Container | `container/v2/container/_cq_themeConfig/.content.xml` | Main aggregator — references all component styleConfigs | +| Footer | `footer/v1/footer/_cq_themeConfig/.content.xml` | Footer aggregator | +| Page Header | `pageheader/v1/pageheader/_cq_themeConfig/.content.xml` | Header aggregator | + +## Recommended Templates + +When generating a new config file, use the closest existing component as a template: + +| If the new component is... | Use as template | +|---------------------------|----------------| +| A text/number/email-like input | `textinput/v1/textinput/_cq_styleConfig/.content.xml` | +| A button variant | `button/v2/button/_cq_styleConfig/.content.xml` | +| A checkbox/radio/toggle | `checkbox/v1/checkbox/_cq_styleConfig/.content.xml` | +| A panel/container | `panelcontainer/v1/panelcontainer/_cq_styleConfig/.content.xml` | +| An accordion/tabs/wizard | `accordion/v1/accordion/_cq_styleConfig/.content.xml` | +| A simple display component | `image/v1/image/_cq_styleConfig/.content.xml` | +| A container themeConfig | `container/v2/container/_cq_themeConfig/.content.xml` | diff --git a/scripts/css_to_theme_xml.py b/scripts/css_to_theme_xml.py new file mode 100644 index 0000000000..bd81b44bdb --- /dev/null +++ b/scripts/css_to_theme_xml.py @@ -0,0 +1,332 @@ +#!/usr/bin/env python3 +""" +Parse CSS and generate theme.structure content.xml per css-to-theme-content-xml skill. +""" +import re +import sys +from collections import defaultdict + +# Selector pattern -> (af2_id, state) +# State: default, hover, focus, error, success, disabled, active, checked +SELECTOR_MAP = [ + # Container / Form + (r"^\.cmp-container\.cmp-adaptiveform-container$", ("af2_form", "default")), + (r"^\.cmp-adaptiveform-container$", ("af2_form", "default")), + # Button + (r"^\.cmp-adaptiveform-button$", ("af2_button", "default")), + (r"^\.cmp-adaptiveform-button__widget$", ("af2_button", "default")), + (r"^\.cmp-adaptiveform-button__widget:hover$", ("af2_button", "hover")), + (r"^\[data-cmp-enabled\]:not\(\[data-cmp-enabled=true\]\) \.cmp-adaptiveform-button__widget$", ("af2_button", "disabled")), + (r"^\.cmp-adaptiveform-button__text$", ("af2_buttontext", "default")), + (r"^\.cmp-adaptiveform-button__longdescription$", ("af2_buttondescriptionlong", "default")), + (r"^\.cmp-adaptiveform-button__shortdescription$", ("af2_buttondescriptionshort", "default")), + (r"^\.cmp-adaptiveform-button__questionmark$", ("af2_buttonhelpicon", "default")), + (r"^\.cmp-adaptiveform-button__help-container$", ("af2_buttonhelpcontainer", "default")), + # Input fields - widget + (r"^\.cmp-adaptiveform-textinput__widget$", ("af2_textinputwidgetandtext", "default")), + (r"^\.cmp-adaptiveform-numberinput__widget$", ("af2_numberinputwidgetandtext", "default")), + (r"^\.cmp-adaptiveform-emailinput__widget$", ("af2_emailinputwidgetandtext", "default")), + (r"^\.cmp-adaptiveform-telephoneinput__widget$", ("af2_telephoneinputwidgetandtext", "default")), + (r"^\.cmp-adaptiveform-datepicker__widget$", ("af2_datepickerwidgetandtext", "default")), + (r"^\.cmp-adaptiveform-dropdown__widget$", ("af2_dropdownwidgetandtext", "default")), + (r"^\.cmp-adaptiveform-datetime__widget$", ("af2_datetimewidgetandtext", "default")), + (r"^textarea\.cmp-adaptiveform-textinput__widget$", ("af2_textareawidgetandtext", "default")), + # Input fields - root (widgetAndText) + (r"^\.cmp-adaptiveform-textinput$", ("af2_textinputwidgetandtext", "default")), + (r"^\.cmp-adaptiveform-numberinput$", ("af2_numberinputwidgetandtext", "default")), + (r"^\.cmp-adaptiveform-emailinput$", ("af2_emailinputwidgetandtext", "default")), + (r"^\.cmp-adaptiveform-telephoneinput$", ("af2_telephoneinputwidgetandtext", "default")), + (r"^\.cmp-adaptiveform-datepicker$", ("af2_datepickerwidgetandtext", "default")), + (r"^\.cmp-adaptiveform-dropdown$", ("af2_dropdownwidgetandtext", "default")), + (r"^\.cmp-adaptiveform-datetime$", ("af2_datetimewidgetandtext", "default")), + # Input fields - data-cmp-valid states + (r"^\.cmp-adaptiveform-textinput\[data-cmp-valid=false\]$", ("af2_textinputwidgetandtext", "error")), + (r"^\.cmp-adaptiveform-textinput\[data-cmp-valid=true\]$", ("af2_textinputwidgetandtext", "success")), + (r"^\.cmp-adaptiveform-numberinput\[data-cmp-valid=false\]$", ("af2_numberinputwidgetandtext", "error")), + (r"^\.cmp-adaptiveform-numberinput\[data-cmp-valid=true\]$", ("af2_numberinputwidgetandtext", "success")), + (r"^\.cmp-adaptiveform-emailinput\[data-cmp-valid=false\]$", ("af2_emailinputwidgetandtext", "error")), + (r"^\.cmp-adaptiveform-emailinput\[data-cmp-valid=true\]$", ("af2_emailinputwidgetandtext", "success")), + (r"^\.cmp-adaptiveform-telephoneinput\[data-cmp-valid=false\]$", ("af2_telephoneinputwidgetandtext", "error")), + (r"^\.cmp-adaptiveform-telephoneinput\[data-cmp-valid=true\]$", ("af2_telephoneinputwidgetandtext", "success")), + (r"^\.cmp-adaptiveform-datepicker\[data-cmp-valid=false\]$", ("af2_datepickerwidgetandtext", "error")), + (r"^\.cmp-adaptiveform-datepicker\[data-cmp-valid=true\]$", ("af2_datepickerwidgetandtext", "success")), + (r"^\.cmp-adaptiveform-dropdown\[data-cmp-valid=false\]$", ("af2_dropdownwidgetandtext", "error")), + (r"^\.cmp-adaptiveform-dropdown\[data-cmp-valid=true\]$", ("af2_dropdownwidgetandtext", "success")), + (r"^\.cmp-adaptiveform-datetime\[data-cmp-valid=false\]$", ("af2_datetimewidgetandtext", "error")), + (r"^\.cmp-adaptiveform-datetime\[data-cmp-valid=true\]$", ("af2_datetimewidgetandtext", "success")), + # Checkbox / Radio / CheckboxGroup + (r"^\.cmp-adaptiveform-checkboxgroup$", ("af2_checkboxgroupwidgetandtext", "default")), + (r"^\.cmp-adaptiveform-checkboxgroup\[data-cmp-valid=false\]$", ("af2_checkboxgroupwidgetandtext", "error")), + (r"^\.cmp-adaptiveform-checkboxgroup\[data-cmp-valid=true\]$", ("af2_checkboxgroupwidgetandtext", "success")), + (r"^\.cmp-adaptiveform-checkbox$", ("af2_checkboxwidgetandtext", "default")), + (r"^\.cmp-adaptiveform-checkbox\[data-cmp-valid=false\]$", ("af2_checkboxwidgetandtext", "error")), + (r"^\.cmp-adaptiveform-checkbox\[data-cmp-valid=true\]$", ("af2_checkboxwidgetandtext", "success")), + (r"^\.cmp-adaptiveform-checkbox__widget$", ("af2_checkboxwidgetandtext", "default")), + (r"^\.cmp-adaptiveform-radiobutton$", ("af2_radiobuttonwidgetandtext", "default")), + (r"^\.cmp-adaptiveform-radiobutton\[data-cmp-valid=false\]$", ("af2_radiobuttonwidgetandtext", "error")), + (r"^\.cmp-adaptiveform-radiobutton\[data-cmp-valid=true\]$", ("af2_radiobuttonwidgetandtext", "success")), + (r"^\.cmp-adaptiveform-radiobutton__widget$", ("af2_radiobuttonwidgetandtext", "default")), + # Switch + (r"^\.cmp-adaptiveform-switch$", ("af2_switchwidgetandtext", "default")), + (r"^\.cmp-adaptiveform-switch__widget-slider$", ("af2_switchonlabel", "default")), + (r"^\.cmp-adaptiveform-switch__circle-indicator$", ("af2_switchhandle", "default")), + (r"^\.cmp-adaptiveform-switch\[data-cmp-valid=false\]$", ("af2_switchwidgetandtext", "error")), + (r"^\.cmp-adaptiveform-switch\[data-cmp-valid=true\]$", ("af2_switchwidgetandtext", "success")), + # File input + (r"^\.cmp-adaptiveform-fileinput$", ("af2_fileinputwidgetandtext", "default")), + (r"^\.cmp-adaptiveform-fileinput__widget$", ("af2_fileinputwidgetandtext", "default")), + (r"^\.cmp-adaptiveform-fileinput\[data-cmp-valid=false\]$", ("af2_fileinputwidgetandtext", "error")), + (r"^\.cmp-adaptiveform-fileinput\[data-cmp-valid=true\]$", ("af2_fileinputwidgetandtext", "success")), + # Panel / Accordion / Tabs / Wizard + (r"^\.cmp-container$", ("af2_panel", "default")), + (r"^\.cmp-accordion$", ("af2_accordionpanel", "default")), + (r"^\.cmp-accordion__panel$", ("af2_accordionpanelwidget", "default")), + (r"^\.cmp-tabs$", ("af2_tabsontoppanel", "default")), + (r"^\.cmp-verticaltabs$", ("af2_tabsonleftpanel", "default")), + (r"^\.cmp-adaptiveform-wizard$", ("af2_wizardpanel", "default")), + # Image / Text / Title + (r"^\.cmp-image$", ("af2_image", "default")), + (r"^\.cmp-image__image$", ("af2_image", "default")), + (r"^\.cmp-adaptiveform-text$", ("af2_text", "default")), + (r"^\.cmp-title__text$", ("af2_title", "default")), + # reCAPTCHA / hCaptcha + (r"^\.cmp-adaptiveform-recaptcha$", ("af2_recaptchawidgetandtext", "default")), + (r"^\.cmp-adaptiveform-recaptcha\[data-cmp-valid=false\]$", ("af2_recaptchawidgetandtext", "error")), + (r"^\.cmp-adaptiveform-recaptcha\[data-cmp-valid=true\]$", ("af2_recaptchawidgetandtext", "success")), + (r"^\.cmp-adaptiveform-hcaptcha$", ("af2_hcaptchawidgetandtext", "default")), + (r"^\.cmp-adaptiveform-hcaptcha\[data-cmp-valid=false\]$", ("af2_hcaptchawidgetandtext", "error")), + (r"^\.cmp-adaptiveform-hcaptcha\[data-cmp-valid=true\]$", ("af2_hcaptchawidgetandtext", "success")), + # Footer + (r"^\.cmp-adaptiveform-footer$", ("af2_footer", "default")), + (r"^\.cmp-adaptiveform-footer__text$", ("af2_footertext", "default")), +] + +def hex_to_rgb(hex_val): + """Convert #RRGGBB or #RGB to rgb(r,g,b). Commas escaped in normalize_prop.""" + h = hex_val.lstrip("#") + if len(h) == 6: + r, g, b = int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16) + elif len(h) == 3: + r, g, b = int(h[0]*2, 16), int(h[1]*2, 16), int(h[2]*2, 16) + else: + return hex_val + return f"rgb({r},{g},{b})" + +def normalize_prop(prop, val): + """Normalize CSS property for bracket format. Convert hex to rgb, escape commas.""" + prop = prop.strip().lower() + val = val.strip() + # Skip url(), complex values + if "url(" in val or "gradient" in val: + return None + # Convert hex to rgb (returns rgb(r,g,b) with raw commas) + hex_match = re.search(r"#([0-9a-fA-F]{3,8})\b", val) + if hex_match: + val = re.sub(r"#([0-9a-fA-F]{3,8})\b", lambda m: hex_to_rgb(m.group(0)), val) + # Escape commas in values for JCR bracket format + val = val.replace(",", "\\,") + return f"{prop}:{val}" + +def parse_css(content): + """Parse CSS into (selector, props) list. Skip @media for now.""" + rules = [] + # Remove @media blocks for simplicity + content = re.sub(r"@media[^{]+\{[^}]*\}", "", content, flags=re.DOTALL) + # Match selector { ... } + pattern = re.compile(r"([^{]+)\{([^}]*)\}", re.DOTALL) + for m in pattern.finditer(content): + selector = m.group(1).strip().strip(",").strip() + # Take first selector if comma-separated + if "," in selector: + for s in selector.split(","): + s = s.strip() + if s and s.startswith(".cmp-"): + rules.append((s, m.group(2))) + else: + if selector.startswith(".cmp-") or "cmp-adaptiveform" in selector or "cmp-container" in selector: + rules.append((selector, m.group(2))) + return rules + +def resolve_selector(sel): + """Resolve selector to (af2_id, state).""" + sel = sel.strip() + for pattern, (af2_id, state) in SELECTOR_MAP: + if re.match(pattern, sel): + return (af2_id, state) + return None + +def parse_declarations(block): + """Parse CSS declarations into list of (prop, val).""" + props = [] + for part in block.split(";"): + part = part.strip() + if ":" in part: + idx = part.index(":") + prop, val = part[:idx], part[idx+1:].strip() + if prop and val: + props.append((prop, val)) + return props + +def build_bracket(props): + """Build bracket value string from props.""" + parts = [] + for prop, val in props: + n = normalize_prop(prop, val) + if n: + parts.append(n) + if not parts: + return None + return "[" + ",".join(parts) + "]" + +def escape_commas(val): + """Escape commas for bracket format: , -> \\,""" + return val.replace(",", "\\,") + +def extract_ui_metadata(props): + """Extract UI metadata (popover, backgroundColor) from props.""" + ui = [] + has_border = any("border" in p for p, _ in props) + has_border_radius = any("radius" in p for p, _ in props) + has_padding = any("padding" in p for p, _ in props) + has_margin = any("margin" in p for p, _ in props) + bg = None + for p, v in props: + if "background" in p and "color" in p.lower() and "url" not in v: + bg = v.strip() + hex_m = re.search(r"#([0-9a-fA-F]{3,8})\b", bg) + if hex_m: + bg = escape_commas(hex_to_rgb(hex_m.group(0))) + if bg: + ui.append(f"backgroundColor:{bg}") + if has_border: + # Find border-width + bw = "1px" + for p, v in props: + if "border-width" in p or (p == "border" and "solid" in v): + if "border-width" in p: + bw = v.strip() + break + ui.append(f"borderWidthPopover:{bw}") + if has_border_radius: + br = "0" + for p, v in props: + if "radius" in p: + br = v.strip() + break + ui.append(f"borderRadiusPopover:{br}") + if has_padding: + pad = "0" + for p, v in props: + if "padding" in p: + pad = v.strip() + break + ui.append(f"paddingPopover:{pad}") + if has_margin: + mar = "0" + for p, v in props: + if "margin" in p: + mar = v.strip() + break + ui.append(f"marginPopover:{mar}") + if not ui: + return None + return "[" + ",".join(ui) + "]" + +def expand_shorthand(props): + """Expand border, padding, margin shorthand to longhand.""" + result = [] + for prop, val in props: + p = prop.strip().lower() + v = val.strip() + if p == "border" and "solid" in v: + parts = v.split() + width = "1px" + color = "#000" + for x in parts: + if x.endswith("px") or x.endswith("rem"): + width = x + elif x.startswith("#") or x in ("solid", "none"): + if x.startswith("#"): + color = x + result.extend([ + ("border-style", "solid"), + ("border-top-width", width), + ("border-right-width", width), + ("border-bottom-width", width), + ("border-left-width", width), + ("border-color", color), + ]) + elif p == "padding" and not any(x in p for x in ["top", "right", "bottom", "left"]): + result.extend([ + ("padding-top", v), + ("padding-right", v), + ("padding-bottom", v), + ("padding-left", v), + ]) + elif p == "margin" and not any(x in p for x in ["top", "right", "bottom", "left"]): + result.extend([ + ("margin-top", v), + ("margin-right", v), + ("margin-bottom", v), + ("margin-left", v), + ]) + else: + result.append((prop, val)) + return result + +def main(): + css_path = sys.argv[1] if len(sys.argv) > 1 else "a.css" + with open(css_path, "r") as f: + css = f.read() + + nodes = defaultdict(dict) # af2_id -> { state -> (bracket, ui) } + + for selector, block in parse_css(css): + resolved = resolve_selector(selector) + if not resolved: + continue + af2_id, state = resolved + props = parse_declarations(block) + if not props: + continue + props = expand_shorthand(props) + bracket = build_bracket(props) + if not bracket: + continue + ui = extract_ui_metadata(props) + attr = f"default_x0023_{state}" + nodes[af2_id][attr] = (bracket, ui) + + # Emit XML + print('') + print('') + print(' ') + print(' ') + print(' ') + + # Sort nodes for consistent output + for af2_id in sorted(nodes.keys()): + attrs = nodes[af2_id] + parts = [f' <{af2_id}', + ' jcr:primaryType="nt:unstructured"'] + for attr in sorted(attrs.keys()): + bracket, ui = attrs[attr] + parts.append(f' {attr}="{bracket}"') + if ui: + parts.append(f' {attr}_x0023_ui="{ui}"') + parts.append(' />') + print("\n".join(parts)) + + print(' ') + print(' ') + print(' ') + print(' ') + print('') + +if __name__ == "__main__": + main() From 8a2bc723a3b52e47d5782b6c34d280cdd5b348d1 Mon Sep 17 00:00:00 2001 From: "sakarora@adobe.com" Date: Thu, 26 Feb 2026 12:07:57 +0530 Subject: [PATCH 2/2] update --- .../skills/css-to-theme-content-xml/SKILL.md | 79 +++++++++++++++++++ .../component-selectors.md | 18 ++++- 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/.cursor/skills/css-to-theme-content-xml/SKILL.md b/.cursor/skills/css-to-theme-content-xml/SKILL.md index 39aeb7d439..b8baf8d50d 100644 --- a/.cursor/skills/css-to-theme-content-xml/SKILL.md +++ b/.cursor/skills/css-to-theme-content-xml/SKILL.md @@ -415,6 +415,85 @@ Use popover values only when applicable; omit Lock values if not needed: --- +## Round-Trip Fidelity and Missing Styles + +When CSS → content.xml → exported CSS produces different output, styles are lost or transformed. Document these cases and handle them to improve round-trip fidelity. + +### Selector Equivalence (Map Alternate Selectors) + +The theme editor uses specific selectors; input CSS may use equivalent but different patterns. **Map these to the same state:** + +| Input CSS Pattern | Theme Config Selector | State | Notes | +|-------------------|------------------------|-------|-------| +| `[data-cmp-enabled]:not([data-cmp-enabled=true]) .cmp-adaptiveform-button__widget` | `.cmp-adaptiveform-button__widget:disabled` | `default_x0023_disabled` | Both target disabled button | +| `[data-cmp-valid=false]` | `[data-cmp-valid='false']` | `default_x0023_error` | Quote style may differ | +| `[data-cmp-valid=true]` | `[data-cmp-valid='true']` | `default_x0023_success` | Quote style may differ | +| `.cmp-container.cmp-adaptiveform-container` | `.cmp-adaptiveform-container` | `af2_form` | Compound selector targets same form element | + +### Padding and Margin Shorthand Expansion + +**Expand shorthands correctly** — wrong expansion produces invalid or different CSS on export: + +| Shorthand | Correct Expansion | +|-----------|-------------------| +| `padding: 10px` | `padding-top:10px,padding-right:10px,padding-bottom:10px,padding-left:10px` | +| `padding: 0 20px` | `padding-top:0,padding-right:20px,padding-bottom:0,padding-left:20px` | +| `padding: .5rem 1rem` | `padding-top:.5rem,padding-right:1rem,padding-bottom:.5rem,padding-left:1rem` | +| `padding: 1rem 2rem 3rem` | `padding-top:1rem,padding-right:2rem,padding-bottom:3rem,padding-left:2rem` | +| `margin: .25rem 0` | `margin-top:.25rem,margin-right:0,margin-bottom:.25rem,margin-left:0` | + +**Incorrect:** `padding-top: 0 20px` (invalid — two values in one property). Always expand to four longhand properties. + +### CheckboxGroup Selector Mapping + +CheckboxGroup has distinct nodes; the theme export uses different selectors than the root: + +| Config Node ID | cssSelector | Theme Export Selector | +|----------------|-------------|------------------------| +| `af2_checkboxgroupfieldwidgetandtext` | `.cmp-adaptiveform-checkboxgroup` | `.cmp-adaptiveform-checkboxgroup` | +| `af2_checkboxgroupitemswidget` | `.cmp-adaptiveform-checkboxgroup__widget` | `.cmp-adaptiveform-checkboxgroup__widget` | +| `af2_checkboxgroupwidgetandtext` | `.cmp-adaptiveform-checkboxgroup__option__widget` | `.cmp-adaptiveform-checkboxgroup__option__widget` | + +**Important:** `.cmp-adaptiveform-checkboxgroup__widget` maps to `af2_checkboxgroupitemswidget` (container). `.cmp-adaptiveform-checkboxgroup__option__widget` maps to `af2_checkboxgroupwidgetandtext` (individual option input). Do not conflate `__widget` with `__option__widget`. + +### Properties That Cannot Be Stored in Bracket Format + +These are typically **dropped** or require `cssOverride`: + +| Property Pattern | Reason | Alternative | +|------------------|--------|-------------| +| `background: url(...)` | URL values not supported in standard bracket props | Use `cssOverride` with raw CSS, or `addonCss` | +| `background: url(...) 50%/cover no-repeat,#969696` | Complex value with URL | Split: store `background-color` in bracket; put URL in `cssOverride` | +| Compound selectors (e.g. `.help-container .questionmark`) | Theme config has flat nodes per element | Apply to the inner element node, or use `cssOverride` | +| `@media` rules | Breakpoint handling differs | Use `phone_x0023_*` / `tablet_x0023_*` attributes | + +### Root Element Styles Without Config Nodes + +Some root selectors (e.g. `.cmp-adaptiveform-button` with only `margin`) have no direct config node — the button config starts at `__widget`. Options: + +- Merge root-only styles into the primary child node (e.g. `af2_button`) when semantically appropriate +- Use `cssOverride` on the nearest config node if the theme supports it +- Document as "not round-trippable" if no suitable node exists + +### Border Shorthand Expansion + +| Shorthand | Expansion | +|-----------|-----------| +| `border: 2px solid #666` | `border-style:solid,border-top-width:2px,border-right-width:2px,border-bottom-width:2px,border-left-width:2px,border-color:rgb(102\,102\,102)` | +| `border: 0 solid #ca1b15` | `border-style:solid,border-top-width:0,...` (expand all sides) | + +### cssOverride for Complex Values + +When a property cannot be expressed in bracket format, use `cssOverride`: + +```xml +default_x0023_default="[cssOverride:background\: url(resources/images/question.svg) 50%/cover no-repeat\, #969696]" +``` + +Escape colons and commas in the raw CSS value. + +--- + ## Validation Checklist - [ ] Read the actual matching config file from the repository before generating diff --git a/.cursor/skills/css-to-theme-content-xml/component-selectors.md b/.cursor/skills/css-to-theme-content-xml/component-selectors.md index 95c702f096..12f4d66afb 100644 --- a/.cursor/skills/css-to-theme-content-xml/component-selectors.md +++ b/.cursor/skills/css-to-theme-content-xml/component-selectors.md @@ -118,7 +118,18 @@ Outer states: hover, focus, disabled (`[data-cmp-enabled='false']`), error, succ ### CheckboxGroup (`checkboxgroup/v1/checkboxgroup`) -Same pattern as Checkbox, replace `checkbox` with `checkboxgroup` in selectors and ids. +CheckboxGroup has a more complex structure than Checkbox. Key nodes: + +| XML Node | cssSelector | id | +|----------|-------------|-----| +| widgetAndText (root) | `.cmp-adaptiveform-checkboxgroup` | `af2_checkboxgroupfieldwidgetandtext` | +| checkBoxGroupLabelContainer | `.cmp-adaptiveform-checkboxgroup__label-container` | `af2_checkboxgrouplabelcontainer` | +| checkBoxGroupWidget (items container) | `.cmp-adaptiveform-checkboxgroup__widget` | `af2_checkboxgroupitemswidget` | +| checkBoxGroupItem | `.cmp-adaptiveform-checkboxgroup-item` | `af2_checkboxgroupitem` | +| checkBoxGroupWidgetAndText (option input) | `.cmp-adaptiveform-checkboxgroup__option__widget` | `af2_checkboxgroupwidgetandtext` | + +**Important:** `.cmp-adaptiveform-checkboxgroup__widget` is the container; `.cmp-adaptiveform-checkboxgroup__option__widget` is the individual checkbox input. Theme export outputs to `__option__widget` for `af2_checkboxgroupwidgetandtext`. + - `checkboxgroup/v2` has no own config — inherits from `checkboxgroup/v1` - `toggleablelink/v1` also inherits from `checkboxgroup/v1` (no own config) @@ -219,6 +230,11 @@ Root selector: `.cmp-adaptiveform-text` or `.cmp-text`. ### Container _cq_themeConfig (`container/v2/container`) +| Selector | id | Notes | +|----------|-----|-------| +| `.cmp-adaptiveform-container` | `af2_form` | Primary form selector | +| `.cmp-container.cmp-adaptiveform-container` | `af2_form` | Compound selector — same element when both classes present | + The container themeConfig aggregates ALL component styleConfigs. It uses: - `componentId="af2_guideContainer"` on the root - `label="CC Container"` on the root