/* ============================================================================
 * 서명전에 — TDS ListRow 컴포넌트
 *  · 목록의 기본 단위 "한 줄" 컴포넌트. 내부는 좌(left) / 본문(contents) / 우(right)
 *    3단 구조이고, 각 슬롯에 서브 컴포넌트를 꽂아 쓰는 조합식이에요.
 *  · 서브 컴포넌트:
 *      ListRow.Loader       — 로딩 스켈레톤 (shape = square/circle/bar)
 *      ListRow.AssetIcon    — SVG 아이콘 (mono 지원)
 *      ListRow.AssetImage   — 비트맵 이미지
 *      ListRow.AssetLottie  — Lottie 애니메이션 자리(웹은 플레이스홀더)
 *      ListRow.AssetText    — 뱃지 형태 텍스트
 *      ListRow.IconButton   — 우측 클릭 가능한 아이콘 버튼
 *      ListRow.Texts        — 제목/중간/보조 텍스트 타입 25종
 *
 *  · 메인 ListRow props (ListRowProps):
 *      left              ReactNode — Asset* 또는 Loader 권장
 *      contents*         ReactNode — 보통 <ListRow.Texts ... />
 *      right             ReactNode — AssetText / IconButton / ListRow.Texts(Right*)
 *      withArrow         boolean   — 우측 끝에 ▶ chevron 표시
 *      border            "indented" | "none"   (기본 "indented")
 *      horizontalPadding "small" | "medium"    (기본 "medium")
 *      verticalPadding   "small" | "medium" | "large" | "xlarge" (기본 "medium")
 *      align             "center" | "top"      (기본 "center")
 *      onClick           (e) => void           — 있으면 클릭 가능 상태
 *      disabled          boolean               — 비활성(기본 false)
 *      disabledType      "type1" | "type2"     (기본 "type1" — 완전 회색 / type2 — 액션만 억제)
 *      className, style, aria-* ...
 *
 *  · Ref API:
 *      const ref = useRef();
 *      <ListRow ref={ref} ... />
 *      ref.current.shine(1.6);   // 1.6s 동안 가로지르는 반짝임
 *      ref.current.blink(1.2);   // 1.2s 동안 배경색 깜빡임
 *    각각 CSS 클래스(`tds-listrow--shine` / `tds-listrow--blink`) 토글 후
 *    setTimeout 으로 해제해요. CSS 변수(`--tds-listrow-shine-dur` /
 *    `--tds-listrow-blink-dur`) 로 시간을 연동.
 *
 *  · 접근성:
 *      - onClick 이 있으면 <div role="button" tabIndex="0"> + Enter/Space 키보드 지원
 *      - disabled 면 aria-disabled="true" + 클릭 비활성
 *      - withArrow 의 ▶ 는 aria-hidden
 *      - AssetIcon/Image/Lottie 는 acc(=accessibility label) 를 반드시 받아 aria-label 로 투사
 *
 *  · 구현 노트:
 *      - Babel Standalone 전역 스코프 충돌 방지를 위해 React 훅은
 *        `__lrUseRef`, `__lrUseImperativeHandle`, `__lrUseState` 로 프리픽스.
 *      - forwardRef 사용. 메인 컴포넌트만 ref 를 받음.
 *      - Asset 계열은 `shape` 에 따라 배경/라운드/가로폭 달라지는데, 우리 CSS 는
 *        `.tds-listrow-asset--{shape}` + `.tds-listrow-asset--{size}` 조합으로 처리.
 *      - Texts 는 type 이름이 "Right" 로 시작하면 자동으로
 *        `tds-listrow-texts--right` 클래스를 붙여 오른쪽 정렬.
 *      - window.ListRow / window.List 로 글로벌 등록.
 * ========================================================================== */

// React 훅 프리픽스 (Babel Standalone 글로벌 충돌 방지)
const __lrUseRef              = (typeof React !== "undefined" && React.useRef) || function () { return { current: null }; };
const __lrUseImperativeHandle = (typeof React !== "undefined" && React.useImperativeHandle) || function () {};
const __lrUseState            = (typeof React !== "undefined" && React.useState) || function (v) { return [v, function () {}]; };
const __lrUseEffect           = (typeof React !== "undefined" && React.useEffect) || function () {};
const __lrForwardRef          = (typeof React !== "undefined" && React.forwardRef) || function (fn) { return fn; };

