@j-solution/components 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/README.md +10 -11
  2. package/USAGE_GUIDE.md +1 -1
  3. package/assets/jwms-portal-frontend-BEvCuV-9.css +1 -0
  4. package/assets/styles/j-components.css +1 -1
  5. package/components/organisms/JSidebarSimple/JDynamicMenuItem.vue.cjs +1 -1
  6. package/components/organisms/JSidebarSimple/JDynamicMenuItem.vue.cjs.map +1 -1
  7. package/components/organisms/JSidebarSimple/JDynamicMenuItem.vue.js +45 -40
  8. package/components/organisms/JSidebarSimple/JDynamicMenuItem.vue.js.map +1 -1
  9. package/components/organisms/JSidebarSimple.vue.cjs +1 -1
  10. package/components/organisms/JSidebarSimple.vue.js +2 -2
  11. package/components/organisms/JSidebarSimple.vue2.cjs +1 -1
  12. package/components/organisms/JSidebarSimple.vue2.cjs.map +1 -1
  13. package/components/organisms/JSidebarSimple.vue2.js +40 -70
  14. package/components/organisms/JSidebarSimple.vue2.js.map +1 -1
  15. package/components/organisms/JTree.vue.cjs +2 -0
  16. package/components/organisms/JTree.vue.cjs.map +1 -0
  17. package/components/organisms/JTree.vue.js +83 -0
  18. package/components/organisms/JTree.vue.js.map +1 -0
  19. package/components/organisms/JTree.vue2.cjs +2 -0
  20. package/components/organisms/JTree.vue2.cjs.map +1 -0
  21. package/components/organisms/JTree.vue2.js +5 -0
  22. package/components/organisms/JTree.vue2.js.map +1 -0
  23. package/index.cjs +1 -1
  24. package/index.js +15 -13
  25. package/lib/menu-utils.cjs +2 -0
  26. package/lib/menu-utils.cjs.map +1 -0
  27. package/lib/menu-utils.js +33 -0
  28. package/lib/menu-utils.js.map +1 -0
  29. package/package.json +1 -1
  30. package/types/index.d.ts +89 -6
  31. package/assets/jwms-portal-frontend-D8DdrheA.css +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"JSidebarSimple.vue2.js","sources":["../../../../src/components/organisms/JSidebarSimple.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport { ref, computed, watch } from 'vue'\r\nimport { useRoute } from 'vue-router'\r\nimport type { SidebarMenuItem, MenuPermission, MenuClickEvent } from '@/types/sidebar-menu.types'\r\nimport JDynamicMenuItem from './JSidebarSimple/JDynamicMenuItem.vue'\r\nimport { cn } from '@/lib/utils'\r\n\r\n/**\r\n * JSidebarSimple - 간단한 사이드바 컴포넌트\r\n * Simple Sidebar Component\r\n * \r\n * @description\r\n * 다단계 메뉴 구조를 지원하는 기본 사이드바 컴포넌트입니다.\r\n * 권한 체크, 메뉴 검색 등의 기능을 제공합니다.\r\n * \r\n * @example\r\n * ```vue\r\n * <JSidebarSimple\r\n * :menu-items=\"menuItems\"\r\n * :permissions=\"userPermissions\"\r\n * @menu-click=\"handleMenuClick\"\r\n * />\r\n * ```\r\n * \r\n * @example JSON 메뉴 데이터 예시\r\n * ```json\r\n * [\r\n * {\r\n * \"label\": \"대시보드\",\r\n * \"icon\": \"house\",\r\n * \"menuType\": \"L\",\r\n * \"menuKey\": 1,\r\n * \"path\": \"/dashboard\"\r\n * },\r\n * {\r\n * \"label\": \"재고 관리\",\r\n * \"icon\": \"package\",\r\n * \"menuType\": \"F\",\r\n * \"menuKey\": 2,\r\n * \"children\": [\r\n * {\r\n * \"label\": \"재고 현황\",\r\n * \"menuType\": \"L\",\r\n * \"menuKey\": 21,\r\n * \"path\": \"/inventory/status\"\r\n * },\r\n * {\r\n * \"label\": \"입고 관리\",\r\n * \"menuType\": \"L\",\r\n * \"menuKey\": 22,\r\n * \"path\": \"/inventory/receiving\"\r\n * }\r\n * ]\r\n * }\r\n * ]\r\n * ```\r\n */\r\n\r\ntype StyleType = 'default' | 'minimal'\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n /** 메뉴 아이템 목록 */\r\n menuItems: SidebarMenuItem[]\r\n /** 권한 목록 */\r\n permissions?: MenuPermission[]\r\n /** 검색어 */\r\n searchQuery?: string\r\n /** 스타일 타입 */\r\n styletype?: StyleType\r\n /** 추가 CSS 클래스 */\r\n class?: string\r\n /** 너비 */\r\n width?: string\r\n /** 표시 여부 */\r\n isVisible?: boolean\r\n }>(),\r\n {\r\n permissions: () => [],\r\n searchQuery: '',\r\n styletype: 'default',\r\n width: '250px',\r\n isVisible: true,\r\n },\r\n)\r\n\r\nconst emit = defineEmits<{\r\n /** 메뉴 클릭 이벤트 */\r\n menuClick: [event: MenuClickEvent]\r\n}>()\r\n\r\n// vue-router가 설정되지 않은 경우를 대비 (Storybook에서 router가 제공됨)\r\nconst route = useRoute()\r\n\r\n/**\r\n * 현재 활성화된 경로\r\n */\r\nconst activePath = computed(() => route.path)\r\n\r\n/**\r\n * 확장된 메뉴 키 목록 (Set)\r\n */\r\nconst expandedKeys = ref<Set<number | string>>(new Set())\r\n\r\n/**\r\n * 검색어로 필터링된 메뉴 아이템\r\n * 재귀적으로 children까지 검색\r\n */\r\nconst filteredMenuItems = computed(() => {\r\n if (!Array.isArray(props.menuItems) || props.menuItems.length === 0) {\r\n return []\r\n }\r\n\r\n if (!props.searchQuery || props.searchQuery.trim() === '') {\r\n return props.menuItems\r\n }\r\n\r\n const query = props.searchQuery.toLowerCase().trim()\r\n\r\n /**\r\n * 메뉴 아이템을 재귀적으로 검색\r\n */\r\n const searchInMenu = (items: SidebarMenuItem[]): SidebarMenuItem[] => {\r\n const result: SidebarMenuItem[] = []\r\n\r\n if (!Array.isArray(items)) {\r\n return result\r\n }\r\n\r\n for (const item of items) {\r\n const matchesLabel = item.label?.toLowerCase().includes(query) ?? false\r\n\r\n // 하위 메뉴 검색\r\n let filteredChildren: SidebarMenuItem[] | undefined = undefined\r\n if (item.children && Array.isArray(item.children) && item.children.length > 0) {\r\n filteredChildren = searchInMenu(item.children)\r\n }\r\n\r\n // 현재 메뉴나 하위 메뉴 중 하나라도 매칭되면 포함\r\n if (matchesLabel || (Array.isArray(filteredChildren) && filteredChildren.length > 0)) {\r\n result.push({\r\n ...item,\r\n children: filteredChildren,\r\n })\r\n }\r\n }\r\n\r\n return result\r\n }\r\n\r\n return searchInMenu(props.menuItems)\r\n})\r\n\r\n/**\r\n * 검색 결과에 따라 부모 메뉴 자동 확장\r\n * computed 외부에서 watch를 통해 처리\r\n */\r\nwatch(\r\n () => filteredMenuItems.value,\r\n (filtered) => {\r\n if (!props.searchQuery || props.searchQuery.trim() === '') {\r\n return\r\n }\r\n\r\n // 검색 결과에서 매칭된 하위 메뉴가 있는 부모를 찾아 확장\r\n const findParentsWithMatches = (items: SidebarMenuItem[]): Set<number | string> => {\r\n const keysToExpand = new Set<number | string>()\r\n\r\n const traverse = (menuItems: SidebarMenuItem[]): void => {\r\n if (!Array.isArray(menuItems)) {\r\n return\r\n }\r\n\r\n for (const item of menuItems) {\r\n if (item.children && Array.isArray(item.children) && item.children.length > 0) {\r\n const key = item.menuKey || item.label\r\n keysToExpand.add(key)\r\n traverse(item.children)\r\n }\r\n }\r\n }\r\n\r\n traverse(items)\r\n return keysToExpand\r\n }\r\n\r\n const keysToExpand = findParentsWithMatches(filtered)\r\n keysToExpand.forEach(key => {\r\n expandedKeys.value.add(key)\r\n })\r\n },\r\n { immediate: false }\r\n)\r\n\r\n/**\r\n * 확장 상태 변경 핸들러\r\n */\r\nconst handleExpandChange = (menuKey: number | string | undefined, expanded: boolean) => {\r\n if (!menuKey) return\r\n\r\n if (expanded) {\r\n expandedKeys.value.add(menuKey)\r\n } else {\r\n expandedKeys.value.delete(menuKey)\r\n }\r\n}\r\n\r\n/**\r\n * 메뉴 클릭 핸들러\r\n */\r\nconst handleMenuClick = (event: MenuClickEvent) => {\r\n emit('menuClick', event)\r\n}\r\n\r\n/**\r\n * 스타일 프리셋\r\n */\r\nconst STYLE_PRESETS: Record<StyleType, {\r\n containerClass: string\r\n menuPaddingClass: string\r\n}> = {\r\n default: {\r\n containerClass: 'h-full bg-background border-r border-border overflow-y-auto',\r\n menuPaddingClass: 'p-2',\r\n },\r\n minimal: {\r\n containerClass: 'h-full bg-background border-r border-border overflow-y-auto',\r\n menuPaddingClass: 'p-1',\r\n },\r\n}\r\n\r\nconst preset = computed(() => {\r\n return STYLE_PRESETS[props.styletype] ?? STYLE_PRESETS.default\r\n})\r\n\r\n/**\r\n * 루트 클래스\r\n */\r\nconst rootClasses = computed(() => {\r\n return cn(\r\n preset.value.containerClass,\r\n props.class\r\n )\r\n})\r\n</script>\r\n\r\n<template>\r\n <Transition name=\"slide\">\r\n <aside v-show=\"props.isVisible\" :class=\"rootClasses\" :style=\"{ width }\">\r\n <div :class=\"cn(preset.menuPaddingClass, 'space-y-1')\">\r\n <JDynamicMenuItem\r\n v-for=\"(item, index) in filteredMenuItems\"\r\n :key=\"item.menuKey || item.label || index\"\r\n :item=\"item\"\r\n :level=\"0\"\r\n :permissions=\"permissions\"\r\n :active-path=\"activePath\"\r\n :expanded-keys=\"expandedKeys\"\r\n :styletype=\"styletype\"\r\n @menu-click=\"handleMenuClick\"\r\n @expand-change=\"handleExpandChange\"\r\n />\r\n </div>\r\n </aside>\r\n </Transition>\r\n</template>\r\n\r\n<style scoped>\r\n.slide-enter-active,\r\n.slide-leave-active {\r\n transition: transform 0.3s ease, opacity 0.3s ease;\r\n}\r\n\r\n.slide-enter-from,\r\n.slide-leave-to {\r\n transform: translateX(-100%);\r\n opacity: 0;\r\n}\r\n</style>\r\n"],"names":["props","__props","emit","__emit","route","useRoute","activePath","computed","expandedKeys","ref","filteredMenuItems","query","searchInMenu","items","result","item","matchesLabel","filteredChildren","watch","filtered","keysToExpand","traverse","menuItems","key","handleExpandChange","menuKey","expanded","handleMenuClick","event","STYLE_PRESETS","preset","rootClasses","cn","_createBlock","_Transition","_createElementVNode","_normalizeClass","_unref","_openBlock","_createElementBlock","_Fragment","_renderList","index","JDynamicMenuItem","_vShow"],"mappings":";;;;;;;;;;;;;;;;;AA4DA,UAAMA,IAAQC,GA0BRC,IAAOC,GAMPC,IAAQC,EAAA,GAKRC,IAAaC,EAAS,MAAMH,EAAM,IAAI,GAKtCI,IAAeC,EAA0B,oBAAI,KAAK,GAMlDC,IAAoBH,EAAS,MAAM;AACvC,UAAI,CAAC,MAAM,QAAQP,EAAM,SAAS,KAAKA,EAAM,UAAU,WAAW;AAChE,eAAO,CAAA;AAGT,UAAI,CAACA,EAAM,eAAeA,EAAM,YAAY,KAAA,MAAW;AACrD,eAAOA,EAAM;AAGf,YAAMW,IAAQX,EAAM,YAAY,YAAA,EAAc,KAAA,GAKxCY,IAAe,CAACC,MAAgD;AACpE,cAAMC,IAA4B,CAAA;AAElC,YAAI,CAAC,MAAM,QAAQD,CAAK;AACtB,iBAAOC;AAGT,mBAAWC,KAAQF,GAAO;AACxB,gBAAMG,IAAeD,EAAK,OAAO,cAAc,SAASJ,CAAK,KAAK;AAGlE,cAAIM;AACJ,UAAIF,EAAK,YAAY,MAAM,QAAQA,EAAK,QAAQ,KAAKA,EAAK,SAAS,SAAS,MAC1EE,IAAmBL,EAAaG,EAAK,QAAQ,KAI3CC,KAAiB,MAAM,QAAQC,CAAgB,KAAKA,EAAiB,SAAS,MAChFH,EAAO,KAAK;AAAA,YACV,GAAGC;AAAA,YACH,UAAUE;AAAA,UAAA,CACX;AAAA,QAEL;AAEA,eAAOH;AAAA,MACT;AAEA,aAAOF,EAAaZ,EAAM,SAAS;AAAA,IACrC,CAAC;AAMD,IAAAkB;AAAA,MACE,MAAMR,EAAkB;AAAA,MACxB,CAACS,MAAa;AACZ,YAAI,CAACnB,EAAM,eAAeA,EAAM,YAAY,KAAA,MAAW;AACrD;AA0BF,SAtB+B,CAACa,MAAmD;AACjF,gBAAMO,wBAAmB,IAAA,GAEnBC,IAAW,CAACC,MAAuC;AACvD,gBAAK,MAAM,QAAQA,CAAS;AAI5B,yBAAWP,KAAQO;AACjB,oBAAIP,EAAK,YAAY,MAAM,QAAQA,EAAK,QAAQ,KAAKA,EAAK,SAAS,SAAS,GAAG;AAC7E,wBAAMQ,IAAMR,EAAK,WAAWA,EAAK;AACjCK,kBAAAA,EAAa,IAAIG,CAAG,GACpBF,EAASN,EAAK,QAAQ;AAAA,gBACxB;AAAA;AAAA,UAEJ;AAEA,iBAAAM,EAASR,CAAK,GACPO;AAAAA,QACT,GAE4CD,CAAQ,EACvC,QAAQ,CAAAI,MAAO;AAC1B,UAAAf,EAAa,MAAM,IAAIe,CAAG;AAAA,QAC5B,CAAC;AAAA,MACH;AAAA,MACA,EAAE,WAAW,GAAA;AAAA,IAAM;AAMrB,UAAMC,IAAqB,CAACC,GAAsCC,MAAsB;AACtF,MAAKD,MAEDC,IACFlB,EAAa,MAAM,IAAIiB,CAAO,IAE9BjB,EAAa,MAAM,OAAOiB,CAAO;AAAA,IAErC,GAKME,IAAkB,CAACC,MAA0B;AACjD,MAAA1B,EAAK,aAAa0B,CAAK;AAAA,IACzB,GAKMC,IAGD;AAAA,MACH,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,kBAAkB;AAAA,MAAA;AAAA,MAEpB,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,kBAAkB;AAAA,MAAA;AAAA,IACpB,GAGIC,IAASvB,EAAS,MACfsB,EAAc7B,EAAM,SAAS,KAAK6B,EAAc,OACxD,GAKKE,IAAcxB,EAAS,MACpByB;AAAA,MACLF,EAAO,MAAM;AAAA,MACb9B,EAAM;AAAA,IAAA,CAET;2BAICiC,EAiBaC,GAAA,EAjBD,MAAK,WAAO;AAAA,iBACtB,MAeQ;AAAA,UAfRC,EAeQ,SAAA;AAAA,UAfyB,SAAOJ,EAAA,KAAW;AAAA,UAAG,kBAAS9B,EAAA,OAAK;AAAA,QAAA;UAClEkC,EAaM,OAAA;AAAA,YAbA,OAAKC,EAAEC,EAAAL,CAAA,EAAGF,EAAA,MAAO,kBAAgB,WAAA,CAAA;AAAA,UAAA;aACrCQ,EAAA,EAAA,GAAAC,EAWEC,GAAA,MAAAC,EAVwB/B,EAAA,OAAiB,CAAjCK,GAAM2B,YADhBT,EAWEU,GAAA;AAAA,cATC,KAAK5B,EAAK,WAAWA,EAAK,SAAS2B;AAAA,cACnC,MAAA3B;AAAA,cACA,OAAO;AAAA,cACP,aAAad,EAAA;AAAA,cACb,eAAaK,EAAA;AAAA,cACb,iBAAeE,EAAA;AAAA,cACf,WAAWP,EAAA;AAAA,cACX,aAAY0B;AAAA,cACZ,gBAAeH;AAAA,YAAA;;;UAZP,CAAAoB,GAAA5C,EAAM,SAAS;AAAA,QAAA;;;;;;"}
