// src/components/editor/domEditingTypes.ts
var CURATED_STYLE_PROPERTIES = [
  "position",
  "display",
  "top",
  "left",
  "right",
  "bottom",
  "inset",
  "width",
  "height",
  "gap",
  "justify-content",
  "align-items",
  "flex-direction",
  "font-size",
  "font-style",
  "font-weight",
  "font-family",
  "line-height",
  "letter-spacing",
  "text-align",
  "text-transform",
  "color",
  "background-color",
  "background-image",
  "opacity",
  "mix-blend-mode",
  "border-radius",
  "border-width",
  "border-style",
  "border-color",
  "border-top-width",
  "border-top-style",
  "border-top-color",
  "outline-color",
  "overflow",
  "clip-path",
  "box-shadow",
  "filter",
  "backdrop-filter",
  "z-index",
  "transform",
  "object-fit",
  "object-position"
];

// src/components/editor/domEditingDom.ts
function isHtmlElement(value) {
  return typeof value === "object" && value !== null && "nodeType" in value && typeof value.nodeType === "number" && value.nodeType === 1;
}
function parsePx(value) {
  if (!value) return null;
  const trimmed = value.trim();
  if (!trimmed.endsWith("px")) return null;
  const parsed = parseFloat(trimmed);
  return Number.isFinite(parsed) ? parsed : null;
}
function isIdentityTransform(value) {
  const transform = (value ?? "none").trim();
  if (!transform || transform === "none") return true;
  const matrix = transform.match(/^matrix\(([^)]+)\)$/i);
  if (matrix) {
    const values2 = matrix[1].split(",").map((part) => Number.parseFloat(part.trim()));
    if (values2.length !== 6 || values2.some((part) => !Number.isFinite(part))) return false;
    return Math.abs(values2[0] - 1) < 1e-4 && Math.abs(values2[1]) < 1e-4 && Math.abs(values2[2]) < 1e-4 && Math.abs(values2[3] - 1) < 1e-4 && Math.abs(values2[4]) < 1e-4 && Math.abs(values2[5]) < 1e-4;
  }
  const matrix3d = transform.match(/^matrix3d\(([^)]+)\)$/i);
  if (!matrix3d) return false;
  const values = matrix3d[1].split(",").map((part) => Number.parseFloat(part.trim()));
  if (values.length !== 16 || values.some((part) => !Number.isFinite(part))) return false;
  const identity = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
  return values.every((part, index) => Math.abs(part - identity[index]) < 1e-4);
}
function isTextBearingTag(tagName) {
  return ["div", "span", "p", "strong", "h1", "h2", "h3", "h4", "h5", "h6"].includes(tagName);
}
var COLOR_GRADING_SOURCE_HIDDEN_ATTR = "data-hf-color-grading-source-hidden";
function isElementVisibleThroughAncestors(el) {
  const win = el.ownerDocument.defaultView;
  if (!win) return true;
  let current = el;
  while (current) {
    const computed = win.getComputedStyle(current);
    if (computed.display === "none" || computed.visibility === "hidden") return false;
    const opacity = Number.parseFloat(computed.opacity);
    if (Number.isFinite(opacity) && opacity <= 0.01 && !current.hasAttribute(COLOR_GRADING_SOURCE_HIDDEN_ATTR))
      return false;
    current = current.parentElement;
  }
  return true;
}
function getCuratedComputedStyles(el) {
  const styles = {};
  const computed = el.ownerDocument.defaultView?.getComputedStyle(el);
  if (!computed) return styles;
  for (const prop of CURATED_STYLE_PROPERTIES) {
    const value = computed.getPropertyValue(prop);
    if (value) styles[prop] = value;
  }
  return styles;
}
function getInlineStyles(el) {
  const styles = {};
  for (const property of CURATED_STYLE_PROPERTIES) {
    const value = el.style.getPropertyValue(property);
    if (value) styles[property] = value;
  }
  return styles;
}
function getDataAttributes(el) {
  const attrs = {};
  for (const attr of el.attributes) {
    if (attr.name.startsWith("data-")) {
      attrs[attr.name.slice(5)] = attr.value;
    }
  }
  return attrs;
}
function findClosestByAttribute(el, attributeNames) {
  let current = el;
  while (current) {
    const candidate = current;
    if (attributeNames.some((attribute) => candidate.hasAttribute(attribute))) {
      return candidate;
    }
    current = current.parentElement;
  }
  return null;
}
var compositionSourceMap = /* @__PURE__ */ new Map();
function setCompositionSourceMap(map) {
  compositionSourceMap = map;
}
function sourceFromCompositionId(ownerRoot) {
  if (!ownerRoot || compositionSourceMap.size === 0) return void 0;
  const authored = ownerRoot.getAttribute("data-hf-original-composition-id");
  const current = ownerRoot.getAttribute("data-composition-id");
  return (authored ? compositionSourceMap.get(authored) : void 0) ?? (current ? compositionSourceMap.get(current) : void 0);
}
function getSourceFileForElement(el, activeCompositionPath) {
  const sourceHost = findClosestByAttribute(el, ["data-composition-file", "data-composition-src"]);
  const ownerRoot = findClosestByAttribute(el, ["data-composition-id"]);
  const sourceFile = sourceHost?.getAttribute("data-composition-file") ?? sourceHost?.getAttribute("data-composition-src") ?? ownerRoot?.getAttribute("data-composition-file") ?? ownerRoot?.getAttribute("data-composition-src") ?? sourceFromCompositionId(ownerRoot) ?? activeCompositionPath ?? "index.html";
  return {
    sourceFile,
    compositionPath: sourceFile
  };
}
function normalizeTimelineCompositionSource(value) {
  const trimmed = value?.trim();
  if (!trimmed) return void 0;
  let pathname = trimmed;
  try {
    pathname = new URL(trimmed, "http://studio.local").pathname;
  } catch {
    pathname = trimmed;
  }
  for (const marker of ["/preview/comp/", "/preview/"]) {
    const markerIndex = pathname.indexOf(marker);
    if (markerIndex < 0) continue;
    const sourcePath = pathname.slice(markerIndex + marker.length).replace(/^\/+/, "");
    return sourcePath || trimmed;
  }
  return trimmed;
}
function escapeCssIdentifier(value) {
  const css = globalThis.CSS;
  if (typeof css?.escape === "function") return css.escape(value);
  if (value === "-") return "\\-";
  let escaped = "";
  for (let index = 0; index < value.length; index += 1) {
    const char = value[index] ?? "";
    const code = char.charCodeAt(0);
    if (code === 0) {
      escaped += "\uFFFD";
      continue;
    }
    const isDigit = code >= 48 && code <= 57;
    const isUpperAlpha = code >= 65 && code <= 90;
    const isLowerAlpha = code >= 97 && code <= 122;
    const isControl = code >= 1 && code <= 31 || code === 127;
    const isLeadingDigit = index === 0 && isDigit;
    const isSecondDigitAfterDash = index === 1 && value.startsWith("-") && isDigit;
    if (isControl || isLeadingDigit || isSecondDigitAfterDash) {
      escaped += `\\${code.toString(16)} `;
      continue;
    }
    if (isUpperAlpha || isLowerAlpha || isDigit || char === "-" || char === "_" || code >= 128) {
      escaped += char;
      continue;
    }
    escaped += `\\${char}`;
  }
  return escaped;
}
function escapeCssString(value) {
  return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\a ").replace(/\r/g, "\\d ").replace(/\f/g, "\\c ");
}
function querySelectorAllSafely(doc, selector) {
  try {
    return Array.from(doc.querySelectorAll(selector));
  } catch {
    return [];
  }
}
function humanizeIdentifier(value) {
  return value.replace(/\.html$/i, "").replace(/^compositions\//i, "").split("/").at(-1)?.replace(/[-_]+/g, " ").replace(/\b\w/g, (char) => char.toUpperCase()) ?? value;
}
function buildStableSelector(el) {
  if (el.id) return `#${escapeCssIdentifier(el.id)}`;
  const compositionId = el.getAttribute("data-composition-id");
  if (compositionId) return `[data-composition-id="${escapeCssString(compositionId)}"]`;
  return getPreferredClassSelector(el);
}
function getPreferredClassSelector(el) {
  const classes = Array.from(el.classList).map((value) => value.trim()).filter(Boolean);
  if (classes.length === 0) return void 0;
  const preferred = classes.find((value) => value !== "clip" && !value.startsWith("__hf-")) ?? classes[0];
  return preferred ? `.${escapeCssIdentifier(preferred)}` : void 0;
}
function getSelectorIndex(doc, el, selector, sourceFile, activeCompositionPath) {
  if (!selector?.startsWith(".")) return void 0;
  const candidates = querySelectorAllSafely(doc, selector).filter(
    (candidate) => isHtmlElement(candidate) && getSourceFileForElement(candidate, activeCompositionPath).sourceFile === sourceFile
  );
  const index = candidates.indexOf(el);
  return index >= 0 ? index : void 0;
}

// src/components/editor/domEditingElement.ts
function isElementComputedVisible(el) {
  return isElementVisibleThroughAncestors(el);
}
var VISUAL_LEAF_TAGS = /* @__PURE__ */ new Set(["img", "video", "canvas", "svg", "audio"]);
function hasVisualPresence(el) {
  const win = el.ownerDocument.defaultView;
  if (!win) return false;
  const cs = win.getComputedStyle(el);
  if (cs.backgroundImage !== "none") return true;
  if (cs.backgroundColor && cs.backgroundColor !== "transparent" && cs.backgroundColor !== "rgba(0, 0, 0, 0)")
    return true;
  if (cs.borderWidth && parseFloat(cs.borderWidth) > 0 && cs.borderStyle !== "none") return true;
  if (cs.boxShadow && cs.boxShadow !== "none") return true;
  return false;
}
function isEmptyVisualContainer(el) {
  const tag = el.tagName.toLowerCase();
  if (VISUAL_LEAF_TAGS.has(tag)) return false;
  if (hasVisualPresence(el)) return false;
  const { children } = el;
  if (children.length === 0) {
    return (el.textContent ?? "").trim().length === 0;
  }
  for (let i = 0; i < children.length; i += 1) {
    const child = children[i];
    if (!isHtmlElement(child)) continue;
    if (VISUAL_LEAF_TAGS.has(child.tagName.toLowerCase())) return false;
    if (isElementComputedVisible(child)) return false;
  }
  return true;
}
function hasRenderedBox(el) {
  const rect = el.getBoundingClientRect();
  if (rect.width <= 1 || rect.height <= 1) return false;
  if (!isElementComputedVisible(el)) return false;
  if (isEmptyVisualContainer(el)) return false;
  return true;
}
var DOM_LAYER_IGNORED_TAGS = /* @__PURE__ */ new Set([
  "base",
  "br",
  "canvas",
  "link",
  "meta",
  "script",
  "source",
  "style",
  "template",
  "track",
  "wbr"
]);
function isInspectableLayerElement(el) {
  const tagName = el.tagName.toLowerCase();
  if (DOM_LAYER_IGNORED_TAGS.has(tagName)) return false;
  const computed = el.ownerDocument.defaultView?.getComputedStyle(el);
  if (computed?.display === "none" || computed?.visibility === "hidden") return false;
  return true;
}
function getDomLayerPatchTarget(el, activeCompositionPath) {
  if (!isInspectableLayerElement(el)) return null;
  if (el.hasAttribute("data-composition-id")) return null;
  const selector = buildStableSelector(el);
  if (!selector) return null;
  const { sourceFile } = getSourceFileForElement(el, activeCompositionPath);
  return {
    id: el.id || void 0,
    hfId: el.getAttribute("data-hf-id") || void 0,
    selector,
    selectorIndex: getSelectorIndex(
      el.ownerDocument,
      el,
      selector,
      sourceFile,
      activeCompositionPath
    ),
    sourceFile
  };
}
function getPreferredClipAncestor(startEl) {
  let current = startEl;
  while (current) {
    if (current.classList.contains("clip")) {
      const isCompositionHost = current.hasAttribute("data-composition-src") || current.hasAttribute("data-composition-file");
      if (!isCompositionHost || current === startEl) return current;
    }
    current = current.parentElement;
  }
  return null;
}
function getSelectionCandidate(startEl, options) {
  if (options.preferClipAncestor) {
    const clipAncestor = getPreferredClipAncestor(startEl);
    if (clipAncestor) {
      return clipAncestor;
    }
  }
  return startEl;
}
function resolveAllVisualDomEditTargets(elementsFromPoint, options) {
  const raw = [];
  for (const entry of elementsFromPoint) {
    if (!isHtmlElement(entry)) continue;
    if (hasRenderedBox(entry) && getDomLayerPatchTarget(entry, options.activeCompositionPath)) {
      raw.push(entry);
    }
  }
  if (raw.length === 0) return [];
  const layers = [];
  let best = raw[0];
  for (let i = 1; i < raw.length; i++) {
    const el = raw[i];
    if (best.contains(el)) {
      best = el;
    } else {
      layers.push(best);
      best = el;
    }
  }
  layers.push(best);
  return layers;
}
function findElementForSelection(doc, selection, activeCompositionPath = null) {
  if (selection.hfId) {
    const byHfId = doc.querySelector(`[data-hf-id="${CSS.escape(selection.hfId)}"]`);
    if (isHtmlElement(byHfId)) return byHfId;
  }
  if (selection.id) {
    const byId = doc.getElementById(selection.id);
    if (isHtmlElement(byId) && (!selection.sourceFile || getSourceFileForElement(byId, activeCompositionPath).sourceFile === selection.sourceFile)) {
      return byId;
    }
  }
  if (!selection.selector) return null;
  if (selection.selector.startsWith(".") && selection.selectorIndex != null) {
    const matches2 = querySelectorAllSafely(doc, selection.selector).filter(
      (candidate) => isHtmlElement(candidate) && (!selection.sourceFile || getSourceFileForElement(candidate, activeCompositionPath).sourceFile === selection.sourceFile)
    );
    return matches2[selection.selectorIndex] ?? null;
  }
  const matches = querySelectorAllSafely(doc, selection.selector).filter(
    (candidate) => isHtmlElement(candidate) && (!selection.sourceFile || getSourceFileForElement(candidate, activeCompositionPath).sourceFile === selection.sourceFile)
  );
  return matches[0] ?? null;
}
function findElementForTimelineElement(doc, element, options) {
  const elementId = typeof element.id === "string" ? element.id : "";
  const compositionSource = normalizeTimelineCompositionSource(element.compositionSrc) ?? options.compIdToSrc?.get(elementId);
  const sourceFile = compositionSource ?? normalizeTimelineCompositionSource(element.sourceFile) ?? options.activeCompositionPath ?? "index.html";
  const escapedElementId = escapeCssString(elementId);
  const escapedCompositionSource = compositionSource ? escapeCssString(compositionSource) : null;
  const selector = element.selector ?? (compositionSource ? `[data-composition-src="${escapedCompositionSource}"],[data-composition-file="${escapedCompositionSource}"],[data-composition-id="${escapedElementId}"]` : escapedElementId ? `[data-composition-id="${escapedElementId}"]` : void 0);
  if (selector || element.domId) {
    const targetElement = findElementForSelection(
      doc,
      {
        id: element.domId ?? void 0,
        selector,
        selectorIndex: element.selectorIndex,
        sourceFile
      },
      options.activeCompositionPath
    );
    if (targetElement) return targetElement;
  }
  const hasExplicitDomTarget = Boolean(element.domId || element.selector || compositionSource);
  if (options.isMasterView || hasExplicitDomTarget || !options.activeCompositionPath) {
    return null;
  }
  const root = doc.querySelector("[data-composition-id]");
  if (!isHtmlElement(root)) return null;
  return getSourceFileForElement(root, options.activeCompositionPath).sourceFile === sourceFile ? root : null;
}
function getDirectLayerChildren(el, options) {
  return Array.from(el.children).filter(
    (child) => isHtmlElement(child) && getDomLayerPatchTarget(child, options.activeCompositionPath) !== null
  );
}

// src/components/editor/domEditingRootLayer.ts
var COMPOSITION_ROOT_LAYER_EPSILON_PX = 1;
function readPositiveDimension(value) {
  if (!value) return null;
  const parsed = Number.parseFloat(value);
  return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
}
function approximatelyEqual(a, b) {
  return Math.abs(a - b) <= COMPOSITION_ROOT_LAYER_EPSILON_PX;
}
function getCompositionRootBounds(doc) {
  const root = doc.querySelector("[data-composition-id]") ?? doc.documentElement ?? null;
  const rootWidth = readPositiveDimension(root?.getAttribute("data-width") ?? null);
  const rootHeight = readPositiveDimension(root?.getAttribute("data-height") ?? null);
  if (!root || !rootWidth || !rootHeight) return null;
  return { rect: root.getBoundingClientRect(), width: rootWidth, height: rootHeight };
}
function getRenderedLayerSize(element, computedStyles) {
  const rect = element.getBoundingClientRect();
  const width = rect.width || parsePx(computedStyles.width);
  const height = rect.height || parsePx(computedStyles.height);
  return width && height ? { width, height } : null;
}
function matchesCompositionRootBounds(elementRect, elementSize, rootBounds) {
  return approximatelyEqual(elementRect.left, rootBounds.rect.left) && approximatelyEqual(elementRect.top, rootBounds.rect.top) && approximatelyEqual(elementSize.width, rootBounds.width) && approximatelyEqual(elementSize.height, rootBounds.height);
}
function isExplicitFullBleedLayer(computedStyles) {
  return computedStyles.position === "absolute" || computedStyles.position === "fixed";
}
function isCompositionRootLayer(element, doc, computedStyles) {
  if (element.parentElement !== doc.body) return false;
  if (element.hasAttribute("data-hf-allow-root-edit")) return false;
  if (isExplicitFullBleedLayer(computedStyles)) return false;
  const rootBounds = getCompositionRootBounds(doc);
  const elementSize = getRenderedLayerSize(element, computedStyles);
  return Boolean(
    rootBounds && elementSize && matchesCompositionRootBounds(element.getBoundingClientRect(), elementSize, rootBounds)
  );
}

// src/components/editor/domEditingLayers.ts
function isEditableTextLeaf(el) {
  return isTextBearingTag(el.tagName.toLowerCase()) && el.children.length === 0;
}
function getTextFieldLabel(_tagName, index, total, source) {
  if (source === "self" || total === 1) return "Content";
  return `Text ${index + 1}`;
}
function buildTextField(el, index, total, source) {
  const tagName = el.tagName.toLowerCase();
  const key = el.getAttribute("data-hf-text-key") ?? `${source}:${index}:${tagName}`;
  return {
    key,
    label: getTextFieldLabel(tagName, index, total, source),
    value: el.textContent ?? "",
    tagName,
    attributes: Array.from(el.attributes).filter((attribute) => attribute.name !== "style").map((attribute) => ({
      name: attribute.name,
      value: attribute.value
    })),
    inlineStyles: getInlineStyles(el),
    computedStyles: getCuratedComputedStyles(el),
    source
  };
}
function collectDomEditTextFields(el) {
  const childElements = Array.from(el.children).filter(isHtmlElement).filter(isEditableTextLeaf);
  if (childElements.length > 0) {
    const hasMixedContent = Array.from(el.childNodes).some(
      (node) => node.nodeType === 3 && node.textContent?.trim()
    );
    if (hasMixedContent) {
      const fields = [];
      let childIdx = 0;
      for (const node of el.childNodes) {
        if (node.nodeType === 3) {
          const text = node.textContent ?? "";
          if (!text.trim()) continue;
          fields.push({
            key: `text-node:${childIdx}`,
            label: `Text ${childIdx + 1}`,
            value: text,
            tagName: "#text",
            attributes: [],
            inlineStyles: {},
            computedStyles: {},
            source: "text-node"
          });
          childIdx++;
        } else if (isHtmlElement(node) && isEditableTextLeaf(node)) {
          fields.push(buildTextField(node, childIdx, childElements.length, "child"));
          childIdx++;
        }
      }
      return fields;
    }
    return childElements.map(
      (child, index) => buildTextField(child, index, childElements.length, "child")
    );
  }
  if (isEditableTextLeaf(el)) {
    return [buildTextField(el, 0, 1, "self")];
  }
  return [];
}
function escapeHtmlText(value) {
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}
function serializeTextFieldStyle(field) {
  const entries = Object.entries(field.inlineStyles).filter(([, value]) => Boolean(value));
  if (entries.length === 0) return "";
  return entries.map(([key, value]) => `${key}: ${value}`).join("; ");
}
function serializeDomEditTextFields(fields) {
  return fields.filter((field) => field.source === "child" || field.source === "text-node").map((field) => {
    if (field.source === "text-node") {
      return escapeHtmlText(field.value);
    }
    const attrs = [
      ...field.attributes.filter((attribute) => attribute.name !== "data-hf-text-key"),
      { name: "data-hf-text-key", value: field.key }
    ].map((attribute) => ` ${attribute.name}="${attribute.value.replace(/"/g, "&quot;")}"`).join("");
    const style = serializeTextFieldStyle(field);
    const styleAttr = style ? ` style="${style.replace(/"/g, "&quot;")}"` : "";
    return `<${field.tagName}${attrs}${styleAttr}>${escapeHtmlText(field.value)}</${field.tagName}>`;
  }).join("");
}
function buildDefaultDomEditTextField(base) {
  return {
    key: `child:new:${Date.now()}`,
    label: "Text",
    value: "New text",
    tagName: "span",
    attributes: [],
    inlineStyles: {
      "font-family": base?.computedStyles?.["font-family"] ?? "inherit",
      "font-size": base?.computedStyles?.["font-size"] ?? "16px",
      "font-weight": base?.computedStyles?.["font-weight"] ?? "400",
      color: base?.computedStyles?.color ?? "inherit"
    },
    computedStyles: {},
    source: "child"
  };
}
function resolveDomEditCapabilities(args) {
  if (!args.selector && !args.hfId || args.isInsideLockedComposition) {
    return {
      canSelect: !args.isInsideLockedComposition,
      canEditStyles: false,
      canMove: false,
      canResize: false,
      canApplyManualOffset: false,
      canApplyManualSize: false,
      canApplyManualRotation: false,
      reasonIfDisabled: args.isInsideLockedComposition ? "This element belongs to a locked composition." : "Studio could not resolve a stable patch target for this element."
    };
  }
  if (args.existsInSource === false) {
    return {
      canSelect: true,
      canEditStyles: false,
      canMove: false,
      canResize: false,
      canApplyManualOffset: false,
      canApplyManualSize: false,
      canApplyManualRotation: false,
      reasonIfDisabled: "This element is generated by a script and cannot be edited visually."
    };
  }
  if (args.isCompositionRoot) {
    return {
      canSelect: true,
      canEditStyles: true,
      canMove: false,
      canResize: false,
      canApplyManualOffset: false,
      canApplyManualSize: false,
      canApplyManualRotation: false,
      reasonIfDisabled: "The root composition defines the preview bounds."
    };
  }
  const position = args.computedStyles.position;
  const left = parsePx(args.inlineStyles.left) ?? parsePx(args.computedStyles.left);
  const top = parsePx(args.inlineStyles.top) ?? parsePx(args.computedStyles.top);
  const width = parsePx(args.inlineStyles.width) ?? parsePx(args.computedStyles.width);
  const height = parsePx(args.inlineStyles.height) ?? parsePx(args.computedStyles.height);
  const hasTransformDrivenGeometry = !isIdentityTransform(args.computedStyles.transform);
  const canMove = (position === "absolute" || position === "fixed") && left != null && top != null && !hasTransformDrivenGeometry;
  const canResize = canMove && (width != null || height != null);
  const canApplyManualGeometry = !args.isCompositionHost;
  const canApplyManualOffset = canApplyManualGeometry;
  const canApplyManualSize = canApplyManualGeometry;
  const canApplyManualRotation = canApplyManualGeometry;
  const reasonIfDisabled = canApplyManualGeometry ? void 0 : "Select an internal layer to transform it.";
  if (args.isCompositionHost && args.isMasterView) {
    return {
      canSelect: true,
      canEditStyles: false,
      canMove,
      canResize,
      canApplyManualOffset,
      canApplyManualSize,
      canApplyManualRotation,
      reasonIfDisabled
    };
  }
  return {
    canSelect: true,
    canEditStyles: true,
    canMove,
    canResize,
    canApplyManualOffset,
    canApplyManualSize,
    canApplyManualRotation,
    reasonIfDisabled
  };
}
function buildElementLabel(el) {
  const compositionId = el.getAttribute("data-composition-id");
  if (compositionId && compositionId !== "main") {
    return humanizeIdentifier(compositionId);
  }
  const compositionSrc = el.getAttribute("data-composition-src") ?? el.getAttribute("data-composition-file");
  if (compositionSrc) {
    return humanizeIdentifier(compositionSrc);
  }
  if (el.id) return humanizeIdentifier(el.id);
  const preferredClass = getPreferredClassSelector(el);
  if (preferredClass) {
    return humanizeIdentifier(preferredClass.replace(/^\./, ""));
  }
  const text = (el.textContent ?? "").trim().replace(/\s+/g, " ");
  if (text) return text.length > 40 ? `${text.slice(0, 39)}\u2026` : text;
  return el.tagName.toLowerCase();
}
async function probeSourceElement(projectId, sourceFile, target) {
  try {
    const response = await fetch(
      `/api/projects/${projectId}/file-mutations/probe-element/${encodeURIComponent(sourceFile)}`,
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ target })
      }
    );
    if (!response.ok) return true;
    const data = await response.json();
    return data.exists !== false;
  } catch {
    return true;
  }
}
async function resolveDomEditSelection(startEl, options) {
  if (!startEl) return null;
  const doc = startEl.ownerDocument;
  let current = getSelectionCandidate(startEl, options);
  while (current && current !== doc.body && current !== doc.documentElement) {
    const selector = buildStableSelector(current);
    const hfId = readHfId(current);
    if (!selector && !hfId) {
      current = current.parentElement;
      continue;
    }
    const { sourceFile, compositionPath } = getSourceFileForElement(
      current,
      options.activeCompositionPath
    );
    const selectorIndex = selector ? getSelectorIndex(doc, current, selector, sourceFile, options.activeCompositionPath) : void 0;
    const compositionSrc = current.getAttribute("data-composition-src") ?? current.getAttribute("data-composition-file") ?? void 0;
    const inlineStyles = getInlineStyles(current);
    const computedStyles = getCuratedComputedStyles(current);
    const isCompositionRoot = current.hasAttribute("data-composition-id") && !compositionSrc || isCompositionRootLayer(current, doc, computedStyles);
    const textFields = collectDomEditTextFields(current);
    const isInsideLocked = Boolean(findClosestByAttribute(current, ["data-timeline-locked"]));
    let existsInSource;
    if (!options.skipSourceProbe && options.projectId && (current.id || selector || hfId)) {
      const probeTarget = {};
      if (current.id) probeTarget.id = current.id;
      if (hfId) probeTarget.hfId = hfId;
      if (selector) probeTarget.selector = selector;
      if (selectorIndex != null) probeTarget.selectorIndex = selectorIndex;
      existsInSource = await probeSourceElement(options.projectId, sourceFile, probeTarget);
    }
    const capabilities = resolveDomEditCapabilities({
      selector,
      hfId,
      tagName: current.tagName.toLowerCase(),
      className: current.className,
      inlineStyles,
      computedStyles,
      isCompositionHost: Boolean(compositionSrc),
      isCompositionRoot,
      isInsideLockedComposition: isInsideLocked,
      isMasterView: options.isMasterView,
      existsInSource
    });
    const rect = current.getBoundingClientRect();
    return {
      element: current,
      id: current.id || void 0,
      hfId,
      selector,
      selectorIndex,
      sourceFile,
      compositionPath,
      compositionSrc,
      isCompositionHost: Boolean(compositionSrc),
      isInsideLockedComposition: isInsideLocked,
      label: buildElementLabel(current),
      tagName: current.tagName.toLowerCase(),
      boundingBox: {
        x: rect.left,
        y: rect.top,
        width: rect.width,
        height: rect.height
      },
      textContent: current.textContent?.trim() || null,
      dataAttributes: getDataAttributes(current),
      inlineStyles,
      computedStyles,
      textFields,
      capabilities
    };
  }
  return null;
}
async function refreshDomEditSelection(selection, activeCompositionPath) {
  const doc = selection.element.ownerDocument;
  const nextElement = findElementForSelection(doc, selection, activeCompositionPath);
  return nextElement ? resolveDomEditSelection(nextElement, {
    activeCompositionPath,
    isMasterView: !activeCompositionPath || activeCompositionPath === "index.html"
  }) : null;
}
function getDomEditLayerKey(target) {
  const selectorIndex = target.selectorIndex ?? 0;
  return `${target.sourceFile}:${target.id ?? target.selector ?? "layer"}:${selectorIndex}`;
}
function countDomEditChildLayers(root, options, maxCount = 99) {
  if (!root) return 0;
  let count = 0;
  const visit = (el) => {
    for (const child of Array.from(el.children)) {
      if (!isHtmlElement(child)) continue;
      if (getDomLayerPatchTarget(child, options.activeCompositionPath)) {
        count += 1;
        if (count >= maxCount) return;
      }
      visit(child);
      if (count >= maxCount) return;
    }
  };
  visit(root);
  return count;
}
function collectDomEditLayerItems(root, options, maxItems = 80) {
  if (!root) return [];
  const items = [];
  const visit = (el, depth) => {
    if (items.length >= maxItems) return;
    const target = getDomLayerPatchTarget(el, options.activeCompositionPath);
    if (target) {
      items.push({
        key: getDomEditLayerKey(target),
        element: el,
        label: buildElementLabel(el),
        tagName: el.tagName.toLowerCase(),
        depth,
        childCount: getDirectLayerChildren(el, options).length,
        id: target.id ?? void 0,
        hfId: target.hfId ?? void 0,
        selector: target.selector ?? void 0,
        selectorIndex: target.selectorIndex,
        sourceFile: target.sourceFile
      });
    }
    const nextDepth = target ? depth + 1 : depth;
    for (const child of Array.from(el.children)) {
      if (!isHtmlElement(child)) continue;
      visit(child, nextDepth);
      if (items.length >= maxItems) return;
    }
  };
  visit(root, 0);
  return items;
}
function buildDomEditStylePatchOperation(property, value) {
  return {
    type: "inline-style",
    property,
    value
  };
}
function buildDomEditTextPatchOperation(value) {
  return {
    type: "text-content",
    property: "text",
    value
  };
}
function hasSupportedDirectEdit(capabilities) {
  return capabilities.canEditStyles || capabilities.canMove || capabilities.canResize || capabilities.canApplyManualOffset || capabilities.canApplyManualSize || capabilities.canApplyManualRotation;
}
function getDomEditNonEditableReason(element, selection) {
  if (!selection) {
    return "No stable source target";
  }
  if (selection.element !== element) {
    return selection.isCompositionHost ? "Nested composition boundary" : `Selection resolves to ${selection.label}`;
  }
  if (!hasSupportedDirectEdit(selection.capabilities)) {
    return selection.capabilities.reasonIfDisabled ?? "No supported direct edits";
  }
  return null;
}
function getDomEditTargetKey(selection) {
  return [
    selection.sourceFile || "index.html",
    selection.hfId ?? "",
    selection.id ?? "",
    selection.selector ?? "",
    selection.selectorIndex ?? ""
  ].join("|");
}
function isTextEditableSelection(selection) {
  return selection.textFields.length > 0 && !selection.isCompositionHost && !selection.isInsideLockedComposition;
}
function readHfId(element) {
  return element.getAttribute("data-hf-id")?.trim() || void 0;
}
function buildDomEditPatchTarget(selection) {
  return {
    id: selection.id,
    hfId: selection.hfId,
    selector: selection.selector,
    selectorIndex: selection.selectorIndex
  };
}

export {
  isElementVisibleThroughAncestors,
  setCompositionSourceMap,
  isElementComputedVisible,
  getDomLayerPatchTarget,
  resolveAllVisualDomEditTargets,
  findElementForSelection,
  findElementForTimelineElement,
  isEditableTextLeaf,
  collectDomEditTextFields,
  serializeDomEditTextFields,
  buildDefaultDomEditTextField,
  resolveDomEditCapabilities,
  buildElementLabel,
  resolveDomEditSelection,
  refreshDomEditSelection,
  getDomEditLayerKey,
  countDomEditChildLayers,
  collectDomEditLayerItems,
  buildDomEditStylePatchOperation,
  buildDomEditTextPatchOperation,
  getDomEditNonEditableReason,
  getDomEditTargetKey,
  isTextEditableSelection,
  readHfId,
  buildDomEditPatchTarget
};
//# sourceMappingURL=chunk-KZXYQYIU.js.map