/* ──────────────────────────────────────────────────────────────────────────
 * 로컬 유틸
 * ────────────────────────────────────────────────────────────────────────── */
function __lrWarn(msg) {
  if (typeof console !== "undefined" && console.warn) console.warn("[ListRow] " + msg);
}

function __lrJoin() {
  const parts = [];
  for (let i = 0; i < arguments.length; i += 1) {
    const v = arguments[i];
    if (v) parts.push(v);
  }
  return parts.join(" ");
}

function __lrSizeCss(v, fallback) {
  if (typeof v === "number" && Number.isFinite(v)) return v + "px";
  if (typeof v === "string" && v.length > 0) return v;
  return fallback;
}

/* 정해진 값만 통과시키는 enum guard */
function __lrEnum(value, allowed, fallback, propPath) {
  if (value == null) return fallback;
  if (allowed.indexOf(value) === -1) {
    __lrWarn(propPath + " 는 " + JSON.stringify(allowed) + " 중 하나여야 해요. 받은 값: " +
      JSON.stringify(value) + " — '" + fallback + "' 로 폴백.");
    return fallback;
  }
  return value;
}

/* Chevron — IOSIcon 이 있으면 위임, 아니면 inline SVG */
function __lrChevronRight(size) {
  const s = typeof size === "number" && Number.isFinite(size) ? size : 14;
  if (typeof window !== "undefined" && typeof window.IOSIcon === "function") {
    const _IOSIcon = window.IOSIcon;
    return <_IOSIcon name="chev-right" size={s} />;
  }
  return (
    <svg
      width={s}
      height={s}
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth="2.2"
      strokeLinecap="round"
      strokeLinejoin="round"
      aria-hidden="true"
    >
      <path d="M9 6l6 6-6 6" />
    </svg>
  );
}

/* ==========================================================================
 * List — ListRow 들의 컨테이너 (첫 행 구분선 숨김 책임)
 * ========================================================================== */
function List({ children, className = "", style, ...rest }) {
  return (
    <div className={__lrJoin("tds-list", className)} style={style} {...rest}>
      {children}
    </div>
  );
}

/* ==========================================================================
 * ListRow.Loader — 로딩 스켈레톤
 *   props:
 *     type*           "square" | "circle" | "bar"
 *     verticalPadding "extraSmall" | "small" | "medium" | "large"  (기본 "medium")
 * ========================================================================== */
function ListRowLoader({
  type,
  verticalPadding = "medium",
  className = "",
  style,
  ...rest
}) {
  const safeType = __lrEnum(type, ["square", "circle", "bar"], "square", "ListRow.Loader.type");
  const safeVp   = __lrEnum(verticalPadding, ["extraSmall", "small", "medium", "large"], "medium", "ListRow.Loader.verticalPadding");
  const cls = __lrJoin(
    "tds-listrow-loader",
    "tds-listrow-loader--vp-" + safeVp,
    className
  );
  return (
    <div className={cls} style={style} role="status" aria-live="polite" aria-label="불러오는 중" {...rest}>
      <div className={"tds-listrow-loader__shape tds-listrow-loader__shape--" + safeType} />
      <div className="tds-listrow-loader__lines">
        <div className="tds-listrow-loader__line tds-listrow-loader__line--long" />
        <div className="tds-listrow-loader__line tds-listrow-loader__line--short" />
      </div>
    </div>
  );
}

/* ==========================================================================
 * ListRow.AssetIcon — SVG 아이콘
 *   props:
 *     name/url/src    string — 아이콘 경로 (셋 중 하나). `-mono.svg` 로 끝나면 mono 모드.
 *     shape           "squircle" | "card" | "circle" | "square" | "original"
 *                     | "circle-background" | "circle-masking" (기본 "squircle")
 *     size            "xsmall" | "small" | "medium" (기본 "small")
 *     color           string — mono 아이콘의 색 (CSS color)
 *     backgroundColor string — 배경색 (shape 이 있을 때만 의미)
 *     variant         "fill" | "none" (기본 "fill" — none 이면 배경 투명)
 *     acc             { node, position, masking } — 우측 상/하단 작은 뱃지
 *     ariaLabel       string — 스크린리더용. 없으면 aria-hidden 처리.
 * ========================================================================== */