1
+ {"version":3,"file":"JSidebarSimple.vue2.js","sources":["../../../../src/components/organisms/JSidebarSimple.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport { ref, computed, watch } from 'vue'\nimport { useRoute } from 'vue-router'\nimport type { SidebarMenuItem, MenuPermission, MenuClickEvent } from '@/types/sidebar-menu.types'\nimport JDynamicMenuItem from './JSidebarSimple/JDynamicMenuItem.vue'\nimport { cn } from '@/lib/utils'\nimport { filterMenuItems, getExpandedKeysForSearch } from '@/lib/menu-utils'\n\r\n/**\r\n * JSidebarSimple - 간단한 사이드바 컴포넌트\r\n * Simple Sidebar Component\r\n * \r\n * @description\r\n * 다단계 메뉴 구조를 지원하는 기본 사이드바 컴포넌트입니다.\r\n * 권한 체크, 메뉴 검색 등의 기능을 제공합니다.\r\n * \r\n * @example\r\n * ```vue\r\n * <JSidebarSimple\r\n * :menu-items=\"menuItems\"\r\n * :permissions=\"userPermissions\"\r\n * @menu-click=\"handleMenuClick\"\r\n * />\r\n * ```\r\n * \r\n * @example JSON 메뉴 데이터 예시\r\n * ```json\r\n * [\r\n * {\r\n * \"label\": \"대시보드\",\r\n * \"icon\": \"house\",\r\n * \"menuType\": \"L\",\r\n * \"menuKey\": 1,\r\n * \"path\": \"/dashboard\"\r\n * },\r\n * {\r\n * \"label\": \"재고 관리\",\r\n * \"icon\": \"package\",\r\n * \"menuType\": \"F\",\r\n * \"menuKey\": 2,\r\n * \"children\": [\r\n * {\r\n * \"label\": \"재고 현황\",\r\n * \"menuType\": \"L\",\r\n * \"menuKey\": 21,\r\n * \"path\": \"/inventory/status\"\r\n * },\r\n * {\r\n * \"label\": \"입고 관리\",\r\n * \"menuType\": \"L\",\r\n * \"menuKey\": 22,\r\n * \"path\": \"/inventory/receiving\"\r\n * }\r\n * ]\r\n * }\r\n * ]\r\n * ```\r\n */\r\n\r\ntype StyleType = 'default' | 'minimal'\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n /** 메뉴 아이템 목록 */\r\n menuItems: SidebarMenuItem[]\r\n /** 권한 목록 */\r\n permissions?: MenuPermission[]\r\n /** 검색어 */\r\n searchQuery?: string\r\n /** 스타일 타입 */\r\n styletype?: StyleType\r\n /** 추가 CSS 클래스 */\r\n class?: string\r\n /** 너비 */\r\n width?: string\r\n /** 표시 여부 */\r\n isVisible?: boolean\r\n }>(),\r\n {\r\n permissions: () => [],\r\n searchQuery: '',\r\n styletype: 'default',\r\n width: '250px',\r\n isVisible: true,\r\n },\r\n)\r\n\r\nconst emit = defineEmits<{\r\n /** 메뉴 클릭 이벤트 */\r\n menuClick: [event: MenuClickEvent]\r\n}>()\r\n\r\n// vue-router가 설정되지 않은 경우를 대비 (Storybook에서 router가 제공됨)\r\nconst route = useRoute()\r\n\r\n/**\r\n * 현재 활성화된 경로\r\n */\r\nconst activePath = computed(() => route.path)\r\n\r\n/**\r\n * 확장된 메뉴 키 목록 (Set)\r\n */\r\nconst expandedKeys = ref<Set<number | string>>(new Set())\r\n\r\n/**\n * 검색어로 필터링된 메뉴 아이템\n * 재귀적으로 children까지 검색\n */\nconst filteredMenuItems = computed(() => {\n return filterMenuItems(props.menuItems, props.searchQuery || '')\n})\n\r\n/**\n * 검색 결과에 따라 부모 메뉴 자동 확장\n * computed 외부에서 watch를 통해 처리\n */\nwatch(\n () => filteredMenuItems.value,\n (filtered) => {\n if (!props.searchQuery || props.searchQuery.trim() === '') {\n return\n }\n\n // 검색 결과에서 매칭된 하위 메뉴가 있는 부모를 찾아 확장\n const keysToExpand = getExpandedKeysForSearch(filtered)\n keysToExpand.forEach(key => {\n expandedKeys.value.add(key)\n })\n },\n { immediate: false }\n)\n\r\n/**\r\n * 확장 상태 변경 핸들러\r\n */\r\nconst handleExpandChange = (menuKey: number | string | undefined, expanded: boolean) => {\r\n if (!menuKey) return\r\n\r\n if (expanded) {\r\n expandedKeys.value.add(menuKey)\r\n } else {\r\n expandedKeys.value.delete(menuKey)\r\n }\r\n}\r\n\r\n/**\r\n * 메뉴 클릭 핸들러\r\n */\r\nconst handleMenuClick = (event: MenuClickEvent) => {\r\n emit('menuClick', event)\r\n}\r\n\r\n/**\r\n * 스타일 프리셋\r\n */\r\nconst STYLE_PRESETS: Record<StyleType, {\r\n containerClass: string\r\n menuPaddingClass: string\r\n}> = {\r\n default: {\r\n containerClass: 'h-full bg-background border-r border-border overflow-y-auto',\r\n menuPaddingClass: 'p-2',\r\n },\r\n minimal: {\r\n containerClass: 'h-full bg-background border-r border-border overflow-y-auto',\r\n menuPaddingClass: 'p-1',\r\n },\r\n}\r\n\r\nconst preset = computed(() => {\r\n return STYLE_PRESETS[props.styletype] ?? STYLE_PRESETS.default\r\n})\r\n\r\n/**\r\n * 루트 클래스\r\n */\r\nconst rootClasses = computed(() => {\r\n return cn(\r\n preset.value.containerClass,\r\n props.class\r\n )\r\n})\r\n</script>\r\n\r\n<template>\r\n <Transition name=\"slide\">\r\n <aside v-show=\"props.isVisible\" :class=\"rootClasses\" :style=\"{ width }\">\r\n <div :class=\"cn(preset.menuPaddingClass, 'space-y-1')\">\r\n <JDynamicMenuItem\r\n v-for=\"(item, index) in filteredMenuItems\"\r\n :key=\"item.menuKey || item.label || index\"\r\n :item=\"item\"\r\n :level=\"0\"\r\n :permissions=\"permissions\"\r\n :active-path=\"activePath\"\r\n :expanded-keys=\"expandedKeys\"\r\n :styletype=\"styletype\"\r\n @menu-click=\"handleMenuClick\"\r\n @expand-change=\"handleExpandChange\"\r\n />\r\n </div>\r\n </aside>\r\n </Transition>\r\n</template>\r\n\r\n<style scoped>\r\n.slide-enter-active,\r\n.slide-leave-active {\r\n transition: transform 0.3s ease, opacity 0.3s ease;\r\n}\r\n\r\n.slide-enter-from,\r\n.slide-leave-to {\r\n transform: translateX(-100%);\r\n opacity: 0;\r\n}\r\n</style>\r\n"],"names":["props","__props","emit","__emit","route","useRoute","activePath","computed","expandedKeys","ref","filteredMenuItems","filterMenuItems","watch","filtered","getExpandedKeysForSearch","key","handleExpandChange","menuKey","expanded","handleMenuClick","event","STYLE_PRESETS","preset","rootClasses","cn","_createBlock","_Transition","_createElementVNode","_normalizeClass","_unref","_openBlock","_createElementBlock","_Fragment","_renderList","item","index","JDynamicMenuItem","_vShow"],"mappings":";;;;;;;;;;;;;;;;;;AA6DA,UAAMA,IAAQC,GA0BRC,IAAOC,GAMPC,IAAQC,EAAA,GAKRC,IAAaC,EAAS,MAAMH,EAAM,IAAI,GAKtCI,IAAeC,EAA0B,oBAAI,KAAK,GAMlDC,IAAoBH,EAAS,MAC1BI,EAAgBX,EAAM,WAAWA,EAAM,eAAe,EAAE,CAChE;AAMD,IAAAY;AAAA,MACE,MAAMF,EAAkB;AAAA,MACxB,CAACG,MAAa;AACZ,YAAI,CAACb,EAAM,eAAeA,EAAM,YAAY,KAAA,MAAW;AACrD;AAKF,QADqBc,EAAyBD,CAAQ,EACzC,QAAQ,CAAAE,MAAO;AAC1B,UAAAP,EAAa,MAAM,IAAIO,CAAG;AAAA,QAC5B,CAAC;AAAA,MACH;AAAA,MACA,EAAE,WAAW,GAAA;AAAA,IAAM;AAMrB,UAAMC,IAAqB,CAACC,GAAsCC,MAAsB;AACtF,MAAKD,MAEDC,IACFV,EAAa,MAAM,IAAIS,CAAO,IAE9BT,EAAa,MAAM,OAAOS,CAAO;AAAA,IAErC,GAKME,IAAkB,CAACC,MAA0B;AACjD,MAAAlB,EAAK,aAAakB,CAAK;AAAA,IACzB,GAKMC,IAGD;AAAA,MACH,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,kBAAkB;AAAA,MAAA;AAAA,MAEpB,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,kBAAkB;AAAA,MAAA;AAAA,IACpB,GAGIC,IAASf,EAAS,MACfc,EAAcrB,EAAM,SAAS,KAAKqB,EAAc,OACxD,GAKKE,IAAchB,EAAS,MACpBiB;AAAA,MACLF,EAAO,MAAM;AAAA,MACbtB,EAAM;AAAA,IAAA,CAET;2BAICyB,EAiBaC,GAAA,EAjBD,MAAK,WAAO;AAAA,iBACtB,MAeQ;AAAA,UAfRC,EAeQ,SAAA;AAAA,UAfyB,SAAOJ,EAAA,KAAW;AAAA,UAAG,kBAAStB,EAAA,OAAK;AAAA,QAAA;UAClE0B,EAaM,OAAA;AAAA,YAbA,OAAKC,EAAEC,EAAAL,CAAA,EAAGF,EAAA,MAAO,kBAAgB,WAAA,CAAA;AAAA,UAAA;aACrCQ,EAAA,EAAA,GAAAC,EAWEC,GAAA,MAAAC,EAVwBvB,EAAA,OAAiB,CAAjCwB,GAAMC,YADhBV,EAWEW,GAAA;AAAA,cATC,KAAKF,EAAK,WAAWA,EAAK,SAASC;AAAA,cACnC,MAAAD;AAAA,cACA,OAAO;AAAA,cACP,aAAajC,EAAA;AAAA,cACb,eAAaK,EAAA;AAAA,cACb,iBAAeE,EAAA;AAAA,cACf,WAAWP,EAAA;AAAA,cACX,aAAYkB;AAAA,cACZ,gBAAeH;AAAA,YAAA;;;UAZP,CAAAqB,GAAArC,EAAM,SAAS;AAAA,QAAA;;;;;;"}
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),x=require("./JSidebarSimple/JDynamicMenuItem.vue.cjs"),h=require("../../lib/utils.cjs"),o=require("../../lib/menu-utils.cjs"),C=e.defineComponent({__name:"JTree",props:{items:{},expandedKeys:{default:()=>[]},activeKey:{default:null},permissions:{default:()=>[]},maxDepth:{default:10},styletype:{default:"minimal"},searchQuery:{default:""},class:{}},emits:["nodeClick","expandChange","update:expandedKeys","update:activeKey"],setup(u,{emit:p}){const n=u,d=p,l=e.ref(new Set(n.expandedKeys)),r=e.computed(()=>o.filterMenuItems(n.items,n.searchQuery||""));e.watch(()=>r.value,t=>{if(!n.searchQuery||n.searchQuery.trim()==="")return;o.getExpandedKeysForSearch(t).forEach(a=>{l.value.add(a)}),d("update:expandedKeys",[...l.value])},{immediate:!1}),e.watch(()=>n.expandedKeys,t=>{l.value=new Set(t)},{deep:!0});const m=(t,s)=>{if(!t)return;const a=new Set(l.value);s?a.add(t):a.delete(t),l.value=a,d("update:expandedKeys",[...a]),d("expandChange",t,s)},y=t=>{const s=t.menuItem.menuKey;s!==void 0&&d("update:activeKey",s),d("nodeClick",t)},c={default:{containerClass:"w-full",menuPaddingClass:"space-y-1"},minimal:{containerClass:"w-full",menuPaddingClass:"space-y-0.5"}},i=e.computed(()=>c[n.styletype]??c.default),v=e.computed(()=>h.cn(i.value.containerClass,n.class));return(t,s)=>(e.openBlock(),e.createElementBlock("div",{class:e.normalizeClass(v.value)},[e.createElementVNode("div",{class:e.normalizeClass(i.value.menuPaddingClass)},[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(r.value,(a,f)=>(e.openBlock(),e.createBlock(x.default,{key:a.menuKey||a.label||f,item:a,level:0,"max-depth":u.maxDepth,permissions:u.permissions,"expanded-keys":l.value,styletype:u.styletype,"disable-navigation":!0,"active-key":u.activeKey,onMenuClick:y,onExpandChange:m},null,8,["item","max-depth","permissions","expanded-keys","styletype","active-key"]))),128))],2)],2))}});exports.default=C;
2
+ //# sourceMappingURL=JTree.vue.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JTree.vue.cjs","sources":["../../../../src/components/organisms/JTree.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ref, computed, watch } from 'vue'\nimport type { SidebarMenuItem, MenuPermission, MenuClickEvent } from '@/types/sidebar-menu.types'\nimport JDynamicMenuItem from './JSidebarSimple/JDynamicMenuItem.vue'\nimport { cn } from '@/lib/utils'\nimport { filterMenuItems, getExpandedKeysForSearch } from '@/lib/menu-utils'\n\n/**\n * JTree - 트리 뷰 컴포넌트\n * Tree View Component\n *\n * @description\n * 계층 데이터를 조회/탐색하는 읽기 전용 트리 컴포넌트입니다.\n * JDynamicMenuItem을 재귀적으로 렌더링하며, 네비게이션 기능을 비활성화하고\n * nodeClick 이벤트로 선택을 처리합니다.\n *\n * @example\n * ```vue\n * <JTree\n * :items=\"treeData\"\n * v-model:expanded-keys=\"expandedKeys\"\n * v-model:active-key=\"activeKey\"\n * @node-click=\"handleNodeClick\"\n * />\n * ```\n *\n * @example JSON 트리 데이터 예시\n * ```json\n * [\n * {\n * \"label\": \"프로그램 관리\",\n * \"icon\": \"folder\",\n * \"menuType\": \"F\",\n * \"menuKey\": 1,\n * \"children\": [\n * {\n * \"label\": \"시스템 관리\",\n * \"menuType\": \"L\",\n * \"menuKey\": 11\n * },\n * {\n * \"label\": \"사용자 관리\",\n * \"menuType\": \"L\",\n * \"menuKey\": 12\n * }\n * ]\n * }\n * ]\n * ```\n */\n\ntype StyleType = 'default' | 'minimal'\n\nconst props = withDefaults(\n defineProps<{\n /** 트리 노드 데이터 */\n items: SidebarMenuItem[]\n /** 펼쳐진 노드 키 목록 (v-model 지원, 배열) */\n expandedKeys?: (number | string)[]\n /** 현재 선택(하이라이트)된 노드의 menuKey (v-model 지원) */\n activeKey?: number | string | null\n /** 권한 목록 */\n permissions?: MenuPermission[]\n /** 최대 깊이 제한 (무한 루프 방지) */\n maxDepth?: number\n /** 스타일 타입 */\n styletype?: StyleType\n /** 검색어 (노드 필터링) */\n searchQuery?: string\n /** 추가 CSS 클래스 */\n class?: string\n }>(),\n {\n expandedKeys: () => [],\n activeKey: null,\n permissions: () => [],\n maxDepth: 10,\n styletype: 'minimal',\n searchQuery: '',\n },\n)\n\nconst emit = defineEmits<{\n /** 노드 클릭 이벤트 */\n nodeClick: [event: MenuClickEvent]\n /** 확장 상태 변경 이벤트 */\n expandChange: [menuKey: number | string | undefined, expanded: boolean]\n /** expandedKeys v-model 업데이트 */\n 'update:expandedKeys': [keys: (number | string)[]]\n /** activeKey v-model 업데이트 */\n 'update:activeKey': [key: number | string | null]\n}>()\n\n/**\n * 내부 확장 키 관리 (Set으로 변환하여 사용)\n */\nconst internalExpandedKeys = ref<Set<number | string>>(new Set(props.expandedKeys))\n\n/**\n * 검색어로 필터링된 트리 아이템\n */\nconst filteredTreeItems = computed(() => {\n return filterMenuItems(props.items, props.searchQuery || '')\n})\n\n/**\n * 검색 결과에 따라 부모 노드 자동 확장\n */\nwatch(\n () => filteredTreeItems.value,\n (filtered) => {\n if (!props.searchQuery || props.searchQuery.trim() === '') {\n return\n }\n\n // 검색 결과에서 매칭된 하위 노드가 있는 부모를 찾아 확장\n const keysToExpand = getExpandedKeysForSearch(filtered)\n keysToExpand.forEach(key => {\n internalExpandedKeys.value.add(key)\n })\n\n // v-model 업데이트\n emit('update:expandedKeys', [...internalExpandedKeys.value])\n },\n { immediate: false }\n)\n\n/**\n * expandedKeys prop 변경 감지\n */\nwatch(\n () => props.expandedKeys,\n (newKeys) => {\n internalExpandedKeys.value = new Set(newKeys)\n },\n { deep: true }\n)\n\n/**\n * 확장 상태 변경 핸들러\n */\nconst handleExpandChange = (menuKey: number | string | undefined, expanded: boolean) => {\n if (!menuKey) return\n\n // 새로운 Set 생성 (reactivity 보장)\n const newKeys = new Set(internalExpandedKeys.value)\n\n if (expanded) {\n newKeys.add(menuKey)\n } else {\n newKeys.delete(menuKey)\n }\n\n internalExpandedKeys.value = newKeys\n\n // v-model 업데이트 및 이벤트 발생\n emit('update:expandedKeys', [...newKeys])\n emit('expandChange', menuKey, expanded)\n}\n\n/**\n * 노드 클릭 핸들러\n */\nconst handleNodeClick = (event: MenuClickEvent) => {\n // activeKey 업데이트\n const menuKey = event.menuItem.menuKey\n if (menuKey !== undefined) {\n emit('update:activeKey', menuKey)\n }\n\n // nodeClick 이벤트 발생\n emit('nodeClick', event)\n}\n\n/**\n * 스타일 프리셋\n */\nconst STYLE_PRESETS: Record<StyleType, {\n containerClass: string\n menuPaddingClass: string\n}> = {\n default: {\n containerClass: 'w-full',\n menuPaddingClass: 'space-y-1',\n },\n minimal: {\n containerClass: 'w-full',\n menuPaddingClass: 'space-y-0.5',\n },\n}\n\nconst preset = computed(() => {\n return STYLE_PRESETS[props.styletype] ?? STYLE_PRESETS.default\n})\n\n/**\n * 루트 클래스\n */\nconst rootClasses = computed(() => {\n return cn(\n preset.value.containerClass,\n props.class\n )\n})\n</script>\n\n<template>\n <div :class=\"rootClasses\">\n <div :class=\"preset.menuPaddingClass\">\n <JDynamicMenuItem\n v-for=\"(item, index) in filteredTreeItems\"\n :key=\"item.menuKey || item.label || index\"\n :item=\"item\"\n :level=\"0\"\n :max-depth=\"maxDepth\"\n :permissions=\"permissions\"\n :expanded-keys=\"internalExpandedKeys\"\n :styletype=\"styletype\"\n :disable-navigation=\"true\"\n :active-key=\"activeKey\"\n @menu-click=\"handleNodeClick\"\n @expand-change=\"handleExpandChange\"\n />\n </div>\n </div>\n</template>\n"],"names":["props","__props","emit","__emit","internalExpandedKeys","ref","filteredTreeItems","computed","filterMenuItems","watch","filtered","getExpandedKeysForSearch","key","newKeys","handleExpandChange","menuKey","expanded","handleNodeClick","event","STYLE_PRESETS","preset","rootClasses","cn","_createElementBlock","_createElementVNode","_normalizeClass","_openBlock","_Fragment","_renderList","item","index","_createBlock","JDynamicMenuItem"],"mappings":"8jBAqDA,MAAMA,EAAQC,EA6BRC,EAAOC,EAcPC,EAAuBC,EAAAA,IAA0B,IAAI,IAAIL,EAAM,YAAY,CAAC,EAK5EM,EAAoBC,EAAAA,SAAS,IAC1BC,EAAAA,gBAAgBR,EAAM,MAAOA,EAAM,aAAe,EAAE,CAC5D,EAKDS,EAAAA,MACE,IAAMH,EAAkB,MACvBI,GAAa,CACZ,GAAI,CAACV,EAAM,aAAeA,EAAM,YAAY,KAAA,IAAW,GACrD,OAImBW,EAAAA,yBAAyBD,CAAQ,EACzC,QAAQE,GAAO,CAC1BR,EAAqB,MAAM,IAAIQ,CAAG,CACpC,CAAC,EAGDV,EAAK,sBAAuB,CAAC,GAAGE,EAAqB,KAAK,CAAC,CAC7D,EACA,CAAE,UAAW,EAAA,CAAM,EAMrBK,EAAAA,MACE,IAAMT,EAAM,aACXa,GAAY,CACXT,EAAqB,MAAQ,IAAI,IAAIS,CAAO,CAC9C,EACA,CAAE,KAAM,EAAA,CAAK,EAMf,MAAMC,EAAqB,CAACC,EAAsCC,IAAsB,CACtF,GAAI,CAACD,EAAS,OAGd,MAAMF,EAAU,IAAI,IAAIT,EAAqB,KAAK,EAE9CY,EACFH,EAAQ,IAAIE,CAAO,EAEnBF,EAAQ,OAAOE,CAAO,EAGxBX,EAAqB,MAAQS,EAG7BX,EAAK,sBAAuB,CAAC,GAAGW,CAAO,CAAC,EACxCX,EAAK,eAAgBa,EAASC,CAAQ,CACxC,EAKMC,EAAmBC,GAA0B,CAEjD,MAAMH,EAAUG,EAAM,SAAS,QAC3BH,IAAY,QACdb,EAAK,mBAAoBa,CAAO,EAIlCb,EAAK,YAAagB,CAAK,CACzB,EAKMC,EAGD,CACH,QAAS,CACP,eAAgB,SAChB,iBAAkB,WAAA,EAEpB,QAAS,CACP,eAAgB,SAChB,iBAAkB,aAAA,CACpB,EAGIC,EAASb,EAAAA,SAAS,IACfY,EAAcnB,EAAM,SAAS,GAAKmB,EAAc,OACxD,EAKKE,EAAcd,EAAAA,SAAS,IACpBe,EAAAA,GACLF,EAAO,MAAM,eACbpB,EAAM,KAAA,CAET,8BAICuB,EAAAA,mBAiBM,MAAA,CAjBA,uBAAOF,EAAA,KAAW,CAAA,GACtBG,EAAAA,mBAeM,MAAA,CAfA,MAAKC,EAAAA,eAAEL,EAAA,MAAO,gBAAgB,CAAA,IAClCM,EAAAA,UAAA,EAAA,EAAAH,EAAAA,mBAaEI,WAAA,KAAAC,EAAAA,WAZwBtB,EAAA,MAAiB,CAAjCuB,EAAMC,mBADhBC,EAAAA,YAaEC,UAAA,CAXC,IAAKH,EAAK,SAAWA,EAAK,OAASC,EACnC,KAAAD,EACA,MAAO,EACP,YAAW5B,EAAA,SACX,YAAaA,EAAA,YACb,gBAAeG,EAAA,MACf,UAAWH,EAAA,UACX,qBAAoB,GACpB,aAAYA,EAAA,UACZ,YAAYgB,EACZ,eAAeH,CAAA"}
@@ -0,0 +1,83 @@
1
+ import { defineComponent as k, ref as K, computed as r, watch as m, createElementBlock as p, openBlock as i, normalizeClass as y, createElementVNode as E, Fragment as g, renderList as w, createBlock as S } from "vue";
2
+ import T from "./JSidebarSimple/JDynamicMenuItem.vue.js";
3
+ import { cn as P } from "../../lib/utils.js";
4
+ import { filterMenuItems as Q, getExpandedKeysForSearch as B } from "../../lib/menu-utils.js";
5
+ const L = /* @__PURE__ */ k({
6
+ __name: "JTree",
7
+ props: {
8
+ items: {},
9
+ expandedKeys: { default: () => [] },
10
+ activeKey: { default: null },
11
+ permissions: { default: () => [] },
12
+ maxDepth: { default: 10 },
13
+ styletype: { default: "minimal" },
14
+ searchQuery: { default: "" },
15
+ class: {}
16
+ },
17
+ emits: ["nodeClick", "expandChange", "update:expandedKeys", "update:activeKey"],
18
+ setup(l, { emit: f }) {
19
+ const t = l, d = f, s = K(new Set(t.expandedKeys)), u = r(() => Q(t.items, t.searchQuery || ""));
20
+ m(
21
+ () => u.value,
22
+ (e) => {
23
+ if (!t.searchQuery || t.searchQuery.trim() === "")
24
+ return;
25
+ B(e).forEach((a) => {
26
+ s.value.add(a);
27
+ }), d("update:expandedKeys", [...s.value]);
28
+ },
29
+ { immediate: !1 }
30
+ ), m(
31
+ () => t.expandedKeys,
32
+ (e) => {
33
+ s.value = new Set(e);
34
+ },
35
+ { deep: !0 }
36
+ );
37
+ const v = (e, n) => {
38
+ if (!e) return;
39
+ const a = new Set(s.value);
40
+ n ? a.add(e) : a.delete(e), s.value = a, d("update:expandedKeys", [...a]), d("expandChange", e, n);
41
+ }, x = (e) => {
42
+ const n = e.menuItem.menuKey;
43
+ n !== void 0 && d("update:activeKey", n), d("nodeClick", e);
44
+ }, c = {
45
+ default: {
46
+ containerClass: "w-full",
47
+ menuPaddingClass: "space-y-1"
48
+ },
49
+ minimal: {
50
+ containerClass: "w-full",
51
+ menuPaddingClass: "space-y-0.5"
52
+ }
53
+ }, o = r(() => c[t.styletype] ?? c.default), h = r(() => P(
54
+ o.value.containerClass,
55
+ t.class
56
+ ));
57
+ return (e, n) => (i(), p("div", {
58
+ class: y(h.value)
59
+ }, [
60
+ E("div", {
61
+ class: y(o.value.menuPaddingClass)
62
+ }, [
63
+ (i(!0), p(g, null, w(u.value, (a, C) => (i(), S(T, {
64
+ key: a.menuKey || a.label || C,
65
+ item: a,
66
+ level: 0,
67
+ "max-depth": l.maxDepth,
68
+ permissions: l.permissions,
69
+ "expanded-keys": s.value,
70
+ styletype: l.styletype,
71
+ "disable-navigation": !0,
72
+ "active-key": l.activeKey,
73
+ onMenuClick: x,
74
+ onExpandChange: v
75
+ }, null, 8, ["item", "max-depth", "permissions", "expanded-keys", "styletype", "active-key"]))), 128))
76
+ ], 2)
77
+ ], 2));
78
+ }
79
+ });
80
+ export {
81
+ L as default
82
+ };
83
+ //# sourceMappingURL=JTree.vue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JTree.vue.js","sources":["../../../../src/components/organisms/JTree.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ref, computed, watch } from 'vue'\nimport type { SidebarMenuItem, MenuPermission, MenuClickEvent } from '@/types/sidebar-menu.types'\nimport JDynamicMenuItem from './JSidebarSimple/JDynamicMenuItem.vue'\nimport { cn } from '@/lib/utils'\nimport { filterMenuItems, getExpandedKeysForSearch } from '@/lib/menu-utils'\n\n/**\n * JTree - 트리 뷰 컴포넌트\n * Tree View Component\n *\n * @description\n * 계층 데이터를 조회/탐색하는 읽기 전용 트리 컴포넌트입니다.\n * JDynamicMenuItem을 재귀적으로 렌더링하며, 네비게이션 기능을 비활성화하고\n * nodeClick 이벤트로 선택을 처리합니다.\n *\n * @example\n * ```vue\n * <JTree\n * :items=\"treeData\"\n * v-model:expanded-keys=\"expandedKeys\"\n * v-model:active-key=\"activeKey\"\n * @node-click=\"handleNodeClick\"\n * />\n * ```\n *\n * @example JSON 트리 데이터 예시\n * ```json\n * [\n * {\n * \"label\": \"프로그램 관리\",\n * \"icon\": \"folder\",\n * \"menuType\": \"F\",\n * \"menuKey\": 1,\n * \"children\": [\n * {\n * \"label\": \"시스템 관리\",\n * \"menuType\": \"L\",\n * \"menuKey\": 11\n * },\n * {\n * \"label\": \"사용자 관리\",\n * \"menuType\": \"L\",\n * \"menuKey\": 12\n * }\n * ]\n * }\n * ]\n * ```\n */\n\ntype StyleType = 'default' | 'minimal'\n\nconst props = withDefaults(\n defineProps<{\n /** 트리 노드 데이터 */\n items: SidebarMenuItem[]\n /** 펼쳐진 노드 키 목록 (v-model 지원, 배열) */\n expandedKeys?: (number | string)[]\n /** 현재 선택(하이라이트)된 노드의 menuKey (v-model 지원) */\n activeKey?: number | string | null\n /** 권한 목록 */\n permissions?: MenuPermission[]\n /** 최대 깊이 제한 (무한 루프 방지) */\n maxDepth?: number\n /** 스타일 타입 */\n styletype?: StyleType\n /** 검색어 (노드 필터링) */\n searchQuery?: string\n /** 추가 CSS 클래스 */\n class?: string\n }>(),\n {\n expandedKeys: () => [],\n activeKey: null,\n permissions: () => [],\n maxDepth: 10,\n styletype: 'minimal',\n searchQuery: '',\n },\n)\n\nconst emit = defineEmits<{\n /** 노드 클릭 이벤트 */\n nodeClick: [event: MenuClickEvent]\n /** 확장 상태 변경 이벤트 */\n expandChange: [menuKey: number | string | undefined, expanded: boolean]\n /** expandedKeys v-model 업데이트 */\n 'update:expandedKeys': [keys: (number | string)[]]\n /** activeKey v-model 업데이트 */\n 'update:activeKey': [key: number | string | null]\n}>()\n\n/**\n * 내부 확장 키 관리 (Set으로 변환하여 사용)\n */\nconst internalExpandedKeys = ref<Set<number | string>>(new Set(props.expandedKeys))\n\n/**\n * 검색어로 필터링된 트리 아이템\n */\nconst filteredTreeItems = computed(() => {\n return filterMenuItems(props.items, props.searchQuery || '')\n})\n\n/**\n * 검색 결과에 따라 부모 노드 자동 확장\n */\nwatch(\n () => filteredTreeItems.value,\n (filtered) => {\n if (!props.searchQuery || props.searchQuery.trim() === '') {\n return\n }\n\n // 검색 결과에서 매칭된 하위 노드가 있는 부모를 찾아 확장\n const keysToExpand = getExpandedKeysForSearch(filtered)\n keysToExpand.forEach(key => {\n internalExpandedKeys.value.add(key)\n })\n\n // v-model 업데이트\n emit('update:expandedKeys', [...internalExpandedKeys.value])\n },\n { immediate: false }\n)\n\n/**\n * expandedKeys prop 변경 감지\n */\nwatch(\n () => props.expandedKeys,\n (newKeys) => {\n internalExpandedKeys.value = new Set(newKeys)\n },\n { deep: true }\n)\n\n/**\n * 확장 상태 변경 핸들러\n */\nconst handleExpandChange = (menuKey: number | string | undefined, expanded: boolean) => {\n if (!menuKey) return\n\n // 새로운 Set 생성 (reactivity 보장)\n const newKeys = new Set(internalExpandedKeys.value)\n\n if (expanded) {\n newKeys.add(menuKey)\n } else {\n newKeys.delete(menuKey)\n }\n\n internalExpandedKeys.value = newKeys\n\n // v-model 업데이트 및 이벤트 발생\n emit('update:expandedKeys', [...newKeys])\n emit('expandChange', menuKey, expanded)\n}\n\n/**\n * 노드 클릭 핸들러\n */\nconst handleNodeClick = (event: MenuClickEvent) => {\n // activeKey 업데이트\n const menuKey = event.menuItem.menuKey\n if (menuKey !== undefined) {\n emit('update:activeKey', menuKey)\n }\n\n // nodeClick 이벤트 발생\n emit('nodeClick', event)\n}\n\n/**\n * 스타일 프리셋\n */\nconst STYLE_PRESETS: Record<StyleType, {\n containerClass: string\n menuPaddingClass: string\n}> = {\n default: {\n containerClass: 'w-full',\n menuPaddingClass: 'space-y-1',\n },\n minimal: {\n containerClass: 'w-full',\n menuPaddingClass: 'space-y-0.5',\n },\n}\n\nconst preset = computed(() => {\n return STYLE_PRESETS[props.styletype] ?? STYLE_PRESETS.default\n})\n\n/**\n * 루트 클래스\n */\nconst rootClasses = computed(() => {\n return cn(\n preset.value.containerClass,\n props.class\n )\n})\n</script>\n\n<template>\n <div :class=\"rootClasses\">\n <div :class=\"preset.menuPaddingClass\">\n <JDynamicMenuItem\n v-for=\"(item, index) in filteredTreeItems\"\n :key=\"item.menuKey || item.label || index\"\n :item=\"item\"\n :level=\"0\"\n :max-depth=\"maxDepth\"\n :permissions=\"permissions\"\n :expanded-keys=\"internalExpandedKeys\"\n :styletype=\"styletype\"\n :disable-navigation=\"true\"\n :active-key=\"activeKey\"\n @menu-click=\"handleNodeClick\"\n @expand-change=\"handleExpandChange\"\n />\n </div>\n </div>\n</template>\n"],"names":["props","__props","emit","__emit","internalExpandedKeys","ref","filteredTreeItems","computed","filterMenuItems","watch","filtered","getExpandedKeysForSearch","key","newKeys","handleExpandChange","menuKey","expanded","handleNodeClick","event","STYLE_PRESETS","preset","rootClasses","cn","_createElementBlock","_createElementVNode","_normalizeClass","_openBlock","_Fragment","_renderList","item","index","_createBlock","JDynamicMenuItem"],"mappings":";;;;;;;;;;;;;;;;;;AAqDA,UAAMA,IAAQC,GA6BRC,IAAOC,GAcPC,IAAuBC,EAA0B,IAAI,IAAIL,EAAM,YAAY,CAAC,GAK5EM,IAAoBC,EAAS,MAC1BC,EAAgBR,EAAM,OAAOA,EAAM,eAAe,EAAE,CAC5D;AAKD,IAAAS;AAAA,MACE,MAAMH,EAAkB;AAAA,MACxB,CAACI,MAAa;AACZ,YAAI,CAACV,EAAM,eAAeA,EAAM,YAAY,KAAA,MAAW;AACrD;AAKF,QADqBW,EAAyBD,CAAQ,EACzC,QAAQ,CAAAE,MAAO;AAC1B,UAAAR,EAAqB,MAAM,IAAIQ,CAAG;AAAA,QACpC,CAAC,GAGDV,EAAK,uBAAuB,CAAC,GAAGE,EAAqB,KAAK,CAAC;AAAA,MAC7D;AAAA,MACA,EAAE,WAAW,GAAA;AAAA,IAAM,GAMrBK;AAAA,MACE,MAAMT,EAAM;AAAA,MACZ,CAACa,MAAY;AACX,QAAAT,EAAqB,QAAQ,IAAI,IAAIS,CAAO;AAAA,MAC9C;AAAA,MACA,EAAE,MAAM,GAAA;AAAA,IAAK;AAMf,UAAMC,IAAqB,CAACC,GAAsCC,MAAsB;AACtF,UAAI,CAACD,EAAS;AAGd,YAAMF,IAAU,IAAI,IAAIT,EAAqB,KAAK;AAElD,MAAIY,IACFH,EAAQ,IAAIE,CAAO,IAEnBF,EAAQ,OAAOE,CAAO,GAGxBX,EAAqB,QAAQS,GAG7BX,EAAK,uBAAuB,CAAC,GAAGW,CAAO,CAAC,GACxCX,EAAK,gBAAgBa,GAASC,CAAQ;AAAA,IACxC,GAKMC,IAAkB,CAACC,MAA0B;AAEjD,YAAMH,IAAUG,EAAM,SAAS;AAC/B,MAAIH,MAAY,UACdb,EAAK,oBAAoBa,CAAO,GAIlCb,EAAK,aAAagB,CAAK;AAAA,IACzB,GAKMC,IAGD;AAAA,MACH,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,kBAAkB;AAAA,MAAA;AAAA,MAEpB,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,kBAAkB;AAAA,MAAA;AAAA,IACpB,GAGIC,IAASb,EAAS,MACfY,EAAcnB,EAAM,SAAS,KAAKmB,EAAc,OACxD,GAKKE,IAAcd,EAAS,MACpBe;AAAA,MACLF,EAAO,MAAM;AAAA,MACbpB,EAAM;AAAA,IAAA,CAET;2BAICuB,EAiBM,OAAA;AAAA,MAjBA,SAAOF,EAAA,KAAW;AAAA,IAAA;MACtBG,EAeM,OAAA;AAAA,QAfA,OAAKC,EAAEL,EAAA,MAAO,gBAAgB;AAAA,MAAA;SAClCM,EAAA,EAAA,GAAAH,EAaEI,GAAA,MAAAC,EAZwBtB,EAAA,OAAiB,CAAjCuB,GAAMC,YADhBC,EAaEC,GAAA;AAAA,UAXC,KAAKH,EAAK,WAAWA,EAAK,SAASC;AAAA,UACnC,MAAAD;AAAA,UACA,OAAO;AAAA,UACP,aAAW5B,EAAA;AAAA,UACX,aAAaA,EAAA;AAAA,UACb,iBAAeG,EAAA;AAAA,UACf,WAAWH,EAAA;AAAA,UACX,sBAAoB;AAAA,UACpB,cAAYA,EAAA;AAAA,UACZ,aAAYgB;AAAA,UACZ,gBAAeH;AAAA,QAAA;;;;;"}
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("./JTree.vue.cjs");exports.default=e.default;
2
+ //# sourceMappingURL=JTree.vue2.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JTree.vue2.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":""}
@@ -0,0 +1,5 @@
1
+ import f from "./JTree.vue.js";
2
+ export {
3
+ f as default
4
+ };
5
+ //# sourceMappingURL=JTree.vue2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JTree.vue2.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
package/index.cjs CHANGED
@@ -1,4 +1,4 @@
1
1
  require('./assets/styles/j-components.css');
