{"version":3,"file":"JDynamicTabs.vue.cjs","sources":["../../../../src/components/organisms/JDynamicTabs.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ref, computed, watch } from 'vue'\nimport JTabs from '@/components/molecules/JTabs.vue'\nimport { cn } from '@/lib/utils'\nimport type {\n  JDynamicTabsProps, \n  JDynamicTabsEmits, \n  JDynamicTabsMethods,\n  DynamicTab \n} from '@/types/dynamic-tabs.types'\n\n/**\n * JDynamicTabs - 동적 탭 컴포넌트 (organisms)\n * Dynamic Tabs Component\n * \n * @description\n * 사이드 메뉴 클릭으로 탭을 동적으로 추가/제거할 수 있는 탭 컴포넌트입니다.\n * \n * Features:\n * - 탭 동적 추가/제거\n * - 중복 탭 방지 (이미 있으면 활성화)\n * - 닫기 버튼으로 탭 제거\n * - 활성 탭 자동 재설정\n * - 최대 탭 개수 제한\n * \n * @example\n * const tabsRef = ref()\n * \n * const handleMenuClick = (menuItem) => {\n *   tabsRef.value?.addTab({\n *     id: menuItem.id,\n *     label: menuItem.label,\n *     component: menuItem.component,\n *     closable: true\n *   })\n * }\n */\n\nconst props = withDefaults(defineProps<JDynamicTabsProps>(), {\n  initialTabs: () => [],\n  maxTabs: 0, // 0 = unlimited\n  emptyMessage: '탭을 추가해주세요.',\n  styletype: 'default',\n})\n\nconst emit = defineEmits<JDynamicTabsEmits>()\n\n/**\n * 탭 목록 (내부 상태)\n * Tabs list (internal state)\n */\nconst tabs = ref<DynamicTab[]>(Array.isArray(props.initialTabs) ? [...props.initialTabs] : [])\n\n/**\n * 현재 활성화된 탭 ID (내부 상태)\n * Current active tab ID (internal state)\n */\nconst activeTabId = ref<string>(\n  props.defaultActiveId || (Array.isArray(props.initialTabs) && props.initialTabs.length > 0 ? props.initialTabs[0]?.id : '') || ''\n)\n\n/**\n * initialTabs가 변경되면 내부 상태 업데이트\n * Update internal state when initialTabs changes\n */\nwatch(() => props.initialTabs, (newTabs) => {\n  if (!newTabs) {\n    tabs.value = []\n    return\n  }\n  \n  if (Array.isArray(newTabs) && newTabs.length > 0) {\n    tabs.value = [...newTabs]\n    if (!activeTabId.value && newTabs[0]) {\n      activeTabId.value = newTabs[0].id\n    }\n  } else if (Array.isArray(newTabs)) {\n    // 빈 배열인 경우\n    tabs.value = []\n  } else {\n    // 배열이 아닌 경우\n    tabs.value = []\n  }\n}, { immediate: true })\n\n/**\n * 탭 추가\n * Add tab (if exists, activate it; otherwise, add to array and activate)\n * \n * @param tab - 추가할 탭 정보\n */\nconst addTab = (tab: DynamicTab) => {\n  // tabs.value가 배열이 아니면 초기화\n  if (!Array.isArray(tabs.value)) {\n    tabs.value = []\n  }\n  \n  // 이미 존재하는 탭인지 확인\n  const existingTab = tabs.value.find(t => t.id === tab.id)\n  \n  if (existingTab) {\n    // 이미 있으면 해당 탭 활성화\n    activateTab(tab.id)\n    return\n  }\n  \n  // 최대 탭 개수 체크\n  if (props.maxTabs > 0 && tabs.value.length >= props.maxTabs) {\n    console.warn(`최대 ${props.maxTabs}개의 탭만 열 수 있습니다.`)\n    return\n  }\n  \n  // 새 탭 추가\n  tabs.value.push({\n    ...tab,\n    closable: tab.closable !== false, // 기본값 true\n  })\n  \n  // 새로 추가된 탭 활성화\n  activateTab(tab.id)\n  \n  // 이벤트 발생\n  emit('tabAdd', tab)\n}\n\n/**\n * 탭 닫기\n * Close tab (remove from array and reset active tab)\n * \n * @param id - 닫을 탭 ID\n */\nconst closeTab = (id: string) => {\n  if (!Array.isArray(tabs.value)) {\n    return\n  }\n  \n  const tabIndex = tabs.value.findIndex(t => t.id === id)\n  \n  if (tabIndex === -1) return\n  \n  // 탭 제거\n  tabs.value.splice(tabIndex, 1)\n  \n  // 활성 탭 재설정\n  if (activeTabId.value === id) {\n    if (tabs.value.length > 0) {\n      // 이전 탭이 있으면 이전 탭 활성화, 없으면 다음 탭 활성화\n      const newIndex = Math.min(tabIndex, tabs.value.length - 1)\n      activeTabId.value = tabs.value[newIndex]?.id || ''\n    } else {\n      activeTabId.value = ''\n    }\n  }\n  \n  // 이벤트 발생\n  emit('tabClose', id)\n}\n\n/**\n * 탭 활성화\n * Activate tab\n * \n * @param id - 활성화할 탭 ID\n */\nconst activateTab = (id: string) => {\n  if (!Array.isArray(tabs.value)) {\n    return\n  }\n  const tab = tabs.value.find(t => t.id === id)\n  if (tab) {\n    activeTabId.value = id\n  }\n}\n\n/**\n * 특정 탭 찾기\n * Find specific tab\n * \n * @param id - 찾을 탭 ID\n */\nconst findTab = (id: string): DynamicTab | undefined => {\n  if (!Array.isArray(tabs.value)) {\n    return undefined\n  }\n  return tabs.value.find(t => t.id === id)\n}\n\n/**\n * 모든 탭 닫기 (closable이 true인 탭만)\n * Close all tabs (only closable tabs)\n */\nconst closeAllTabs = () => {\n  if (!Array.isArray(tabs.value)) {\n    return\n  }\n  const closableTabs = tabs.value.filter(t => t.closable)\n  closableTabs.forEach(tab => closeTab(tab.id))\n}\n\n/**\n * 탭 변경 핸들러\n * Tab change handler\n */\nconst handleTabChange = (id: string) => {\n  activeTabId.value = id\n  emit('tabChange', id)\n}\n\n/**\n * 탭 닫기 핸들러\n * Tab close handler\n */\nconst handleTabClose = (id: string) => {\n  closeTab(id)\n}\n\n/**\n * 외부에서 호출 가능한 메서드 노출\n * Expose methods for external use\n */\ndefineExpose<JDynamicTabsMethods>({\n  addTab,\n  closeTab,\n  activateTab,\n  findTab,\n  closeAllTabs,\n})\n\n/**\n * 안전한 tabs 배열\n * Safe tabs array\n */\nconst safeTabs = computed(() => {\n  return Array.isArray(tabs.value) ? tabs.value : []\n})\n\n/**\n * 탭이 있는지 여부\n * Whether there are tabs\n */\nconst hasTabs = computed(() => safeTabs.value.length > 0)\n\n/**\n * 루트 클래스\n * Root classes\n */\nconst rootClasses = computed(() => {\n  return cn('w-full h-full', props.class)\n})\n\n/**\n * 콘텐츠 클래스\n * Content classes\n */\nconst contentClasses = computed(() => {\n  return props.contentClass || ''\n})\n</script>\n\n<template>\n  <div :class=\"rootClasses\">\n    <!-- 탭이 있을 경우 / When there are tabs -->\n    <JTabs\n      v-if=\"hasTabs\"\n      :tabs=\"safeTabs\"\n      :active-tab-id=\"activeTabId\"\n      :styletype=\"props.styletype\"\n      :keep-alive=\"props.keepAlive\"\n      :class=\"contentClasses\"\n      @tab-change=\"handleTabChange\"\n      @tab-close=\"handleTabClose\"\n    >\n      <!-- 각 탭 콘텐츠 슬롯 전달 / Forward slots for each tab content -->\n      <template\n        v-for=\"tab in safeTabs\"\n        :key=\"tab.id\"\n        #[`content-${tab.id}`]=\"slotProps\"\n      >\n        <slot :name=\"`content-${tab.id}`\" v-bind=\"slotProps\" />\n      </template>\n    </JTabs>\n    \n    <!-- 탭이 없을 경우 / When there are no tabs -->\n    <div\n      v-else\n      class=\"flex items-center justify-center h-full w-full\"\n    >\n      <div class=\"text-center\">\n        <p class=\"text-muted-foreground text-lg\">{{ emptyMessage }}</p>\n        <p class=\"text-muted-foreground/60 text-sm mt-2\">\n          메뉴를 클릭하여 탭을 추가하세요.\n        </p>\n      </div>\n    </div>\n  </div>\n</template>\n"],"names":["props","__props","emit","__emit","tabs","ref","activeTabId","watch","newTabs","addTab","tab","t","activateTab","closeTab","id","tabIndex","newIndex","findTab","closeAllTabs","handleTabChange","handleTabClose","__expose","safeTabs","computed","hasTabs","rootClasses","cn","contentClasses","_createElementBlock","_createBlock","JTabs","_renderList","_withCtx","slotProps","_renderSlot","_ctx","_openBlock","_hoisted_1","_createElementVNode","_hoisted_2","_hoisted_3","_toDisplayString","_cache"],"mappings":"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"}