function __lrAssetBase({
  shape = "squircle",
  size = "small",
  backgroundColor,
  color,
  variant = "fill",
  acc,
  children,
  ariaLabel,
  className = "",
  style,
  extraAssetStyle,
  ...rest
}) {
  const safeShape = __lrEnum(
    shape,
    ["squircle", "card", "circle", "square", "original", "circle-background", "circle-masking"],
    "squircle",
    "AssetShape"
  );
  const safeSize = __lrEnum(size, ["xsmall", "small", "medium"], "small", "AssetSize");
  const safeVariant = __lrEnum(variant, ["fill", "none"], "fill", "variant");

  const rootStyle = { ...(style || {}), ...(extraAssetStyle || {}) };
  if (backgroundColor && safeVariant === "fill") rootStyle["--tds-listrow-asset-bg"] = backgroundColor;
  if (safeVariant === "none") rootStyle.background = "transparent";
  if (color) rootStyle["--tds-listrow-asseticon-color"] = color;

  const cls = __lrJoin(
    "tds-listrow-asset",
    "tds-listrow-asset--" + safeShape,
    "tds-listrow-asset--" + safeSize,
    className
  );

  // acc 슬롯 렌더 — 우측 상/하단 작은 뱃지
  let accNode = null;
  if (acc && acc.node != null) {
    const accPos = __lrEnum(acc.position, ["top-right", "bottom-right"], "bottom-right", "acc.position");
    const accMask = __lrEnum(acc.masking, ["circle", "none"], "none", "acc.masking");
    const accCls = __lrJoin(
      "tds-listrow-asset__acc",
      "tds-listrow-asset__acc--" + accPos,
      accMask === "circle" ? "tds-listrow-asset__acc--masking-circle" : ""
    );
    accNode = <span className={accCls}>{acc.node}</span>;
  }

  const hasLabel = typeof ariaLabel === "string" && ariaLabel.length > 0;

  return (
    <div
      className={cls}
      style={rootStyle}
      role={hasLabel ? "img" : undefined}
      aria-label={hasLabel ? ariaLabel : undefined}
      aria-hidden={hasLabel ? undefined : true}
      {...rest}
    >
      {children}
      {accNode}
    </div>
  );
}

function ListRowAssetIcon({
  name,
  url,
  src,
  shape = "squircle",
  size = "small",
  color,
  backgroundColor,
  variant = "fill",
  acc,
  ariaLabel,
  className,
  style,
  ...rest
}) {
  const href = src || url || name;
  if (!href) __lrWarn("AssetIcon: `src`(또는 url/name) 이 필요해요.");
  const isMono = typeof href === "string" && /-mono\.svg(\?.*)?$/i.test(href);

  let inner;
  if (!href) {
    inner = null;
  } else if (isMono) {
    inner = (
      <span
        className="tds-listrow-asseticon__mono"
        style={{ WebkitMaskImage: "url(" + href + ")", maskImage: "url(" + href + ")" }}
      />
    );
  } else {
    inner = <img className="tds-listrow-asseticon__svg" src={href} alt="" />;
  }

  return __lrAssetBase({
    shape,
    size,
    backgroundColor,
    color,
    variant,
    acc,
    ariaLabel,
    className,
    style,
    children: inner,
    ...rest,
  });
}

/* ==========================================================================
 * ListRow.AssetImage — 비트맵 이미지
 *   props:
 *     src*           string
 *     shape          같은 5 variants (기본 "square")
 *     size           "xsmall" | "small" | "medium"
 *     scale          (0, 1] — 내부 이미지 스케일 (기본 1)
 *     scaleType      "fit" | "crop" (기본 "fit")
 *     backgroundColor string
 *     acc            같은 구조
 *     ariaLabel      string
 * ========================================================================== */
function ListRowAssetImage({
  src,
  shape = "square",
  size = "small",
  scale = 1,
  scaleType = "fit",
  backgroundColor,
  acc,
  ariaLabel,
  className,
  style,
  ...rest
}) {
  if (!src) __lrWarn("AssetImage: `src` 는 필수예요.");
  const safeScale = (typeof scale === "number" && Number.isFinite(scale) && scale > 0 && scale <= 1) ? scale : 1;
  const safeScaleType = __lrEnum(scaleType, ["fit", "crop"], "fit", "AssetImage.scaleType");

  const imgStyle = {
    objectFit: safeScaleType === "crop" ? "cover" : "contain",
    transform: safeScale !== 1 ? "scale(" + safeScale + ")" : undefined,
  };

  const inner = src ? (
    <img className="tds-listrow-assetimage__img" src={src} alt="" style={imgStyle} />
  ) : null;

  return __lrAssetBase({
    shape,
    size,
    backgroundColor,
    acc,
    ariaLabel,
    className,
    style,
    children: inner,
    ...rest,
  });
}

