src/lib/form/adapters/antd/antd-radio-adapter.ts

Total Symbols
4
Lines of Code
131
Avg Complexity
4.0
Avg Coverage
100.0%

File Relationships

graph LR buildField["buildField"] extractRadioOptions["extractRadioOptions"] buildField -->|calls| extractRadioOptions click buildField "../symbols/0731bf36655a7ca8.html" click extractRadioOptions "../symbols/1022a5863703a14b.html"

Symbols by Kind

method 3
function 1

All Symbols

Name Kind Visibility Status Lines Signature
matches method - 37-39 matches(el: HTMLElement): : boolean
buildField method - 41-58 buildField(wrapper: HTMLElement): : FormField
fill method - 60-104 fill(wrapper: HTMLElement, value: string): : boolean
extractRadioOptions function - 109-130 extractRadioOptions( wrapper: HTMLElement, ): : Array<{ value: string; text: string }> | undefined

Full Source

/**
 * Ant Design Radio Group Adapter
 *
 * Detects and fills `<Radio.Group>` components.
 *
 * DOM structure (antd v5):
 *   <div class="ant-radio-group ...">
 *     <label class="ant-radio-wrapper">
 *       <span class="ant-radio"><input type="radio" /></span>
 *       <span>Option text</span>
 *     </label>
 *     ...
 *   </div>
 *
 * Also handles `<Radio.Button>` style:
 *   <div class="ant-radio-group ant-radio-group-solid">
 *     <label class="ant-radio-button-wrapper">...
 *
 * Filling: Clicks the matching radio option.
 */

import type { FormField } from "@/types";
import type { CustomComponentAdapter } from "../adapter.interface";
import {
  findAntLabel,
  findAntId,
  isAntRequired,
  simulateClick,
  getAntdSelector,
} from "./antd-utils";
import { buildSignals } from "../../extractors";

export const antdRadioAdapter: CustomComponentAdapter = {
  name: "antd-radio",
  selector: ".ant-radio-group",

  matches(el: HTMLElement): boolean {
    return el.classList.contains("ant-radio-group");
  },

  buildField(wrapper: HTMLElement): FormField {
    const options = extractRadioOptions(wrapper);

    const field: FormField = {
      element: wrapper,
      selector: getAntdSelector(wrapper),
      category: "unknown",
      fieldType: "radio",
      adapterName: "antd-radio",
      label: findAntLabel(wrapper),
      id: findAntId(wrapper),
      required: isAntRequired(wrapper),
      options,
    };

    field.contextSignals = buildSignals(field);
    return field;
  },

  fill(wrapper: HTMLElement, value: string): boolean {
    const labels = wrapper.querySelectorAll<HTMLElement>(
      ".ant-radio-wrapper, .ant-radio-button-wrapper",
    );

    // Try matching by value on the underlying input
    for (const label of labels) {
      const input = label.querySelector<HTMLInputElement>(
        "input[type='radio']",
      );
      if (input?.value === value) {
        simulateClick(label);
        return true;
      }
    }

    // Try matching by text
    for (const label of labels) {
      const text = label.textContent?.trim() ?? "";
      if (text.toLowerCase() === value.toLowerCase()) {
        simulateClick(label);
        return true;
      }
    }

    // Partial text match
    for (const label of labels) {
      const text = label.textContent?.trim() ?? "";
      if (text.toLowerCase().includes(value.toLowerCase())) {
        simulateClick(label);
        return true;
      }
    }

    // Fallback: click the first unchecked option
    const first = wrapper.querySelector<HTMLElement>(
      ".ant-radio-wrapper:not(.ant-radio-wrapper-checked), .ant-radio-button-wrapper:not(.ant-radio-button-wrapper-checked)",
    );
    if (first) {
      simulateClick(first);
      return true;
    }

    return false;
  },
};

// ── Helpers ───────────────────────────────────────────────────────────────────

function extractRadioOptions(
  wrapper: HTMLElement,
): Array<{ value: string; text: string }> | undefined {
  const labels = wrapper.querySelectorAll<HTMLElement>(
    ".ant-radio-wrapper, .ant-radio-button-wrapper",
  );

  const opts = Array.from(labels)
    .map((label) => {
      const input = label.querySelector<HTMLInputElement>(
        "input[type='radio']",
      );
      const text = label.textContent?.trim() ?? "";
      return {
        value: input?.value || text,
        text,
      };
    })
    .filter((o) => o.text);

  return opts.length > 0 ? opts : undefined;
}