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

Total Symbols
3
Lines of Code
108
Avg Complexity
3.7
Avg Coverage
100.0%

Symbols by Kind

method 3

All Symbols

Name Kind Visibility Status Lines Signature
matches method - 33-38 matches(el: HTMLElement): : boolean
buildField method - 40-59 buildField(wrapper: HTMLElement): : FormField
fill method - 61-106 fill(wrapper: HTMLElement, value: string): : boolean

Full Source

/**
 * Ant Design Slider Adapter
 *
 * Detects and fills `<Slider>` components.
 *
 * DOM structure (antd v5):
 *   <div class="ant-slider ...">
 *     <div class="ant-slider-rail"></div>
 *     <div class="ant-slider-track" style="left: 0%; width: N%"></div>
 *     <div class="ant-slider-step"></div>
 *     <div class="ant-slider-handle" role="slider"
 *          aria-valuenow="N" aria-valuemin="0" aria-valuemax="100"
 *          tabindex="0" style="left: N%"></div>
 *   </div>
 *
 * Filling: Sets the slider handle position via ARIA attributes and dispatches events.
 */

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

export const antdSliderAdapter: CustomComponentAdapter = {
  name: "antd-slider",
  selector: ".ant-slider",

  matches(el: HTMLElement): boolean {
    return (
      el.classList.contains("ant-slider") &&
      !el.classList.contains("ant-slider-disabled")
    );
  },

  buildField(wrapper: HTMLElement): FormField {
    const handle = wrapper.querySelector<HTMLElement>(".ant-slider-handle");
    const min = handle?.getAttribute("aria-valuemin") ?? "0";
    const max = handle?.getAttribute("aria-valuemax") ?? "100";

    const field: FormField = {
      element: wrapper,
      selector: getAntdSelector(wrapper),
      category: "unknown",
      fieldType: "number",
      adapterName: "antd-slider",
      label: findAntLabel(wrapper),
      id: findAntId(wrapper),
      required: isAntRequired(wrapper),
      placeholder: `${min}–${max}`,
    };

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

  fill(wrapper: HTMLElement, value: string): boolean {
    const handle = wrapper.querySelector<HTMLElement>(".ant-slider-handle");
    if (!handle) return false;

    const min = parseFloat(handle.getAttribute("aria-valuemin") ?? "0");
    const max = parseFloat(handle.getAttribute("aria-valuemax") ?? "100");

    let numericValue = parseFloat(value);
    if (isNaN(numericValue)) {
      // Generate a random value within the range
      numericValue = min + Math.random() * (max - min);
    }

    // Clamp to range
    numericValue = Math.max(min, Math.min(max, numericValue));

    // Calculate percentage position
    const percent = ((numericValue - min) / (max - min)) * 100;

    // Update the handle
    handle.setAttribute("aria-valuenow", String(Math.round(numericValue)));
    handle.style.left = `${percent}%`;

    // Update the track
    const track = wrapper.querySelector<HTMLElement>(".ant-slider-track");
    if (track) {
      track.style.width = `${percent}%`;
    }

    // Simulate mouse events to trigger React state updates
    const rect = wrapper.getBoundingClientRect();
    const clientX = rect.left + (rect.width * percent) / 100;
    const clientY = rect.top + rect.height / 2;

    handle.dispatchEvent(
      new MouseEvent("mousedown", { bubbles: true, clientX, clientY }),
    );
    document.dispatchEvent(
      new MouseEvent("mousemove", { bubbles: true, clientX, clientY }),
    );
    document.dispatchEvent(
      new MouseEvent("mouseup", { bubbles: true, clientX, clientY }),
    );

    return true;
  },
};