/* ==========================================================================
 * ListRow.AssetLottie — Lottie 자리 (웹엔 플레이어 없어 플레이스홀더)
 *   props:
 *     src*            string — JSON 경로 (로드는 실제 플레이어 통합 시)
 *     shape           기본 "square"
 *     size            기본 "small"
 *     backgroundColor string
 *     acc             동일
 *     ariaLabel       string
 * ========================================================================== */
function ListRowAssetLottie({
  src,
  shape = "square",
  size = "small",
  backgroundColor,
  acc,
  ariaLabel,
  className,
  style,
  ...rest
}) {
  if (!src) __lrWarn("AssetLottie: `src` 는 필수예요.");
  // 웹 환경에선 Lottie 플레이어를 동적으로 붙일 자리만 확보.
  const inner = (
    <div className="tds-listrow-assetlottie__inner" data-lottie-src={src || undefined} />
  );
  return __lrAssetBase({
    shape,
    size,
    backgroundColor,
    acc,
    ariaLabel,
    className,
    style,
    children: inner,
    ...rest,
  });
}

/* ==========================================================================
 * ListRow.AssetText — 뱃지 형태 텍스트 라벨
 *   props:
 *     children*        string
 *     shape            "squircle" | "card"  (기본 "squircle")
 *     size             "small" | "medium"   (기본 "small")
 *     color            string — 텍스트 색
 *     backgroundColor  string — 배경색
 *     acc              옵션
 *     ariaLabel        string
 * ========================================================================== */
function ListRowAssetText({
  children,
  shape = "squircle",
  size = "small",
  color,
  backgroundColor,
  acc,
  ariaLabel,
  className,
  style,
  ...rest
}) {
  if (children == null || children === "") __lrWarn("AssetText: children 이 비어 있어요.");
  const safeShape = __lrEnum(shape, ["squircle", "card"], "squircle", "AssetText.shape");
  const safeSize  = __lrEnum(size, ["small", "medium"], "small", "AssetText.size");

  const extraStyle = {};
  if (color) extraStyle["--tds-listrow-assettext-color"] = color;

  const inner = <span className="tds-listrow-assettext__label">{children}</span>;

  return __lrAssetBase({
    shape: safeShape,
    size: safeSize,
    backgroundColor,
    acc,
    ariaLabel,
    className: __lrJoin("tds-listrow-asset--padx", className),
    style,
    extraAssetStyle: extraStyle,
    children: inner,
    ...rest,
  });
}

/* ==========================================================================
 * ListRow.IconButton — 우측 클릭 가능한 아이콘 버튼
 *   props:
 *     src*           string — 아이콘 (mono 권장)
 *     onClick*       () => void
 *     variant        "clear" | "fill" | "border"  (기본 "clear")
 *     iconSize       number (px, 기본 24)
 *     size           number (px, 버튼 전체 크기 — 기본 36)
 *     color          string — 아이콘 색
 *     backgroundColor string — variant=fill 때 배경색
 *     ariaLabel / label  string — 접근성 라벨 (필수)
 *     disabled       boolean
 * ========================================================================== */
