{"version":3,"file":"JDynamicMenuItem.vue.cjs","sources":["../../../../../src/components/organisms/JSidebarSimple/JDynamicMenuItem.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { useRouter } from 'vue-router'\nimport type { SidebarMenuItem, MenuPermission, MenuClickEvent } from '@/types/sidebar-menu.types'\nimport JIcon from '@/components/atoms/JIcon.vue'\nimport { cn, hasMenuPermission } from '@/lib/utils'\n\n/**\n * JDynamicMenuItem - 재귀적 메뉴 아이템 컴포넌트\n * Recursive Menu Item Component\n * \n * @description\n * 다단계 메뉴 구조를 재귀적으로 렌더링하는 컴포넌트입니다.\n * 폴더 타입 메뉴는 확장/축소가 가능하고, 링크 타입 메뉴는 클릭 시 라우팅합니다.\n */\n\ntype StyleType = 'default' | 'minimal'\n\nconst props = withDefaults(\n  defineProps<{\n    /** 메뉴 아이템 */\n    item: SidebarMenuItem\n    /** 메뉴 레벨 (들여쓰기용, 0부터 시작) */\n    level?: number\n    /** 권한 목록 */\n    permissions?: MenuPermission[]\n    /** 활성화된 메뉴 경로 */\n    activePath?: string\n    /** 확장된 메뉴 키 목록 */\n    expandedKeys?: Set<number | string>\n    /** 즐겨찾기 메뉴 키 목록 */\n    favorites?: (number | string)[]\n    /** 즐겨찾기 변경 핸들러 */\n    onFavoriteToggle?: (menuKey: number | string | undefined) => void\n    /** 즐겨찾기 확인 함수 */\n    isFavorite?: (menuKey: number | string | undefined) => boolean\n    /** 스타일 타입 */\n    styletype?: StyleType\n    /** 추가 CSS 클래스 */\n    className?: string\n    /** 최대 깊이 제한 (무한 루프 방지, 기본값: 10) */\n    maxDepth?: number\n    /** 네비게이션 비활성화 (true일 때 router.push 건너뛰고 emit만 수행) */\n    disableNavigation?: boolean\n    /** 활성화된 메뉴 키 (menuKey 기반 활성화, activePath보다 우선) */\n    activeKey?: number | string | null\n  }>(),\n  {\n    level: 0,\n    permissions: () => [],\n    expandedKeys: () => new Set(),\n    favorites: () => [],\n    styletype: 'default',\n    maxDepth: 10,\n    disableNavigation: false,\n    activeKey: null,\n  },\n)\n\nconst emit = defineEmits<{\n  /** 메뉴 클릭 이벤트 */\n  menuClick: [event: MenuClickEvent]\n  /** 확장 상태 변경 이벤트 */\n  expandChange: [menuKey: number | string | undefined, expanded: boolean]\n}>()\n\nconst router = useRouter()\n\n/**\n * 권한 체크 함수\n * Permission check function\n * hasMenuPermission 유틸리티 함수를 사용하여 일관성 유지\n */\nconst checkPermission = computed(() => {\n  return hasMenuPermission(props.item.menuKey, props.permissions)\n})\n\n/**\n * 메뉴가 활성화되어 있는지 여부\n * activeKey가 제공되면 menuKey 매칭, 아니면 경로 매칭\n */\nconst isActive = computed(() => {\n  // activeKey가 제공되면 menuKey 기반 매칭 (우선순위 높음)\n  if (props.activeKey !== undefined && props.activeKey !== null) {\n    return props.item.menuKey === props.activeKey\n  }\n  // 경로 기반 매칭 (기본 동작)\n  if (!props.item.path || !props.activePath) return false\n  return props.activePath === props.item.path\n})\n\n/**\n * 메뉴 타입이 폴더인지 여부\n * 순환 참조 방지: children이 유효한 배열인지 확인\n */\nconst isFolder = computed(() => {\n  return props.item.menuType === 'F' || (Array.isArray(props.item.children) && props.item.children.length > 0)\n})\n\n/**\n * 메뉴가 확장되어 있는지 여부\n */\nconst isExpanded = computed(() => {\n  if (!isFolder.value) return false\n  const key = props.item.menuKey || props.item.label\n  return props.expandedKeys?.has(key) ?? false\n})\n\n/**\n * 메뉴가 비활성화되어 있는지 여부\n */\nconst isDisabled = computed(() => {\n  return props.item.disabled || !checkPermission.value\n})\n\n/**\n * 레벨별 들여쓰기 스타일\n * 모든 레벨에서 동일한 padding 사용 (들여쓰기는 컨테이너의 ml로 조절)\n */\nconst indentStyle = computed(() => {\n  return { paddingLeft: '8px', paddingRight: '8px' }\n})\n\n\n\n/**\n * 메뉴 클릭 핸들러\n */\nconst handleMenuClick = (event: MouseEvent) => {\n  if (isDisabled.value) {\n    event.preventDefault()\n    return\n  }\n\n  if (isFolder.value) {\n    // 폴더 타입: 확장/축소 토글\n    const key = props.item.menuKey || props.item.label\n    const newExpanded = !isExpanded.value\n    emit('expandChange', key, newExpanded)\n    // 폴더도 메뉴 클릭 이벤트 발생\n    emit('menuClick', {\n      menuItem: props.item,\n      path: [props.item],\n      event,\n    })\n  } else {\n    // 링크 타입: 라우팅 (disableNavigation이 false일 때만)\n    if (!props.disableNavigation && props.item.path) {\n      router.push(props.item.path)\n    }\n    \n    // 메뉴 클릭 이벤트 발생\n    emit('menuClick', {\n      menuItem: props.item,\n      path: [props.item], // 단순화된 경로 (필요시 부모 경로 포함하도록 확장 가능)\n      event,\n    })\n  }\n}\n\n/**\n * 스타일 프리셋\n */\nconst STYLE_PRESETS: Record<StyleType, {\n  itemClass: string\n  labelClass: string\n  iconSize: 'sm' | 'md'\n}> = {\n  default: {\n    itemClass: 'flex items-center gap-1.5 py-0.5 rounded-md cursor-pointer transition-colors group',\n    labelClass: 'flex-1 truncate',\n    iconSize: 'sm',\n  },\n  minimal: {\n    itemClass: 'flex items-center gap-1 py-0.5 rounded-md cursor-pointer transition-colors group',\n    labelClass: 'flex-1 truncate',\n    iconSize: 'sm', // JIcon은 'xs'를 지원하지 않으므로 'sm' 사용\n  },\n}\n\nconst preset = computed(() => {\n  return STYLE_PRESETS[props.styletype] ?? STYLE_PRESETS.default\n})\n\n/**\n * Chevron 아이콘 컴포넌트\n */\nconst ChevronIcon = computed(() => {\n  return isExpanded.value ? 'chevronDown' : 'chevronRight'\n})\n\n/**\n * 하위 메뉴 갯수 계산 (재귀적으로 모든 하위 메뉴 포함)\n * 폴더 타입인 경우에만 표시\n */\nconst childrenCount = computed(() => {\n  if (!isFolder.value || !Array.isArray(props.item.children)) {\n    return 0\n  }\n  \n  const countChildren = (items: SidebarMenuItem[]): number => {\n    let count = 0\n    for (const item of items) {\n      count++ // 현재 아이템 카운트\n      if (Array.isArray(item.children) && item.children.length > 0) {\n        count += countChildren(item.children) // 재귀적으로 하위 메뉴 카운트\n      }\n    }\n    return count\n  }\n  \n  return countChildren(props.item.children)\n})\n</script>\n\n<template>\n  <div :class=\"cn('w-full', className)\">\n    <!-- 메뉴 아이템 -->\n    <div\n      :class=\"cn(\n        preset.itemClass,\n        {\n          'bg-primary/5 text-primary border-l-2 border-primary shadow-sm': isActive,\n          'hover:bg-accent/50': !isDisabled && !isActive,\n          'opacity-50 cursor-not-allowed': isDisabled,\n          'font-medium': isActive,\n          'font-semibold': isFolder, // 폴더인 경우 볼드체\n        }\n      )\"\n      :style=\"indentStyle\"\n      @click=\"handleMenuClick\"\n    >\n      <!-- Chevron 아이콘 (폴더 타입만, 덜 굵게) -->\n      <JIcon\n        v-if=\"isFolder\"\n        :name=\"ChevronIcon\"\n        :size=\"preset.iconSize\"\n        class=\"flex-shrink-0 opacity-60\"\n        style=\"stroke-width: 1.5;\"\n      />\n      <span v-else class=\"w-4 flex-shrink-0\" /> <!-- 폴더가 아닐 때 공간 확보 -->\n\n      <!-- 메뉴 아이콘 (폴더가 아닌 경우만 표시) -->\n      <JIcon\n        v-if=\"item.icon && !isFolder\"\n        :name=\"item.icon\"\n        :size=\"preset.iconSize\"\n        class=\"flex-shrink-0\"\n      />\n\n      <!-- 메뉴 라벨 (폴더: text-xs, 링크: text-[11px]) -->\n      <span :class=\"[preset.labelClass, isFolder ? 'text-xs' : 'text-[11px]']\">{{ item.label }}</span>\n      \n      <!-- 하위 메뉴 갯수 (폴더인 경우만) -->\n      <span\n        v-if=\"isFolder && childrenCount > 0\"\n        :class=\"cn(\n          'text-muted-foreground ml-1 flex-shrink-0',\n          props.styletype === 'minimal' ? 'text-[10px]' : 'text-xs'\n        )\"\n      >\n        ({{ childrenCount }})\n      </span>\n      \n      <!-- 즐겨찾기 버튼 (menuType이 L인 경우만) -->\n      <button\n        v-if=\"item.menuKey && item.menuType === 'L' && onFavoriteToggle\"\n        :class=\"cn(\n          'opacity-0 group-hover:opacity-100 transition-opacity hover:bg-accent rounded flex-shrink-0',\n          props.styletype === 'minimal' ? 'p-0.5' : 'p-1',\n          isFavorite && isFavorite(item.menuKey) && 'opacity-100'\n        )\"\n        @click.stop=\"onFavoriteToggle(item.menuKey)\"\n      >\n        <JIcon\n          :name=\"isFavorite && isFavorite(item.menuKey) ? 'star' : 'star'\"\n          :size=\"preset.iconSize\"\n          :class=\"isFavorite && isFavorite(item.menuKey) ? 'text-yellow-500 fill-yellow-500' : 'text-muted-foreground'\"\n        />\n      </button>\n    </div>\n\n    <!-- 하위 메뉴 (폴더 타입이고 확장된 경우) -->\n    <!-- 깊이 제한 체크: maxDepth를 초과하지 않는 경우에만 렌더링 -->\n    <div\n      v-if=\"isFolder && isExpanded && item.children && Array.isArray(item.children) && item.children.length > 0 && (level + 1) < maxDepth\"\n      class=\"border-l border-border/60 ml-[14px] pl-[6px]\"\n    >\n      <JDynamicMenuItem\n        v-for=\"(child, index) in item.children\"\n        :key=\"child.menuKey || child.label || index\"\n        :item=\"child\"\n        :level=\"level + 1\"\n        :max-depth=\"maxDepth\"\n        :permissions=\"permissions\"\n        :active-path=\"activePath\"\n        :expanded-keys=\"expandedKeys\"\n        :favorites=\"favorites\"\n        :on-favorite-toggle=\"onFavoriteToggle\"\n        :is-favorite=\"isFavorite\"\n        :styletype=\"styletype\"\n        :disable-navigation=\"disableNavigation\"\n        :active-key=\"activeKey\"\n        @menu-click=\"emit('menuClick', $event)\"\n        @expand-change=\"(menuKey, expanded) => emit('expandChange', menuKey, expanded)\"\n      />\n    </div>\n  </div>\n</template>\n"],"names":["props","__props","emit","__emit","router","useRouter","checkPermission","computed","hasMenuPermission","isActive","isFolder","isExpanded","key","isDisabled","indentStyle","handleMenuClick","event","newExpanded","STYLE_PRESETS","preset","ChevronIcon","childrenCount","countChildren","items","count","item","_createElementBlock","_normalizeClass","_unref","cn","_createElementVNode","_createBlock","JIcon","_openBlock","_hoisted_1","_toDisplayString","_cache","_withModifiers","$event","_createVNode","_hoisted_2","_Fragment","child","index","_component_JDynamicMenuItem","menuKey","expanded"],"mappings":"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"}