2
2
  require('./assets/styles/themes.css');
3
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});;/* empty css */;/* empty css */;/* empty css */const e=require("./components/atoms/JButton.vue.cjs"),t=require("./components/atoms/JInput.vue.cjs"),u=require("./components/atoms/JTextarea.vue.cjs"),_=require("./components/atoms/JCheckbox.vue.cjs"),r=require("./components/atoms/JCombo.vue.cjs"),a=require("./components/atoms/JSearchCombo.vue.cjs"),s=require("./components/atoms/JRadio.vue.cjs"),i=require("./components/atoms/JSwitch.vue.cjs"),p=require("./components/atoms/JDatepicker.vue.cjs"),n=require("./components/atoms/JDivider.vue.cjs"),o=require("./components/atoms/JEditor.vue.cjs"),c=require("./components/atoms/JLink.vue.cjs"),l=require("./components/atoms/JImage.vue.cjs"),J=require("./components/atoms/JBadge.vue.cjs"),d=require("./components/atoms/JProgress.vue.cjs");;/* empty css */const v=require("./components/atoms/JSpinner.vue.cjs"),q=require("./components/atoms/JAvatar.vue.cjs"),y=require("./components/atoms/JKbd.vue.cjs"),f=require("./components/atoms/JTooltip.vue.cjs"),g=require("./components/atoms/JIcon.vue.cjs"),b=require("./components/atoms/JLabel.vue.cjs"),m=require("./components/atoms/JPopover.vue.cjs"),S=require("./components/atoms/JPreview.vue.cjs"),C=require("./components/atoms/JGrid.vue.cjs"),T=require("vue-sonner"),A=require("./components/atoms/JToast.vue.cjs"),P=require("./components/molecules/JFormField.vue.cjs");;/* empty css */const h=require("./components/molecules/JGroupCombo.vue.cjs"),L=require("./components/molecules/JTabs.vue.cjs"),B=require("./components/molecules/JSearchAddr.vue.cjs"),D=require("./components/molecules/JContextMenu.vue.cjs"),F=require("./components/molecules/JCard.vue.cjs"),M=require("./components/molecules/JAlert.vue.cjs"),k=require("./components/molecules/JAccordion.vue.cjs"),x=require("./components/molecules/JTitlebar.vue.cjs"),G=require("./components/molecules/JButtonGroup.vue.cjs"),I=require("./components/molecules/JBreadcrumb.vue.cjs"),w=require("./components/organisms/JDynamicTabs.vue.cjs"),j=require("./components/organisms/JModal.vue.cjs"),E=require("./components/organisms/JFormModal.vue.cjs"),H=require("./components/organisms/JDynamicForm.vue.cjs"),K=require("./components/organisms/JSearchPanel.vue.cjs"),O=require("./components/organisms/JHeader.vue.cjs"),R=require("./components/organisms/JSidebarSimple.vue.cjs"),z=require("./components/organisms/JSidebarAdvanced.vue.cjs"),N=require("./components/organisms/JPageContainer.vue.cjs"),Q=require("./components/templates/JLayout.vue.cjs"),U=require("./components/templates/JLayoutSimple.vue.cjs"),V=require("./components/templates/JLayoutAdvanced.vue.cjs");exports.JButton=e.default;exports.JInput=t.default;exports.JTextarea=u.default;exports.JCheckbox=_.default;exports.JCombo=r.default;exports.JSearchCombo=a.default;exports.JRadio=s.default;exports.JSwitch=i.default;exports.JDatepicker=p.default;exports.JDivider=n.default;exports.JEditor=o.default;exports.JLink=c.default;exports.JImage=l.default;exports.JBadge=J.default;exports.JProgress=d.default;exports.JSpinner=v.default;exports.JAvatar=q.default;exports.JKbd=y.default;exports.JTooltip=f.default;exports.JIcon=g.default;exports.JLabel=b.default;exports.JPopover=m.default;exports.JPreview=S.default;exports.JGrid=C.default;Object.defineProperty(exports,"JToast",{enumerable:!0,get:()=>T.toast});exports.JToaster=A.default;exports.JFormField=P.default;exports.JGroupCombo=h.default;exports.JTabs=L.default;exports.JSearchAddr=B.default;exports.JContextMenu=D.default;exports.JCard=F.default;exports.JAlert=M.default;exports.JAccordion=k.default;exports.JTitlebar=x.default;exports.JButtonGroup=G.default;exports.JBreadcrumb=I.default;exports.JDynamicTabs=w.default;exports.JModal=j.default;exports.JFormModal=E.default;exports.JDynamicForm=H.default;exports.JSearchPanel=K.default;exports.JHeader=O.default;exports.JSidebarSimple=R.default;exports.JSidebarAdvanced=z.default;exports.JPageContainer=N.default;exports.JLayout=Q.default;exports.JLayoutSimple=U.default;exports.JLayoutAdvanced=V.default;
3
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});;/* empty css */;/* empty css */;/* empty css */const e=require("./components/atoms/JButton.vue.cjs"),t=require("./components/atoms/JInput.vue.cjs"),u=require("./components/atoms/JTextarea.vue.cjs"),_=require("./components/atoms/JCheckbox.vue.cjs"),r=require("./components/atoms/JCombo.vue.cjs"),a=require("./components/atoms/JSearchCombo.vue.cjs"),s=require("./components/atoms/JRadio.vue.cjs"),i=require("./components/atoms/JSwitch.vue.cjs"),p=require("./components/atoms/JDatepicker.vue.cjs"),n=require("./components/atoms/JDivider.vue.cjs"),o=require("./components/atoms/JEditor.vue.cjs"),c=require("./components/atoms/JLink.vue.cjs"),l=require("./components/atoms/JImage.vue.cjs"),J=require("./components/atoms/JBadge.vue.cjs"),d=require("./components/atoms/JProgress.vue.cjs");;/* empty css */const v=require("./components/atoms/JSpinner.vue.cjs"),q=require("./components/atoms/JAvatar.vue.cjs"),y=require("./components/atoms/JKbd.vue.cjs"),f=require("./components/atoms/JTooltip.vue.cjs"),g=require("./components/atoms/JIcon.vue.cjs"),b=require("./components/atoms/JLabel.vue.cjs"),m=require("./components/atoms/JPopover.vue.cjs"),S=require("./components/atoms/JPreview.vue.cjs"),T=require("./components/atoms/JGrid.vue.cjs"),C=require("vue-sonner"),A=require("./components/atoms/JToast.vue.cjs"),P=require("./components/molecules/JFormField.vue.cjs");;/* empty css */const h=require("./components/molecules/JGroupCombo.vue.cjs"),L=require("./components/molecules/JTabs.vue.cjs"),B=require("./components/molecules/JSearchAddr.vue.cjs"),D=require("./components/molecules/JContextMenu.vue.cjs"),F=require("./components/molecules/JCard.vue.cjs"),M=require("./components/molecules/JAlert.vue.cjs"),k=require("./components/molecules/JAccordion.vue.cjs"),x=require("./components/molecules/JTitlebar.vue.cjs"),G=require("./components/molecules/JButtonGroup.vue.cjs"),I=require("./components/molecules/JBreadcrumb.vue.cjs"),w=require("./components/organisms/JDynamicTabs.vue.cjs"),j=require("./components/organisms/JModal.vue.cjs"),E=require("./components/organisms/JFormModal.vue.cjs"),H=require("./components/organisms/JDynamicForm.vue.cjs"),K=require("./components/organisms/JSearchPanel.vue.cjs"),O=require("./components/organisms/JHeader.vue.cjs"),R=require("./components/organisms/JSidebarSimple.vue.cjs"),z=require("./components/organisms/JSidebarAdvanced.vue.cjs"),N=require("./components/organisms/JPageContainer.vue.cjs"),Q=require("./components/organisms/JTree.vue.cjs"),U=require("./components/templates/JLayout.vue.cjs"),V=require("./components/templates/JLayoutSimple.vue.cjs"),W=require("./components/templates/JLayoutAdvanced.vue.cjs");exports.JButton=e.default;exports.JInput=t.default;exports.JTextarea=u.default;exports.JCheckbox=_.default;exports.JCombo=r.default;exports.JSearchCombo=a.default;exports.JRadio=s.default;exports.JSwitch=i.default;exports.JDatepicker=p.default;exports.JDivider=n.default;exports.JEditor=o.default;exports.JLink=c.default;exports.JImage=l.default;exports.JBadge=J.default;exports.JProgress=d.default;exports.JSpinner=v.default;exports.JAvatar=q.default;exports.JKbd=y.default;exports.JTooltip=f.default;exports.JIcon=g.default;exports.JLabel=b.default;exports.JPopover=m.default;exports.JPreview=S.default;exports.JGrid=T.default;Object.defineProperty(exports,"JToast",{enumerable:!0,get:()=>C.toast});exports.JToaster=A.default;exports.JFormField=P.default;exports.JGroupCombo=h.default;exports.JTabs=L.default;exports.JSearchAddr=B.default;exports.JContextMenu=D.default;exports.JCard=F.default;exports.JAlert=M.default;exports.JAccordion=k.default;exports.JTitlebar=x.default;exports.JButtonGroup=G.default;exports.JBreadcrumb=I.default;exports.JDynamicTabs=w.default;exports.JModal=j.default;exports.JFormModal=E.default;exports.JDynamicForm=H.default;exports.JSearchPanel=K.default;exports.JHeader=O.default;exports.JSidebarSimple=R.default;exports.JSidebarAdvanced=z.default;exports.JPageContainer=N.default;exports.JTree=Q.default;exports.JLayout=U.default;exports.JLayoutSimple=V.default;exports.JLayoutAdvanced=W.default;
4
4
  //# sourceMappingURL=index.cjs.map