function ListRowIconButton({
  src,
  onClick,
  variant = "clear",
  iconSize = 24,
  size = 36,
  color,
  backgroundColor,
  ariaLabel,
  label,
  disabled = false,
  className = "",
  style,
  ...rest
}) {
  if (!src) __lrWarn("IconButton: `src` 는 필수예요.");
  if (typeof onClick !== "function") __lrWarn("IconButton: `onClick` 은 필수예요.");
  const safeVariant = __lrEnum(variant, ["clear", "fill", "border"], "clear", "IconButton.variant");
  const accLabel = ariaLabel || label;
  if (!accLabel) __lrWarn("IconButton: `ariaLabel`(또는 label) 이 비어 있어요 — 접근성 라벨을 넣어주세요.");

  const btnStyle = { ...(style || {}) };
  btnStyle.width = __lrSizeCss(size, "36px");
  btnStyle.height = __lrSizeCss(size, "36px");
  if (color) btnStyle["--tds-listrow-iconbutton-color"] = color;
  if (backgroundColor && safeVariant === "fill") btnStyle["--tds-listrow-iconbutton-bg"] = backgroundColor;
  btnStyle["--tds-listrow-iconbutton-size"] = __lrSizeCss(iconSize, "24px");

  const cls = __lrJoin(
    "tds-listrow-iconbutton",
    "tds-listrow-iconbutton--" + safeVariant,
    className
  );

  return (
    <button
      type="button"
      className={cls}
      style={btnStyle}
      onClick={onClick}
      disabled={disabled}
      aria-label={accLabel}
      {...rest}
    >
      <span
        className="tds-listrow-iconbutton__icon"
        style={{ WebkitMaskImage: "url(" + src + ")", maskImage: "url(" + src + ")" }}
      />
    </button>
  );
}

/* ==========================================================================
 * ListRow.Texts — 제목/중간/보조 텍스트 타입 25종
 *   props:
 *     type*      string — 1RowTypeA..C / Right1RowTypeA..E /
 *                         2RowTypeA..F / Right2RowTypeA..E /
 *                         3RowTypeA..F
 *     top        ReactNode
 *     middle     ReactNode
 *     bottom     ReactNode
 *     marginTop  number (px) — 전체 위 여백(콘텐츠 정렬용)
 *     color      string — 기본 top 색 오버라이드
 *     subColor   string — middle/bottom 색 오버라이드
 * ========================================================================== */
const __LR_TEXTS_TYPES = [
  "1RowTypeA", "1RowTypeB", "1RowTypeC",
  "Right1RowTypeA", "Right1RowTypeB", "Right1RowTypeC", "Right1RowTypeD", "Right1RowTypeE",
  "2RowTypeA", "2RowTypeB", "2RowTypeC", "2RowTypeD", "2RowTypeE", "2RowTypeF",
  "Right2RowTypeA", "Right2RowTypeB", "Right2RowTypeC", "Right2RowTypeD", "Right2RowTypeE",
  "3RowTypeA", "3RowTypeB", "3RowTypeC", "3RowTypeD", "3RowTypeE", "3RowTypeF",
];

function ListRowTexts({
  type,
  top,
  middle,
  bottom,
  marginTop,
  color,
  subColor,
  className = "",
  style,
  ...rest
}) {
  if (!type) __lrWarn("Texts.type 은 필수예요.");
  const safeType = __LR_TEXTS_TYPES.indexOf(type) !== -1 ? type : "1RowTypeA";
  const isRight = /^Right/.test(safeType);

  const cls = __lrJoin(
    "tds-listrow-texts",
    "tds-listrow-texts--" + safeType,
    isRight ? "tds-listrow-texts--right" : "",
    className
  );

  const rootStyle = { ...(style || {}) };
  if (typeof marginTop === "number" && Number.isFinite(marginTop)) rootStyle.marginTop = marginTop + "px";

  const topStyle = color ? { color } : undefined;
  const subStyle = subColor ? { color: subColor } : undefined;

  return (
    <div className={cls} style={rootStyle} {...rest}>
      {top != null && <span className="tds-listrow-texts__top" style={topStyle}>{top}</span>}
      {middle != null && <span className="tds-listrow-texts__middle" style={subStyle}>{middle}</span>}
      {bottom != null && <span className="tds-listrow-texts__bottom" style={subStyle}>{bottom}</span>}
    </div>
  );
}

/* ==========================================================================
 * 메인 ListRow — forwardRef 로 shine/blink ref API 노출
 * ========================================================================== */
