Ctrl K
ComunidadGuías…

worker/dyad-visual-editor-client.js

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
(() => {
  /* ---------- helpers --------------------------------------------------- */

  // Track text editing state globally
  let textEditingState = new Map(); // componentId -> { originalText, currentText, cleanup }

  function findElementByDyadId(dyadId, runtimeId) {
    // If runtimeId is provided, try to find element by runtime ID first
    if (runtimeId) {
      const elementByRuntimeId = document.querySelector(
        `[data-dyad-runtime-id="${runtimeId}"]`,
      );
      if (elementByRuntimeId) {
        return elementByRuntimeId;
      }
    }

    // Fall back to finding by dyad-id (will get first match)
    const escaped = CSS.escape(dyadId);
    return document.querySelector(`[data-dyad-id="${escaped}"]`);
  }

  function applyStyles(element, styles) {
    if (!element || !styles) return;

    console.debug(
      `[Dyad Visual Editor] Applying styles:`,
      styles,
      "to element:",
      element,
    );

    const applySpacing = (type, values) => {
      if (!values) return;
      Object.entries(values).forEach(([side, value]) => {
        const cssProperty = `${type}${side.charAt(0).toUpperCase() + side.slice(1)}`;
        element.style[cssProperty] = value;
      });
    };

    applySpacing("margin", styles.margin);
    applySpacing("padding", styles.padding);

    if (styles.border) {
      if (styles.border.width !== undefined) {
        element.style.borderWidth = styles.border.width;
        element.style.borderStyle = "solid";
      }
      if (styles.border.radius !== undefined) {
        element.style.borderRadius = styles.border.radius;
      }
      if (styles.border.color !== undefined) {
        element.style.borderColor = styles.border.color;
      }
    }

    if (styles.backgroundColor !== undefined) {
      element.style.backgroundColor = styles.backgroundColor;
    }

    if (styles.text) {
      const textProps = {
        fontSize: "fontSize",
        fontWeight: "fontWeight",
        fontFamily: "fontFamily",
        color: "color",
      };
      Object.entries(textProps).forEach(([key, cssProp]) => {
        if (styles.text[key] !== undefined) {
          element.style[cssProp] = styles.text[key];
        }
      });
    }
  }

  /* ---------- message handlers ------------------------------------------ */

  function handleGetStyles(data) {
    const { elementId, runtimeId } = data;
    const element = findElementByDyadId(elementId, runtimeId);
    if (element) {
      const computedStyle = window.getComputedStyle(element);
      const styles = {
        margin: {
          top: computedStyle.marginTop,
          right: computedStyle.marginRight,
          bottom: computedStyle.marginBottom,
          left: computedStyle.marginLeft,
        },
        padding: {
          top: computedStyle.paddingTop,
          right: computedStyle.paddingRight,
          bottom: computedStyle.paddingBottom,
          left: computedStyle.paddingLeft,
        },
        border: {
          width: computedStyle.borderWidth,
          radius: computedStyle.borderRadius,
          color: computedStyle.borderColor,
        },
        backgroundColor: computedStyle.backgroundColor,
        text: {
          fontSize: computedStyle.fontSize,
          fontWeight: computedStyle.fontWeight,
          fontFamily: computedStyle.fontFamily,
          color: computedStyle.color,
        },
      };

      window.parent.postMessage(
        {
          type: "dyad-component-styles",
          data: styles,
        },
        "*",
      );
    }
  }

  function handleModifyStyles(data) {
    const { elementId, runtimeId, styles } = data;
    const element = findElementByDyadId(elementId, runtimeId);
    if (element) {
      applyStyles(element, styles);

      // Send updated coordinates after style change

      const rect = element.getBoundingClientRect();
      window.parent.postMessage(
        {
          type: "dyad-component-coordinates-updated",
          coordinates: {
            top: rect.top,
            left: rect.left,
            width: rect.width,
            height: rect.height,
          },
        },
        "*",
      );
    }
  }

  function handleEnableTextEditing(data) {
    const { componentId, runtimeId } = data;

    // Clean up any existing text editing states first
    textEditingState.forEach((state, existingId) => {
      if (existingId !== componentId) {
        state.cleanup();
      }
    });

    const element = findElementByDyadId(componentId, runtimeId);
    if (element) {
      const originalText = element.innerText;

      element.contentEditable = "true";
      element.focus();

      // Select all text
      const range = document.createRange();
      range.selectNodeContents(element);
      const sel = window.getSelection();
      sel.removeAllRanges();
      sel.addRange(range);

      // Send updates as user types
      const onInput = () => {
        const currentText = element.innerText;

        // Update tracked state
        const state = textEditingState.get(componentId);
        if (state) {
          state.currentText = currentText;
        }

        window.parent.postMessage(
          {
            type: "dyad-text-updated",
            componentId,
            text: currentText,
          },
          "*",
        );
      };

      element.addEventListener("input", onInput);

      // Prevent click from propagating to selector while editing
      const stopProp = (e) => e.stopPropagation();
      element.addEventListener("click", stopProp);

      // Cleanup function
      const cleanup = () => {
        element.contentEditable = "false";
        element.removeEventListener("input", onInput);
        element.removeEventListener("click", stopProp);

        // Send final text update
        const finalText = element.innerText;
        window.parent.postMessage(
          {
            type: "dyad-text-finalized",
            componentId,
            text: finalText,
          },
          "*",
        );

        textEditingState.delete(componentId);
      };

      // Store state
      textEditingState.set(componentId, {
        originalText,
        currentText: originalText,
        cleanup,
      });
    }
  }

  function handleDisableTextEditing(data) {
    const { componentId } = data;
    const state = textEditingState.get(componentId);
    if (state) {
      state.cleanup();
    }
  }

  function handleGetTextContent(data) {
    const { componentId, runtimeId } = data;
    const element = findElementByDyadId(componentId, runtimeId);
    const state = textEditingState.get(componentId);

    window.parent.postMessage(
      {
        type: "dyad-text-content-response",
        componentId,
        text: state ? state.currentText : element ? element.innerText : null,
        isEditing: !!state,
      },
      "*",
    );
  }

  function handleModifyImageSrc(data) {
    const { elementId, runtimeId, src } = data;
    const element = findElementByDyadId(elementId, runtimeId);
    if (!element) return;

    // Find the <img> element (self or child)
    let imgEl = null;
    if (element.tagName === "IMG") {
      imgEl = element;
    } else {
      imgEl = element.querySelector("img");
    }

    if (imgEl) {
      // Cancel previous listeners to prevent stale error/load events on rapid swaps
      if (imgEl._dyadAbort) imgEl._dyadAbort.abort();
      const controller = new AbortController();
      imgEl._dyadAbort = controller;

      const sendCoordinates = () => {
        const rect = element.getBoundingClientRect();
        window.parent.postMessage(
          {
            type: "dyad-component-coordinates-updated",
            coordinates: {
              top: rect.top,
              left: rect.left,
              width: rect.width,
              height: rect.height,
            },
          },
          "*",
        );
      };

      imgEl.addEventListener("load", sendCoordinates, {
        once: true,
        signal: controller.signal,
      });
      imgEl.addEventListener(
        "error",
        () => {
          sendCoordinates();
          window.parent.postMessage(
            {
              type: "dyad-image-load-error",
              elementId,
              src,
            },
            "*",
          );
        },
        { once: true, signal: controller.signal },
      );
      imgEl.src = src;
    }
  }

  /* ---------- message bridge -------------------------------------------- */

  window.addEventListener("message", (e) => {
    if (e.source !== window.parent) return;

    const { type, data } = e.data;

    switch (type) {
      case "get-dyad-component-styles":
        handleGetStyles(data);
        break;
      case "modify-dyad-component-styles":
        handleModifyStyles(data);
        break;
      case "enable-dyad-text-editing":
        handleEnableTextEditing(data);
        break;
      case "disable-dyad-text-editing":
        handleDisableTextEditing(data);
        break;
      case "get-dyad-text-content":
        handleGetTextContent(data);
        break;
      case "modify-dyad-image-src":
        handleModifyImageSrc(data);
        break;
      case "cleanup-all-text-editing":
        // Clean up all text editing states
        textEditingState.forEach((state) => {
          state.cleanup();
        });
        break;
    }
  });
})();