package/index.js CHANGED
@@ -9,7 +9,7 @@ import { default as s } from "./components/atoms/JTextarea.vue.js";
9
9
  import { default as J } from "./components/atoms/JCheckbox.vue.js";
10
10
  import { default as n } from "./components/atoms/JCombo.vue.js";
11
11
  import { default as b } from "./components/atoms/JSearchCombo.vue.js";
12
- import { default as C } from "./components/atoms/JRadio.vue.js";
12
+ import { default as T } from "./components/atoms/JRadio.vue.js";
13
13
  import { default as v } from "./components/atoms/JSwitch.vue.js";
14
14
  import { default as h } from "./components/atoms/JDatepicker.vue.js";
15
15
  import { default as L } from "./components/atoms/JDivider.vue.js";
@@ -29,7 +29,7 @@ import { default as X } from "./components/atoms/JPopover.vue.js";
29
29
  import { default as Z } from "./components/atoms/JPreview.vue.js";
30
30
  import { default as $ } from "./components/atoms/JGrid.vue.js";
31
31
  import { toast as ro } from "vue-sonner";
32
- import { default as eo } from "./components/atoms/JToast.vue.js";
32
+ import { default as ao } from "./components/atoms/JToast.vue.js";
33
33
  import { default as fo } from "./components/molecules/JFormField.vue.js";