const ListRow = __lrForwardRef(function ListRowInner(props, ref) {
  const {
    left,
    contents,
    right,
    withArrow = false,
    border = "indented",
    horizontalPadding = "medium",
    verticalPadding = "medium",
    align = "center",
    onClick,
    disabled = false,
    disabledType = "type1",
    className = "",
    style,
    ...rest
  } = props || {};

  if (contents == null) __lrWarn("`contents` 는 필수예요. ListRow.Texts 또는 ReactNode 를 넣어주세요.");

  const safeBorder = __lrEnum(border, ["indented", "none"], "indented", "border");
  const safeHp     = __lrEnum(horizontalPadding, ["small", "medium"], "medium", "horizontalPadding");
  const safeVp     = __lrEnum(verticalPadding, ["small", "medium", "large", "xlarge"], "medium", "verticalPadding");
  const safeAlign  = __lrEnum(align, ["center", "top"], "center", "align");
  const safeDisabledType = __lrEnum(disabledType, ["type1", "type2"], "type1", "disabledType");

  // Ref API: shine/blink — 클래스 토글 + setTimeout 해제
  const rootRef = __lrUseRef(null);
  const [shine, setShine] = __lrUseState(null); // duration(s) or null
  const [blink, setBlink] = __lrUseState(null);

  __lrUseEffect(function () {
    if (shine == null) return;
    const t = window.setTimeout(function () { setShine(null); }, shine * 1000 + 80);
    return function () { window.clearTimeout(t); };
  }, [shine]);
  __lrUseEffect(function () {
    if (blink == null) return;
    const t = window.setTimeout(function () { setBlink(null); }, blink * 1000 + 80);
    return function () { window.clearTimeout(t); };
  }, [blink]);

  __lrUseImperativeHandle(ref, function () {
    return {
      shine: function (seconds) {
        const s = typeof seconds === "number" && seconds > 0 ? seconds : 1.6;
        setShine(s);
      },
      blink: function (seconds) {
        const s = typeof seconds === "number" && seconds > 0 ? seconds : 1.2;
        setBlink(s);
      },
      el: function () { return rootRef.current; },
    };
  }, []);

  const clickable = typeof onClick === "function" && !disabled;

  const cls = __lrJoin(
    "tds-listrow",
    "tds-listrow--border-" + safeBorder,
    "tds-listrow--hp-" + safeHp,
    "tds-listrow--vp-" + safeVp,
    clickable ? "tds-listrow--clickable" : "",
    disabled ? "tds-listrow--disabled" : "",
    disabled ? "tds-listrow--disabled-" + safeDisabledType : "",
    shine != null ? "tds-listrow--shine" : "",
    blink != null ? "tds-listrow--blink" : "",
    className
  );

  const rootStyle = { ...(style || {}) };
  if (shine != null) rootStyle["--tds-listrow-shine-dur"] = shine + "s";
  if (blink != null) rootStyle["--tds-listrow-blink-dur"] = blink + "s";

  const handleKey = function (e) {
    if (!clickable) return;
    if (e.key === "Enter" || e.key === " " || e.key === "Spacebar") {
      e.preventDefault();
      try { onClick(e); } catch (err) {
        if (window.console && console.error) console.error("[ListRow] onClick threw:", err);
      }
    }
  };

  const handleClick = function (e) {
    if (!clickable) return;
    try { onClick(e); } catch (err) {
      if (window.console && console.error) console.error("[ListRow] onClick threw:", err);
    }
  };

  const innerCls = __lrJoin(
    "tds-listrow__inner",
    "tds-listrow__align-" + safeAlign
  );

  return (
    <div
      ref={rootRef}
      className={cls}
      style={rootStyle}
      onClick={clickable ? handleClick : undefined}
      onKeyDown={clickable ? handleKey : undefined}
      role={clickable ? "button" : undefined}
      tabIndex={clickable ? 0 : undefined}
      aria-disabled={disabled ? "true" : undefined}
      {...rest}
    >
      <div className={innerCls}>
        {left != null && <div className="tds-listrow__left">{left}</div>}
        <div className="tds-listrow__contents">{contents}</div>
        {right != null && <div className="tds-listrow__right">{right}</div>}
        {withArrow && (
          <div className="tds-listrow__arrow" aria-hidden="true">
            {__lrChevronRight(14)}
          </div>
        )}
      </div>
    </div>
  );
});

// 서브 컴포넌트 네임스페이스 부착
ListRow.Loader      = ListRowLoader;
ListRow.AssetIcon   = ListRowAssetIcon;
ListRow.AssetImage  = ListRowAssetImage;
ListRow.AssetLottie = ListRowAssetLottie;
ListRow.AssetText   = ListRowAssetText;
ListRow.IconButton  = ListRowIconButton;
ListRow.Texts       = ListRowTexts;

// 글로벌 등록
window.ListRow = ListRow;
window.List    = List;