34
34
  /* empty css */
35
35
  import { default as po } from "./components/molecules/JGroupCombo.vue.js";
@@ -38,7 +38,7 @@ import { default as xo } from "./components/molecules/JSearchAddr.vue.js";
38
38
  import { default as io } from "./components/molecules/JContextMenu.vue.js";
39
39
  import { default as co } from "./components/molecules/JCard.vue.js";
40
40
  import { default as So } from "./components/molecules/JAlert.vue.js";
41
- import { default as To } from "./components/molecules/JAccordion.vue.js";
41
+ import { default as Co } from "./components/molecules/JAccordion.vue.js";
42
42
  import { default as Ao } from "./components/molecules/JTitlebar.vue.js";
43
43
  import { default as yo } from "./components/molecules/JButtonGroup.vue.js";
44
44
  import { default as Po } from "./components/molecules/JBreadcrumb.vue.js";
@@ -51,11 +51,12 @@ import { default as Ko } from "./components/organisms/JHeader.vue.js";
51
51
  import { default as jo } from "./components/organisms/JSidebarSimple.vue.js";
52
52
  import { default as zo } from "./components/organisms/JSidebarAdvanced.vue.js";
53
53
  import { default as Oo } from "./components/organisms/JPageContainer.vue.js";
54
- import { default as Uo } from "./components/templates/JLayout.vue.js";
55
- import { default as Wo } from "./components/templates/JLayoutSimple.vue.js";
56
- import { default as Yo } from "./components/templates/JLayoutAdvanced.vue.js";
54
+ import { default as Uo } from "./components/organisms/JTree.vue.js";
55
+ import { default as Wo } from "./components/templates/JLayout.vue.js";
56
+ import { default as Yo } from "./components/templates/JLayoutSimple.vue.js";
57
+ import { default as _o } from "./components/templates/JLayoutAdvanced.vue.js";
57
58
  export {
58
- To as JAccordion,
59
+ Co as JAccordion,
59
60
  So as JAlert,
60
61
  R as JAvatar,
61
62
  I as JBadge,
@@ -81,16 +82,16 @@ export {
81
82
  l as JInput,
82
83
  q as JKbd,
83
84
  V as JLabel,
84
- Uo as JLayout,
85
- Yo as JLayoutAdvanced,
86
- Wo as JLayoutSimple,
85
+ Wo as JLayout,
86
+ _o as JLayoutAdvanced,
87
+ Yo as JLayoutSimple,
87
88
  D as JLink,
88
89
  Fo as JModal,
89
90
  Oo as JPageContainer,
90
91
  X as JPopover,
91
92
  Z as JPreview,
92
93
  w as JProgress,
93
- C as JRadio,
94
+ T as JRadio,
94
95
  xo as JSearchAddr,
95
96
  b as JSearchCombo,
96
97
  Eo as JSearchPanel,
@@ -102,7 +103,8 @@ export {
102
103
  s as JTextarea,
103
104
  Ao as JTitlebar,
104
105
  ro as JToast,
105
- eo as JToaster,
106
- N as JTooltip
106
+ ao as JToaster,
107
+ N as JTooltip,
108
+ Uo as JTree
107
109
  };
108
110
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function o(n,e){if(!Array.isArray(n)||n.length===0)return[];if(!e||e.trim()==="")return n;const i=e.toLowerCase().trim(),t=[];for(const r of n){const a=r.label?.toLowerCase().includes(i)??!1;let l;r.children&&Array.isArray(r.children)&&r.children.length>0&&(l=o(r.children,e)),(a||Array.isArray(l)&&l.length>0)&&t.push({...r,children:l})}return t}function c(n){const e=new Set,i=t=>{if(Array.isArray(t)){for(const r of t)if(r.children&&Array.isArray(r.children)&&r.children.length>0){const a=r.menuKey||r.label;e.add(a),i(r.children)}}};return i(n),e}exports.filterMenuItems=o;exports.getExpandedKeysForSearch=c;
2
+ //# sourceMappingURL=menu-utils.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"menu-utils.cjs","sources":["../../../src/lib/menu-utils.ts"],"sourcesContent":["/**\n * 메뉴 유틸리티 함수\n * Menu Utility Functions\n */\n\nimport type { SidebarMenuItem } from '@/types/sidebar-menu.types'\n\n/**\n * 검색어로 메뉴 아이템을 재귀적으로 필터링\n * Recursively filters menu items by search query\n * \n * @param items - 필터링할 메뉴 아이템 배열\n * @param query - 검색어\n * @returns 필터링된 메뉴 아이템 배열\n */\nexport function filterMenuItems(\n items: SidebarMenuItem[],\n query: string\n): SidebarMenuItem[] {\n if (!Array.isArray(items) || items.length === 0) {\n return []\n }\n\n if (!query || query.trim() === '') {\n return items\n }\n\n const normalizedQuery = query.toLowerCase().trim()\n const result: SidebarMenuItem[] = []\n\n for (const item of items) {\n const matchesLabel = item.label?.toLowerCase().includes(normalizedQuery) ?? false\n\n // 하위 메뉴 검색\n let filteredChildren: SidebarMenuItem[] | undefined = undefined\n if (item.children && Array.isArray(item.children) && item.children.length > 0) {\n filteredChildren = filterMenuItems(item.children, query)\n }\n\n // 현재 메뉴나 하위 메뉴 중 하나라도 매칭되면 포함\n if (matchesLabel || (Array.isArray(filteredChildren) && filteredChildren.length > 0)) {\n result.push({\n ...item,\n children: filteredChildren,\n })\n }\n }\n\n return result\n}\n\n/**\n * 검색 결과에서 매칭된 하위 메뉴가 있는 부모 메뉴의 키를 추출\n * Extracts keys of parent menus that have matching children in search results\n * \n * @param items - 검색 결과 메뉴 아이템 배열\n * @returns 확장해야 할 메뉴 키 Set\n */\nexport function getExpandedKeysForSearch(\n items: SidebarMenuItem[]\n): Set<number | string> {\n const keysToExpand = new Set<number | string>()\n\n const traverse = (menuItems: SidebarMenuItem[]): void => {\n if (!Array.isArray(menuItems)) {\n return\n }\n\n for (const item of menuItems) {\n if (item.children && Array.isArray(item.children) && item.children.length > 0) {\n const key = item.menuKey || item.label\n keysToExpand.add(key)\n traverse(item.children)\n }\n }\n }\n\n traverse(items)\n return keysToExpand\n}\n\n/**\n * 메뉴 아이템 배열을 평탄화하여 모든 아이템을 추출\n * Flattens menu items array to extract all items\n * \n * @param items - 메뉴 아이템 배열\n * @returns 평탄화된 메뉴 아이템 배열\n */\nexport function flattenMenuItems(items: SidebarMenuItem[]): SidebarMenuItem[] {\n const result: SidebarMenuItem[] = []\n\n const traverse = (menuItems: SidebarMenuItem[]): void => {\n if (!Array.isArray(menuItems)) {\n return\n }\n\n for (const item of menuItems) {\n result.push(item)\n if (item.children && Array.isArray(item.children) && item.children.length > 0) {\n traverse(item.children)\n }\n }\n }\n\n traverse(items)\n return result\n}\n\n/**\n * menuKey로 메뉴 아이템 찾기\n * Find menu item by menuKey\n * \n * @param items - 메뉴 아이템 배열\n * @param menuKey - 찾을 메뉴 키\n * @returns 찾은 메뉴 아이템 또는 undefined\n */\nexport function findMenuItemByKey(\n items: SidebarMenuItem[],\n menuKey: number | string\n): SidebarMenuItem | undefined {\n const flatItems = flattenMenuItems(items)\n return flatItems.find(item => item.menuKey === menuKey)\n}\n"],"names":["filterMenuItems","items","query","normalizedQuery","result","item","matchesLabel","filteredChildren","getExpandedKeysForSearch","keysToExpand","traverse","menuItems","key"],"mappings":"gFAeO,SAASA,EACdC,EACAC,EACmB,CACnB,GAAI,CAAC,MAAM,QAAQD,CAAK,GAAKA,EAAM,SAAW,EAC5C,MAAO,CAAA,EAGT,GAAI,CAACC,GAASA,EAAM,KAAA,IAAW,GAC7B,OAAOD,EAGT,MAAME,EAAkBD,EAAM,YAAA,EAAc,KAAA,EACtCE,EAA4B,CAAA,EAElC,UAAWC,KAAQJ,EAAO,CACxB,MAAMK,EAAeD,EAAK,OAAO,cAAc,SAASF,CAAe,GAAK,GAG5E,IAAII,EACAF,EAAK,UAAY,MAAM,QAAQA,EAAK,QAAQ,GAAKA,EAAK,SAAS,OAAS,IAC1EE,EAAmBP,EAAgBK,EAAK,SAAUH,CAAK,IAIrDI,GAAiB,MAAM,QAAQC,CAAgB,GAAKA,EAAiB,OAAS,IAChFH,EAAO,KAAK,CACV,GAAGC,EACH,SAAUE,CAAA,CACX,CAEL,CAEA,OAAOH,CACT,CASO,SAASI,EACdP,EACsB,CACtB,MAAMQ,MAAmB,IAEnBC,EAAYC,GAAuC,CACvD,GAAK,MAAM,QAAQA,CAAS,GAI5B,UAAWN,KAAQM,EACjB,GAAIN,EAAK,UAAY,MAAM,QAAQA,EAAK,QAAQ,GAAKA,EAAK,SAAS,OAAS,EAAG,CAC7E,MAAMO,EAAMP,EAAK,SAAWA,EAAK,MACjCI,EAAa,IAAIG,CAAG,EACpBF,EAASL,EAAK,QAAQ,CACxB,EAEJ,EAEA,OAAAK,EAAST,CAAK,EACPQ,CACT"}
@@ -0,0 +1,33 @@
1
+ function o(n, e) {
2
+ if (!Array.isArray(n) || n.length === 0)
3
+ return [];
4
+ if (!e || e.trim() === "")
5
+ return n;
6
+ const i = e.toLowerCase().trim(), t = [];
7
+ for (const r of n) {
8
+ const a = r.label?.toLowerCase().includes(i) ?? !1;
9
+ let l;
10
+ r.children && Array.isArray(r.children) && r.children.length > 0 && (l = o(r.children, e)), (a || Array.isArray(l) && l.length > 0) && t.push({
11
+ ...r,
12
+ children: l
13
+ });
14
+ }
15
+ return t;
16
+ }
17
+ function c(n) {
18
+ const e = /* @__PURE__ */ new Set(), i = (t) => {
19
+ if (Array.isArray(t)) {
20
+ for (const r of t)
21
+ if (r.children && Array.isArray(r.children) && r.children.length > 0) {
22
+ const a = r.menuKey || r.label;
23
+ e.add(a), i(r.children);
24
+ }
25
+ }
26
+ };
27
+ return i(n), e;
28
+ }
29
+ export {
30
+ o as filterMenuItems,
31
+ c as getExpandedKeysForSearch
32
+ };
33
+ //# sourceMappingURL=menu-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"menu-utils.js","sources":["../../../src/lib/menu-utils.ts"],"sourcesContent":["/**\n * 메뉴 유틸리티 함수\n * Menu Utility Functions\n */\n\nimport type { SidebarMenuItem } from '@/types/sidebar-menu.types'\n\n/**\n * 검색어로 메뉴 아이템을 재귀적으로 필터링\n * Recursively filters menu items by search query\n * \n * @param items - 필터링할 메뉴 아이템 배열\n * @param query - 검색어\n * @returns 필터링된 메뉴 아이템 배열\n */\nexport function filterMenuItems(\n items: SidebarMenuItem[],\n query: string\n): SidebarMenuItem[] {\n if (!Array.isArray(items) || items.length === 0) {\n return []\n }\n\n if (!query || query.trim() === '') {\n return items\n }\n\n const normalizedQuery = query.toLowerCase().trim()\n const result: SidebarMenuItem[] = []\n\n for (const item of items) {\n const matchesLabel = item.label?.toLowerCase().includes(normalizedQuery) ?? false\n\n // 하위 메뉴 검색\n let filteredChildren: SidebarMenuItem[] | undefined = undefined\n if (item.children && Array.isArray(item.children) && item.children.length > 0) {\n filteredChildren = filterMenuItems(item.children, query)\n }\n\n // 현재 메뉴나 하위 메뉴 중 하나라도 매칭되면 포함\n if (matchesLabel || (Array.isArray(filteredChildren) && filteredChildren.length > 0)) {\n result.push({\n ...item,\n children: filteredChildren,\n })\n }\n }\n\n return result\n}\n\n/**\n * 검색 결과에서 매칭된 하위 메뉴가 있는 부모 메뉴의 키를 추출\n * Extracts keys of parent menus that have matching children in search results\n * \n * @param items - 검색 결과 메뉴 아이템 배열\n * @returns 확장해야 할 메뉴 키 Set\n */\nexport function getExpandedKeysForSearch(\n items: SidebarMenuItem[]\n): Set<number | string> {\n const keysToExpand = new Set<number | string>()\n\n const traverse = (menuItems: SidebarMenuItem[]): void => {\n if (!Array.isArray(menuItems)) {\n return\n }\n\n for (const item of menuItems) {\n if (item.children && Array.isArray(item.children) && item.children.length > 0) {\n const key = item.menuKey || item.label\n keysToExpand.add(key)\n traverse(item.children)\n }\n }\n }\n\n traverse(items)\n return keysToExpand\n}\n\n/**\n * 메뉴 아이템 배열을 평탄화하여 모든 아이템을 추출\n * Flattens menu items array to extract all items\n * \n * @param items - 메뉴 아이템 배열\n * @returns 평탄화된 메뉴 아이템 배열\n */\nexport function flattenMenuItems(items: SidebarMenuItem[]): SidebarMenuItem[] {\n const result: SidebarMenuItem[] = []\n\n const traverse = (menuItems: SidebarMenuItem[]): void => {\n if (!Array.isArray(menuItems)) {\n return\n }\n\n for (const item of menuItems) {\n result.push(item)\n if (item.children && Array.isArray(item.children) && item.children.length > 0) {\n traverse(item.children)\n }\n }\n }\n\n traverse(items)\n return result\n}\n\n/**\n * menuKey로 메뉴 아이템 찾기\n * Find menu item by menuKey\n * \n * @param items - 메뉴 아이템 배열\n * @param menuKey - 찾을 메뉴 키\n * @returns 찾은 메뉴 아이템 또는 undefined\n */\nexport function findMenuItemByKey(\n items: SidebarMenuItem[],\n menuKey: number | string\n): SidebarMenuItem | undefined {\n const flatItems = flattenMenuItems(items)\n return flatItems.find(item => item.menuKey === menuKey)\n}\n"],"names":["filterMenuItems","items","query","normalizedQuery","result","item","matchesLabel","filteredChildren","getExpandedKeysForSearch","keysToExpand","traverse","menuItems","key"],"mappings":"AAeO,SAASA,EACdC,GACAC,GACmB;AACnB,MAAI,CAAC,MAAM,QAAQD,CAAK,KAAKA,EAAM,WAAW;AAC5C,WAAO,CAAA;AAGT,MAAI,CAACC,KAASA,EAAM,KAAA,MAAW;AAC7B,WAAOD;AAGT,QAAME,IAAkBD,EAAM,YAAA,EAAc,KAAA,GACtCE,IAA4B,CAAA;AAElC,aAAWC,KAAQJ,GAAO;AACxB,UAAMK,IAAeD,EAAK,OAAO,cAAc,SAASF,CAAe,KAAK;AAG5E,QAAII;AACJ,IAAIF,EAAK,YAAY,MAAM,QAAQA,EAAK,QAAQ,KAAKA,EAAK,SAAS,SAAS,MAC1EE,IAAmBP,EAAgBK,EAAK,UAAUH,CAAK,KAIrDI,KAAiB,MAAM,QAAQC,CAAgB,KAAKA,EAAiB,SAAS,MAChFH,EAAO,KAAK;AAAA,MACV,GAAGC;AAAA,MACH,UAAUE;AAAA,IAAA,CACX;AAAA,EAEL;AAEA,SAAOH;AACT;AASO,SAASI,EACdP,GACsB;AACtB,QAAMQ,wBAAmB,IAAA,GAEnBC,IAAW,CAACC,MAAuC;AACvD,QAAK,MAAM,QAAQA,CAAS;AAI5B,iBAAWN,KAAQM;AACjB,YAAIN,EAAK,YAAY,MAAM,QAAQA,EAAK,QAAQ,KAAKA,EAAK,SAAS,SAAS,GAAG;AAC7E,gBAAMO,IAAMP,EAAK,WAAWA,EAAK;AACjC,UAAAI,EAAa,IAAIG,CAAG,GACpBF,EAASL,EAAK,QAAQ;AAAA,QACxB;AAAA;AAAA,EAEJ;AAEA,SAAAK,EAAST,CAAK,GACPQ;AACT;"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@j-solution/components",
3
3
  "description": "Vue 3 Atomic Design component kit for enterprise dashboards",
4
- "version": "1.2.0",
4
+ "version": "1.3.0",
5
5
  "type": "module",
6
6
  "main": "./index.cjs",
7
7
  "module": "./index.js",
package/types/index.d.ts CHANGED
@@ -247,12 +247,12 @@ showTitlebar: boolean;
247
247
  contentScroll: boolean;
248
248
  }, {}, {}, {}, string, ComponentProvideOptions, false, {}, HTMLDivElement>;
249
249
 
250
- declare const __VLS_component_21: DefineComponent<__VLS_Props_37, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {}, string, PublicProps, Readonly<__VLS_Props_37> & Readonly<{}>, {
251
- styletype: StyleType_28;
250
+ declare const __VLS_component_21: DefineComponent<__VLS_Props_38, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {}, string, PublicProps, Readonly<__VLS_Props_38> & Readonly<{}>, {
251
+ styletype: StyleType_29;
252
252
  contentScroll: boolean;
253
253
  }, {}, {}, {}, string, ComponentProvideOptions, false, {}, HTMLDivElement>;
254
254
 
255
- declare const __VLS_component_22: DefineComponent<__VLS_Props_38, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {}, string, PublicProps, Readonly<__VLS_Props_38> & Readonly<{}>, {
255
+ declare const __VLS_component_22: DefineComponent<__VLS_Props_39, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {}, string, PublicProps, Readonly<__VLS_Props_39> & Readonly<{}>, {
256
256
  styletype: "default" | "minimal";
257
257
  permissions: MenuPermission[];
258
258
  menuItems: SidebarMenuItem[];
@@ -803,8 +803,27 @@ declare type __VLS_Props_36 = {
803
803
  };
804
804
 
805
805
  declare type __VLS_Props_37 = {
806
- /** 레이아웃 스타일 타입 */
806
+ /** 트리 노드 데이터 */
807
+ items: SidebarMenuItem[];
808
+ /** 펼쳐진 노드 키 목록 (v-model 지원, 배열) */
809
+ expandedKeys?: (number | string)[];
810
+ /** 현재 선택(하이라이트)된 노드의 menuKey (v-model 지원) */
811
+ activeKey?: number | string | null;
812
+ /** 권한 목록 */
813
+ permissions?: MenuPermission[];
814
+ /** 최대 깊이 제한 (무한 루프 방지) */
815
+ maxDepth?: number;
816
+ /** 스타일 타입 */
807
817
  styletype?: StyleType_28;
818
+ /** 검색어 (노드 필터링) */
819
+ searchQuery?: string;
820
+ /** 추가 CSS 클래스 */
821
+ class?: string;
822
+ };
823
+
824
+ declare type __VLS_Props_38 = {
825
+ /** 레이아웃 스타일 타입 */
826
+ styletype?: StyleType_29;
808
827
  /** 콘텐츠 영역 스크롤 가능 여부 */
809
828
  contentScroll?: boolean;
810
829
  /** 추가 CSS 클래스 */
@@ -841,7 +860,7 @@ declare type __VLS_Props_37 = {
841
860
  * </JLayoutSimple>
842
861
  * ```
843
862
  */
844
- declare type __VLS_Props_38 = {
863
+ declare type __VLS_Props_39 = {
845
864
  /** 레이아웃 스타일 타입 */
846
865
  styletype?: 'default' | 'minimal';
847
866
  /** 콘텐츠 영역 스크롤 가능 여부 */
@@ -2210,6 +2229,25 @@ closeButton: boolean;
2210
2229
 
2211
2230
  export declare const JTooltip: __VLS_WithTemplateSlots_6<typeof __VLS_component_6, __VLS_TemplateResult_6["slots"]>;
2212
2231
 
2232
+ export declare const JTree: DefineComponent<__VLS_Props_37, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {
2233
+ expandChange: (menuKey: string | number | undefined, expanded: boolean) => any;
2234
+ nodeClick: (event: MenuClickEvent) => any;
2235
+ "update:expandedKeys": (keys: (string | number)[]) => any;
2236
+ "update:activeKey": (key: string | number | null) => any;
2237
+ }, string, PublicProps, Readonly<__VLS_Props_37> & Readonly<{
2238
+ onExpandChange?: ((menuKey: string | number | undefined, expanded: boolean) => any) | undefined;
2239
+ onNodeClick?: ((event: MenuClickEvent) => any) | undefined;
2240
+ "onUpdate:expandedKeys"?: ((keys: (string | number)[]) => any) | undefined;
2241
+ "onUpdate:activeKey"?: ((key: string | number | null) => any) | undefined;
2242
+ }>, {
2243
+ styletype: StyleType_28;
2244
+ permissions: MenuPermission[];
2245
+ expandedKeys: (number | string)[];
2246
+ maxDepth: number;
2247
+ activeKey: number | string | null;
2248
+ searchQuery: string;
2249
+ }, {}, {}, {}, string, ComponentProvideOptions, false, {}, HTMLDivElement>;
2250
+
2213
2251
  declare type LabelPosition = 'right' | 'left' | 'top' | 'bottom';
2214
2252
 
2215
2253
  /**
@@ -2473,6 +2511,51 @@ declare type StyleType_26 = 'default' | 'minimal';
2473
2511
  */
2474
2512
  declare type StyleType_27 = 'default' | 'minimal';
2475
2513
 
2514
+ /**
2515
+ * JTree - 트리 뷰 컴포넌트
2516
+ * Tree View Component
2517
+ *
2518
+ * @description
2519
+ * 계층 데이터를 조회/탐색하는 읽기 전용 트리 컴포넌트입니다.
2520
+ * JDynamicMenuItem을 재귀적으로 렌더링하며, 네비게이션 기능을 비활성화하고
2521
+ * nodeClick 이벤트로 선택을 처리합니다.
2522
+ *
2523
+ * @example
2524
+ * ```vue
2525
+ * <JTree
2526
+ * :items="treeData"
2527
+ * v-model:expanded-keys="expandedKeys"
2528
+ * v-model:active-key="activeKey"
2529
+ * @node-click="handleNodeClick"
2530
+ * />
2531
+ * ```
2532
+ *
2533
+ * @example JSON 트리 데이터 예시
2534
+ * ```json
2535
+ * [
2536
+ * {
2537
+ * "label": "프로그램 관리",
2538
+ * "icon": "folder",
2539
+ * "menuType": "F",
2540
+ * "menuKey": 1,
2541
+ * "children": [
2542
+ * {
2543
+ * "label": "시스템 관리",
2544
+ * "menuType": "L",
2545
+ * "menuKey": 11
2546
+ * },
2547
+ * {
2548
+ * "label": "사용자 관리",
2549
+ * "menuType": "L",
2550
+ * "menuKey": 12
2551
+ * }
2552
+ * ]
2553
+ * }
2554
+ * ]
2555
+ * ```
2556
+ */
2557
+ declare type StyleType_28 = 'default' | 'minimal';
2558
+
2476
2559
  /**
2477
2560
  * JLayout - 기본 레이아웃 컴포넌트 (templates)
2478
2561
  * Base Layout Component
@@ -2508,7 +2591,7 @@ declare type StyleType_27 = 'default' | 'minimal';
2508
2591
  * </JLayout>
2509
2592
  * ```
2510
2593
  */
2511
- declare type StyleType_28 = 'default' | 'minimal';
2594
+ declare type StyleType_29 = 'default' | 'minimal';
2512
2595
 
2513
2596
  declare type StyleType_3 = 'default' | 'error' | 'success' | 'warning' | 'sm' | 'lg';
2514
2597