@j-solution/components 2.0.5 → 2.0.7

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 (39) hide show
  1. package/README.md +4 -6
  2. package/assets/jwms-portal-frontend-Cu-V5XAR.css +1 -0
  3. package/assets/styles/j-components.css +1 -1
  4. package/assets/styles/main.css +22 -0
  5. package/components/atoms/JSplitter.vue.cjs +1 -1
  6. package/components/atoms/JSplitter.vue.js +2 -2
  7. package/components/atoms/JSplitter.vue2.cjs +1 -1
  8. package/components/atoms/JSplitter.vue2.cjs.map +1 -1
  9. package/components/atoms/JSplitter.vue2.js +30 -28
  10. package/components/atoms/JSplitter.vue2.js.map +1 -1
  11. package/components/molecules/JFormField.vue.cjs +1 -1
  12. package/components/molecules/JFormField.vue.js +2 -2
  13. package/components/molecules/JFormField.vue2.cjs +1 -1
  14. package/components/molecules/JFormField.vue2.cjs.map +1 -1
  15. package/components/molecules/JFormField.vue2.js +80 -79
  16. package/components/molecules/JFormField.vue2.js.map +1 -1
  17. package/components/templates/JLayout.vue.cjs +6 -1
  18. package/components/templates/JLayout.vue.cjs.map +1 -1
  19. package/components/templates/JLayout.vue.js +10 -43
  20. package/components/templates/JLayout.vue.js.map +1 -1
  21. package/components/templates/JLayout.vue2.cjs +1 -1
  22. package/components/templates/JLayout.vue2.cjs.map +1 -1
  23. package/components/templates/JLayout.vue2.js +71 -2
  24. package/components/templates/JLayout.vue2.js.map +1 -1
  25. package/components/templates/JLayoutAdvanced.vue.cjs +1 -1
  26. package/components/templates/JLayoutAdvanced.vue.js +12 -12
  27. package/components/templates/JLayoutSimple.vue.cjs +1 -1
  28. package/components/templates/JLayoutSimple.vue.cjs.map +1 -1
  29. package/components/templates/JLayoutSimple.vue.js +32 -22
  30. package/components/templates/JLayoutSimple.vue.js.map +1 -1
  31. package/composables/useBreakpoint.cjs +2 -0
  32. package/composables/useBreakpoint.cjs.map +1 -0
  33. package/composables/useBreakpoint.js +27 -0
  34. package/composables/useBreakpoint.js.map +1 -0
  35. package/index.cjs +1 -1
  36. package/index.js +30 -28
  37. package/package.json +2 -1
  38. package/types/index.d.ts +22 -8
  39. package/assets/jwms-portal-frontend-CrtMPGot.css +0 -1
@@ -19,6 +19,28 @@
19
19
  /* 다크 모드 변수는 각 테마에서 정의됨 */
20
20
  }
21
21
 
22
+ /* ── 반응형 토큰 오버라이드 ─────────────────────────────── */
23
+ /* 모바일 (<768px): 터치 타겟 증가, 폰트 스케일링 */
24
+ @media (max-width: 767px) {
25
+ :root {
26
+ --j-grid-row-h: 36px;
27
+ --j-grid-header-h: 32px;
28
+ --j-grid-footer-h: 28px;
29
+ --j-control-h: 36px;
30
+ }
31
+ body {
32
+ font-size: 14px;
33
+ }
34
+ }
35
+
36
+ /* 태블릿 (768px ~ 1023px) */
37
+ @media (min-width: 768px) and (max-width: 1023px) {
38
+ :root {
39
+ --j-grid-row-h: 28px;
40
+ --j-grid-header-h: 28px;
41
+ }
42
+ }
43
+
22
44
  * {
23
45
  border-color: hsl(var(--border));
24
46
  }
@@ -3,5 +3,5 @@
3
3
  for (const [t_key, t_val] of t_opts)
4
4
  t_merged[t_key] = t_val;
5
5
  return t_merged;
6
- };,u=t(e.default,[["__scopeId","data-v-393b9055"]]);exports.default=u;
6
+ };,u=t(e.default,[["__scopeId","data-v-ebf6747f"]]);exports.default=u;
7
7
  //# sourceMappingURL=JSplitter.vue.cjs.map
@@ -6,8 +6,8 @@ const o = (o_comp, o_opts) => {
6
6
  o_merged[o_key] = o_val;
7
7
  return o_merged;
8
8
  };
9
- const _ = /* @__PURE__ */ o(t, [["__scopeId", "data-v-393b9055"]]);
9
+ const e = /* @__PURE__ */ o(t, [["__scopeId", "data-v-ebf6747f"]]);
10
10
  export {
11
- _ as default
11
+ e as default
12
12
  };
13
13
  //# sourceMappingURL=JSplitter.vue.js.map
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),s=require("../../lib/utils.cjs"),r=require("../shadcn/resizable/ResizableHandle.vue.cjs"),u=require("../shadcn/resizable/ResizablePanelGroup.vue.cjs"),l=require("reka-ui"),o=e.defineComponent({__name:"JSplitter",props:{direction:{default:"horizontal"},defaultSize:{default:40},minSize:{default:20},maxSize:{},secondMinSize:{},secondMaxSize:{},withHandle:{type:Boolean,default:!1},gap:{default:2},class:{}},setup(t){const i=t,n=e.computed(()=>100-i.defaultSize);return(a,d)=>(e.openBlock(),e.createBlock(e.unref(u.default),{direction:t.direction,class:e.normalizeClass(e.unref(s.cn)("jsplitter-group min-h-0 min-w-0",a.$props.class))},{default:e.withCtx(()=>[e.createVNode(e.unref(l.SplitterPanel),{"default-size":t.defaultSize,"min-size":t.minSize,"max-size":t.maxSize,class:e.normalizeClass(t.gap>0?t.direction==="horizontal"?"pr-[calc(var(--gap)/2)]":"pb-[calc(var(--gap)/2)]":""),style:e.normalizeStyle(t.gap>0?{"--gap":`${t.gap}px`}:{})},{default:e.withCtx(()=>[e.renderSlot(a.$slots,"first",{},void 0,!0),e.renderSlot(a.$slots,"left",{},void 0,!0),e.renderSlot(a.$slots,"top",{},void 0,!0)]),_:3},8,["default-size","min-size","max-size","class","style"]),e.createVNode(e.unref(r.default),{"with-handle":t.withHandle,class:"jsplitter-handle"},null,8,["with-handle"]),e.createVNode(e.unref(l.SplitterPanel),{"default-size":n.value,"min-size":t.secondMinSize,"max-size":t.secondMaxSize,class:e.normalizeClass(t.gap>0?t.direction==="horizontal"?"pl-[calc(var(--gap)/2)]":"pt-[calc(var(--gap)/2)]":""),style:e.normalizeStyle(t.gap>0?{"--gap":`${t.gap}px`}:{})},{default:e.withCtx(()=>[e.renderSlot(a.$slots,"second",{},void 0,!0),e.renderSlot(a.$slots,"right",{},void 0,!0),e.renderSlot(a.$slots,"bottom",{},void 0,!0)]),_:3},8,["default-size","min-size","max-size","class","style"])]),_:3},8,["direction","class"]))}});exports.default=o;
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),u=require("../../lib/utils.cjs"),d=require("../shadcn/resizable/ResizableHandle.vue.cjs"),c=require("../shadcn/resizable/ResizablePanelGroup.vue.cjs"),s=require("reka-ui"),f=require("../../composables/useBreakpoint.cjs"),z=e.defineComponent({__name:"JSplitter",props:{direction:{default:"horizontal"},defaultSize:{default:40},minSize:{default:20},maxSize:{},secondMinSize:{},secondMaxSize:{},withHandle:{type:Boolean,default:!1},gap:{default:2},class:{},responsive:{type:Boolean,default:!0}},setup(t){const i=t,{isMobile:r}=f.useBreakpoint(),l=e.computed(()=>i.responsive&&r.value&&i.direction==="horizontal"?"vertical":i.direction),n=e.computed(()=>i.responsive&&r.value&&i.direction==="horizontal"?50:i.defaultSize),o=e.computed(()=>100-n.value);return(a,p)=>(e.openBlock(),e.createBlock(e.unref(c.default),{direction:l.value,class:e.normalizeClass(e.unref(u.cn)("jsplitter-group min-h-0 min-w-0",a.$props.class))},{default:e.withCtx(()=>[e.createVNode(e.unref(s.SplitterPanel),{"default-size":n.value,"min-size":t.minSize,"max-size":t.maxSize,class:e.normalizeClass(t.gap>0?l.value==="horizontal"?"pr-[calc(var(--gap)/2)]":"pb-[calc(var(--gap)/2)]":""),style:e.normalizeStyle(t.gap>0?{"--gap":`${t.gap}px`}:{})},{default:e.withCtx(()=>[e.renderSlot(a.$slots,"first",{},void 0,!0),e.renderSlot(a.$slots,"left",{},void 0,!0),e.renderSlot(a.$slots,"top",{},void 0,!0)]),_:3},8,["default-size","min-size","max-size","class","style"]),e.createVNode(e.unref(d.default),{"with-handle":t.withHandle,class:"jsplitter-handle"},null,8,["with-handle"]),e.createVNode(e.unref(s.SplitterPanel),{"default-size":o.value,"min-size":t.secondMinSize,"max-size":t.secondMaxSize,class:e.normalizeClass(t.gap>0?l.value==="horizontal"?"pl-[calc(var(--gap)/2)]":"pt-[calc(var(--gap)/2)]":""),style:e.normalizeStyle(t.gap>0?{"--gap":`${t.gap}px`}:{})},{default:e.withCtx(()=>[e.renderSlot(a.$slots,"second",{},void 0,!0),e.renderSlot(a.$slots,"right",{},void 0,!0),e.renderSlot(a.$slots,"bottom",{},void 0,!0)]),_:3},8,["default-size","min-size","max-size","class","style"])]),_:3},8,["direction","class"]))}});exports.default=z;
2
2
  //# sourceMappingURL=JSplitter.vue2.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"JSplitter.vue2.cjs","sources":["../../../../src/components/atoms/JSplitter.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { cn } from '@/lib/utils'\nimport {\n ResizablePanelGroup,\n ResizablePanel,\n ResizableHandle,\n} from '@/components/shadcn/resizable'\n\ntype Orientation = 'horizontal' | 'vertical'\n\nconst props = withDefaults(\n defineProps<{\n /** 분할 방향 (horizontal: 좌우, vertical: 상하) */\n direction?: Orientation\n /** 첫 번째 패널의 기본 크기 (%) */\n defaultSize?: number\n /** 첫 번째 패널의 최소 크기 (%) */\n minSize?: number\n /** 첫 번째 패널의 최대 크기 (%) */\n maxSize?: number\n /** 두 번째 패널의 최소 크기 (%) */\n secondMinSize?: number\n /** 두 번째 패널의 최대 크기 (%) */\n secondMaxSize?: number\n /** ResizableHandle에 grip 아이콘 표시 여부 */\n withHandle?: boolean\n /** 패널 간 여백 (px) */\n gap?: number\n /** 추가 CSS 클래스 */\n class?: string\n }>(),\n {\n direction: 'horizontal',\n defaultSize: 40,\n minSize: 20,\n withHandle: false,\n gap: 2,\n },\n)\n\n// 두 번째 패널의 기본 크기 계산\nconst secondDefaultSize = computed(() => 100 - props.defaultSize)\n</script>\n\n<template>\n <ResizablePanelGroup :direction=\"direction\" :class=\"cn('jsplitter-group min-h-0 min-w-0', $props.class)\">\n <!-- 첫 번째 패널 (좌측/상단) -->\n <ResizablePanel \n :default-size=\"defaultSize\" \n :min-size=\"minSize\" \n :max-size=\"maxSize\"\n :class=\"gap > 0 ? (direction === 'horizontal' ? 'pr-[calc(var(--gap)/2)]' : 'pb-[calc(var(--gap)/2)]') : ''\"\n :style=\"gap > 0 ? { '--gap': `${gap}px` } : {}\"\n >\n <slot name=\"first\" />\n <!-- direction=\"horizontal\"일 때 left, vertical일 때 top으로도 사용 가능 -->\n <slot name=\"left\" />\n <slot name=\"top\" />\n </ResizablePanel>\n\n <!-- 크기 조정 핸들 -->\n <ResizableHandle :with-handle=\"withHandle\" class=\"jsplitter-handle\" />\n\n <!-- 두 번째 패널 (우측/하단) -->\n <ResizablePanel\n :default-size=\"secondDefaultSize\"\n :min-size=\"secondMinSize\"\n :max-size=\"secondMaxSize\"\n :class=\"gap > 0 ? (direction === 'horizontal' ? 'pl-[calc(var(--gap)/2)]' : 'pt-[calc(var(--gap)/2)]') : ''\"\n :style=\"gap > 0 ? { '--gap': `${gap}px` } : {}\"\n >\n <slot name=\"second\" />\n <!-- direction=\"horizontal\"일 때 right, vertical일 때 bottom으로도 사용 가능 -->\n <slot name=\"right\" />\n <slot name=\"bottom\" />\n </ResizablePanel>\n </ResizablePanelGroup>\n</template>\n\n<style scoped>\n/* ── overflow 보호 ──────────────────────────────────────────────────── */\n:deep(.jsplitter-group [data-panel]) {\n min-width: 0;\n min-height: 0;\n}\n\n/* ── horizontal 핸들: 8px 히트영역 ─────────────────────────────────── */\n:deep(.jsplitter-handle[data-orientation=\"horizontal\"]) {\n width: 8px !important;\n background: transparent !important;\n position: relative;\n z-index: 3;\n transition: background 0.15s ease;\n}\n\n/* ── vertical 핸들: 8px 히트영역 ───────────────────────────────────── */\n:deep(.jsplitter-handle[data-orientation=\"vertical\"]) {\n height: 8px !important;\n background: transparent !important;\n position: relative;\n z-index: 3;\n transition: background 0.15s ease;\n}\n\n/* ── 시각선: 1px (after:w-1 override) ──────────────────────────────── */\n:deep(.jsplitter-handle::after) {\n width: 1px !important;\n background: hsl(var(--border)) !important;\n transition: background 0.15s ease, width 0.12s ease !important;\n}\n\n:deep(.jsplitter-handle[data-orientation=\"vertical\"]::after) {\n height: 1px !important;\n width: 100% !important;\n}\n\n/* ── 그립 도트 인디케이터 (::before) ───────────────────────────────── */\n:deep(.jsplitter-handle::before) {\n content: '';\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 3px;\n height: 20px;\n background-image: radial-gradient(circle, hsl(var(--muted-foreground) / 0.5) 1px, transparent 1px);\n background-size: 3px 6px;\n background-repeat: repeat-y;\n border-radius: 2px;\n opacity: 0.5;\n transition: opacity 0.15s ease;\n pointer-events: none;\n z-index: 4;\n}\n\n:deep(.jsplitter-handle[data-orientation=\"vertical\"]::before) {\n width: 20px;\n height: 3px;\n background-size: 6px 3px;\n background-repeat: repeat-x;\n background-image: radial-gradient(circle, hsl(var(--muted-foreground) / 0.5) 1px, transparent 1px);\n}\n\n/* ── hover ──────────────────────────────────────────────────────────── */\n:deep(.jsplitter-handle:hover) {\n background: hsl(var(--primary) / 0.04) !important;\n}\n\n:deep(.jsplitter-handle:hover::after) {\n width: 2px !important;\n background: hsl(var(--primary) / 0.55) !important;\n}\n\n:deep(.jsplitter-handle:hover::before) {\n opacity: 1;\n}\n\n/* ── drag / active ──────────────────────────────────────────────────── */\n:deep(.jsplitter-handle[data-state=\"drag\"]),\n:deep(.jsplitter-handle:active) {\n background: hsl(var(--primary) / 0.06) !important;\n}\n\n:deep(.jsplitter-handle[data-state=\"drag\"]::after),\n:deep(.jsplitter-handle:active::after) {\n width: 2px !important;\n background: hsl(var(--primary)) !important;\n}\n\n:deep(.jsplitter-handle[data-state=\"drag\"]::before),\n:deep(.jsplitter-handle:active::before) {\n opacity: 1;\n background-image: radial-gradient(circle, hsl(var(--primary) / 0.65) 1px, transparent 1px);\n}\n</style>\n"],"names":["props","__props","secondDefaultSize","computed","_createBlock","_unref","ResizablePanelGroup","_normalizeClass","cn","$props","_createVNode","ResizablePanel","_normalizeStyle","_renderSlot","_ctx","ResizableHandle"],"mappings":"kiBAWA,MAAMA,EAAQC,EA+BRC,EAAoBC,EAAAA,SAAS,IAAM,IAAMH,EAAM,WAAW,8BAI9DI,EAAAA,YA+BsBC,EAAAA,MAAAC,EAAAA,OAAA,EAAA,CA/BA,UAAWL,EAAA,UAAY,MAAKM,EAAAA,eAAEF,EAAAA,MAAAG,EAAAA,EAAA,EAAE,kCAAoCC,EAAAA,OAAO,KAAK,CAAA,CAAA,qBAEpG,IAWiB,CAXjBC,cAWiBL,EAAAA,MAAAM,EAAAA,aAAA,EAAA,CAVd,eAAcV,EAAA,YACd,WAAUA,EAAA,QACV,WAAUA,EAAA,QACV,MAAKM,EAAAA,eAAEN,EAAA,IAAG,EAAQA,EAAA,YAAS,aAAA,0BAAA,0BAAA,EAAA,EAC3B,MAAKW,EAAAA,eAAEX,EAAA,IAAG,EAAA,CAAA,QAAA,GAAqBA,EAAA,GAAG,IAAA,EAAA,CAAA,CAAA,CAAA,qBAEnC,IAAqB,CAArBY,EAAAA,WAAqBC,EAAA,OAAA,QAAA,CAAA,EAAA,OAAA,EAAA,EAErBD,EAAAA,WAAoBC,EAAA,OAAA,OAAA,CAAA,EAAA,OAAA,EAAA,EACpBD,EAAAA,WAAmBC,EAAA,OAAA,MAAA,CAAA,EAAA,OAAA,EAAA,CAAA,kEAIrBJ,cAAsEL,EAAAA,MAAAU,EAAAA,OAAA,EAAA,CAApD,cAAad,EAAA,WAAY,MAAM,kBAAA,0BAGjDS,cAWiBL,EAAAA,MAAAM,EAAAA,aAAA,EAAA,CAVd,eAAcT,EAAA,MACd,WAAUD,EAAA,cACV,WAAUA,EAAA,cACV,MAAKM,EAAAA,eAAEN,EAAA,IAAG,EAAQA,EAAA,YAAS,aAAA,0BAAA,0BAAA,EAAA,EAC3B,MAAKW,EAAAA,eAAEX,EAAA,IAAG,EAAA,CAAA,QAAA,GAAqBA,EAAA,GAAG,IAAA,EAAA,CAAA,CAAA,CAAA,qBAEnC,IAAsB,CAAtBY,EAAAA,WAAsBC,EAAA,OAAA,SAAA,CAAA,EAAA,OAAA,EAAA,EAEtBD,EAAAA,WAAqBC,EAAA,OAAA,QAAA,CAAA,EAAA,OAAA,EAAA,EACrBD,EAAAA,WAAsBC,EAAA,OAAA,SAAA,CAAA,EAAA,OAAA,EAAA,CAAA"}
1
+ {"version":3,"file":"JSplitter.vue2.cjs","sources":["../../../../src/components/atoms/JSplitter.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { cn } from '@/lib/utils'\nimport {\n ResizablePanelGroup,\n ResizablePanel,\n ResizableHandle,\n} from '@/components/shadcn/resizable'\nimport { useBreakpoint } from '@/composables/useBreakpoint'\n\ntype Orientation = 'horizontal' | 'vertical'\n\nconst props = withDefaults(\n defineProps<{\n /** 분할 방향 (horizontal: 좌우, vertical: 상하) */\n direction?: Orientation\n /** 첫 번째 패널의 기본 크기 (%) */\n defaultSize?: number\n /** 첫 번째 패널의 최소 크기 (%) */\n minSize?: number\n /** 첫 번째 패널의 최대 크기 (%) */\n maxSize?: number\n /** 두 번째 패널의 최소 크기 (%) */\n secondMinSize?: number\n /** 두 번째 패널의 최대 크기 (%) */\n secondMaxSize?: number\n /** ResizableHandle에 grip 아이콘 표시 여부 */\n withHandle?: boolean\n /** 패널 간 여백 (px) */\n gap?: number\n /** 추가 CSS 클래스 */\n class?: string\n /** 모바일에서 자동 수직 전환 (기본: true) */\n responsive?: boolean\n }>(),\n {\n direction: 'horizontal',\n defaultSize: 40,\n minSize: 20,\n withHandle: false,\n gap: 2,\n responsive: true,\n },\n)\n\nconst { isMobile } = useBreakpoint()\n\n// 모바일에서 horizontal → vertical 자동 전환\nconst effectiveDirection = computed<Orientation>(() => {\n if (props.responsive && isMobile.value && props.direction === 'horizontal') {\n return 'vertical'\n }\n return props.direction\n})\n\n// 모바일 수직 전환 시 50:50 균등 분할\nconst effectiveDefaultSize = computed(() => {\n if (props.responsive && isMobile.value && props.direction === 'horizontal') {\n return 50\n }\n return props.defaultSize\n})\n\n// 두 번째 패널의 기본 크기 계산\nconst secondDefaultSize = computed(() => 100 - effectiveDefaultSize.value)\n</script>\n\n<template>\n <ResizablePanelGroup :direction=\"effectiveDirection\" :class=\"cn('jsplitter-group min-h-0 min-w-0', $props.class)\">\n <!-- 첫 번째 패널 (좌측/상단) -->\n <ResizablePanel \n :default-size=\"effectiveDefaultSize\" \n :min-size=\"minSize\" \n :max-size=\"maxSize\"\n :class=\"gap > 0 ? (effectiveDirection === 'horizontal' ? 'pr-[calc(var(--gap)/2)]' : 'pb-[calc(var(--gap)/2)]') : ''\"\n :style=\"gap > 0 ? { '--gap': `${gap}px` } : {}\"\n >\n <slot name=\"first\" />\n <!-- direction=\"horizontal\"일 때 left, vertical일 때 top으로도 사용 가능 -->\n <slot name=\"left\" />\n <slot name=\"top\" />\n </ResizablePanel>\n\n <!-- 크기 조정 핸들 -->\n <ResizableHandle :with-handle=\"withHandle\" class=\"jsplitter-handle\" />\n\n <!-- 두 번째 패널 (우측/하단) -->\n <ResizablePanel\n :default-size=\"secondDefaultSize\"\n :min-size=\"secondMinSize\"\n :max-size=\"secondMaxSize\"\n :class=\"gap > 0 ? (effectiveDirection === 'horizontal' ? 'pl-[calc(var(--gap)/2)]' : 'pt-[calc(var(--gap)/2)]') : ''\"\n :style=\"gap > 0 ? { '--gap': `${gap}px` } : {}\"\n >\n <slot name=\"second\" />\n <!-- direction=\"horizontal\"일 때 right, vertical일 때 bottom으로도 사용 가능 -->\n <slot name=\"right\" />\n <slot name=\"bottom\" />\n </ResizablePanel>\n </ResizablePanelGroup>\n</template>\n\n<style scoped>\n/* ── overflow 보호 ──────────────────────────────────────────────────── */\n:deep(.jsplitter-group [data-panel]) {\n min-width: 0;\n min-height: 0;\n}\n\n/* ── horizontal 핸들: 8px 히트영역 ─────────────────────────────────── */\n:deep(.jsplitter-handle[data-orientation=\"horizontal\"]) {\n width: 8px !important;\n background: transparent !important;\n position: relative;\n z-index: 3;\n transition: background 0.15s ease;\n}\n\n/* ── vertical 핸들: 8px 히트영역 ───────────────────────────────────── */\n:deep(.jsplitter-handle[data-orientation=\"vertical\"]) {\n height: 8px !important;\n background: transparent !important;\n position: relative;\n z-index: 3;\n transition: background 0.15s ease;\n}\n\n/* ── 시각선: 1px (after:w-1 override) ──────────────────────────────── */\n:deep(.jsplitter-handle::after) {\n width: 1px !important;\n background: hsl(var(--border)) !important;\n transition: background 0.15s ease, width 0.12s ease !important;\n}\n\n:deep(.jsplitter-handle[data-orientation=\"vertical\"]::after) {\n height: 1px !important;\n width: 100% !important;\n}\n\n/* ── 그립 도트 인디케이터 (::before) ───────────────────────────────── */\n:deep(.jsplitter-handle::before) {\n content: '';\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 3px;\n height: 20px;\n background-image: radial-gradient(circle, hsl(var(--muted-foreground) / 0.5) 1px, transparent 1px);\n background-size: 3px 6px;\n background-repeat: repeat-y;\n border-radius: 2px;\n opacity: 0.5;\n transition: opacity 0.15s ease;\n pointer-events: none;\n z-index: 4;\n}\n\n:deep(.jsplitter-handle[data-orientation=\"vertical\"]::before) {\n width: 20px;\n height: 3px;\n background-size: 6px 3px;\n background-repeat: repeat-x;\n background-image: radial-gradient(circle, hsl(var(--muted-foreground) / 0.5) 1px, transparent 1px);\n}\n\n/* ── hover ──────────────────────────────────────────────────────────── */\n:deep(.jsplitter-handle:hover) {\n background: hsl(var(--primary) / 0.04) !important;\n}\n\n:deep(.jsplitter-handle:hover::after) {\n width: 2px !important;\n background: hsl(var(--primary) / 0.55) !important;\n}\n\n:deep(.jsplitter-handle:hover::before) {\n opacity: 1;\n}\n\n/* ── drag / active ──────────────────────────────────────────────────── */\n:deep(.jsplitter-handle[data-state=\"drag\"]),\n:deep(.jsplitter-handle:active) {\n background: hsl(var(--primary) / 0.06) !important;\n}\n\n:deep(.jsplitter-handle[data-state=\"drag\"]::after),\n:deep(.jsplitter-handle:active::after) {\n width: 2px !important;\n background: hsl(var(--primary)) !important;\n}\n\n:deep(.jsplitter-handle[data-state=\"drag\"]::before),\n:deep(.jsplitter-handle:active::before) {\n opacity: 1;\n background-image: radial-gradient(circle, hsl(var(--primary) / 0.65) 1px, transparent 1px);\n}\n</style>\n"],"names":["props","__props","isMobile","useBreakpoint","effectiveDirection","computed","effectiveDefaultSize","secondDefaultSize","_createBlock","_unref","ResizablePanelGroup","_normalizeClass","cn","$props","_createVNode","ResizablePanel","_normalizeStyle","_renderSlot","_ctx","ResizableHandle"],"mappings":"wnBAYA,MAAMA,EAAQC,EAiCR,CAAE,SAAAC,CAAA,EAAaC,gBAAA,EAGfC,EAAqBC,EAAAA,SAAsB,IAC3CL,EAAM,YAAcE,EAAS,OAASF,EAAM,YAAc,aACrD,WAEFA,EAAM,SACd,EAGKM,EAAuBD,EAAAA,SAAS,IAChCL,EAAM,YAAcE,EAAS,OAASF,EAAM,YAAc,aACrD,GAEFA,EAAM,WACd,EAGKO,EAAoBF,EAAAA,SAAS,IAAM,IAAMC,EAAqB,KAAK,8BAIvEE,EAAAA,YA+BsBC,EAAAA,MAAAC,EAAAA,OAAA,EAAA,CA/BA,UAAWN,EAAA,MAAqB,MAAKO,EAAAA,eAAEF,EAAAA,MAAAG,EAAAA,EAAA,EAAE,kCAAoCC,EAAAA,OAAO,KAAK,CAAA,CAAA,qBAE7G,IAWiB,CAXjBC,cAWiBL,EAAAA,MAAAM,EAAAA,aAAA,EAAA,CAVd,eAAcT,EAAA,MACd,WAAUL,EAAA,QACV,WAAUA,EAAA,QACV,MAAKU,EAAAA,eAAEV,EAAA,IAAG,EAAQG,EAAA,QAAkB,aAAA,0BAAA,0BAAA,EAAA,EACpC,MAAKY,EAAAA,eAAEf,EAAA,IAAG,EAAA,CAAA,QAAA,GAAqBA,EAAA,GAAG,IAAA,EAAA,CAAA,CAAA,CAAA,qBAEnC,IAAqB,CAArBgB,EAAAA,WAAqBC,EAAA,OAAA,QAAA,CAAA,EAAA,OAAA,EAAA,EAErBD,EAAAA,WAAoBC,EAAA,OAAA,OAAA,CAAA,EAAA,OAAA,EAAA,EACpBD,EAAAA,WAAmBC,EAAA,OAAA,MAAA,CAAA,EAAA,OAAA,EAAA,CAAA,kEAIrBJ,cAAsEL,EAAAA,MAAAU,EAAAA,OAAA,EAAA,CAApD,cAAalB,EAAA,WAAY,MAAM,kBAAA,0BAGjDa,cAWiBL,EAAAA,MAAAM,EAAAA,aAAA,EAAA,CAVd,eAAcR,EAAA,MACd,WAAUN,EAAA,cACV,WAAUA,EAAA,cACV,MAAKU,EAAAA,eAAEV,EAAA,IAAG,EAAQG,EAAA,QAAkB,aAAA,0BAAA,0BAAA,EAAA,EACpC,MAAKY,EAAAA,eAAEf,EAAA,IAAG,EAAA,CAAA,QAAA,GAAqBA,EAAA,GAAG,IAAA,EAAA,CAAA,CAAA,CAAA,qBAEnC,IAAsB,CAAtBgB,EAAAA,WAAsBC,EAAA,OAAA,SAAA,CAAA,EAAA,OAAA,EAAA,EAEtBD,EAAAA,WAAqBC,EAAA,OAAA,QAAA,CAAA,EAAA,OAAA,EAAA,EACrBD,EAAAA,WAAsBC,EAAA,OAAA,SAAA,CAAA,EAAA,OAAA,EAAA,CAAA"}
@@ -1,9 +1,10 @@
1
- import { defineComponent as m, computed as u, createBlock as f, openBlock as z, unref as t, normalizeClass as l, withCtx as s, createVNode as n, normalizeStyle as o, renderSlot as i } from "vue";
1
+ import { defineComponent as p, computed as s, createBlock as v, openBlock as h, unref as l, normalizeClass as n, withCtx as r, createVNode as u, normalizeStyle as f, renderSlot as a } from "vue";
2
2
  import { cn as S } from "../../lib/utils.js";
3
3
  import g from "../shadcn/resizable/ResizableHandle.vue.js";
4
- import h from "../shadcn/resizable/ResizablePanelGroup.vue.js";
5
- import { SplitterPanel as d } from "reka-ui";
6
- const M = /* @__PURE__ */ m({
4
+ import $ from "../shadcn/resizable/ResizablePanelGroup.vue.js";
5
+ import { SplitterPanel as m } from "reka-ui";
6
+ import { useBreakpoint as y } from "../../composables/useBreakpoint.js";
7
+ const D = /* @__PURE__ */ p({
7
8
  __name: "JSplitter",
8
9
  props: {
9
10
  direction: { default: "horizontal" },
@@ -14,44 +15,45 @@ const M = /* @__PURE__ */ m({
14
15
  secondMaxSize: {},
15
16
  withHandle: { type: Boolean, default: !1 },
16
17
  gap: { default: 2 },
17
- class: {}
18
+ class: {},
19
+ responsive: { type: Boolean, default: !0 }
18
20
  },
19
21
  setup(e) {
20
- const c = e, r = u(() => 100 - c.defaultSize);
21
- return (a, p) => (z(), f(t(h), {
22
- direction: e.direction,
23
- class: l(t(S)("jsplitter-group min-h-0 min-w-0", a.$props.class))
22
+ const i = e, { isMobile: c } = y(), o = s(() => i.responsive && c.value && i.direction === "horizontal" ? "vertical" : i.direction), d = s(() => i.responsive && c.value && i.direction === "horizontal" ? 50 : i.defaultSize), z = s(() => 100 - d.value);
23
+ return (t, w) => (h(), v(l($), {
24
+ direction: o.value,
25
+ class: n(l(S)("jsplitter-group min-h-0 min-w-0", t.$props.class))
24
26
  }, {
25
- default: s(() => [
26
- n(t(d), {
27
- "default-size": e.defaultSize,
27
+ default: r(() => [
28
+ u(l(m), {
29
+ "default-size": d.value,
28
30
  "min-size": e.minSize,
29
31
  "max-size": e.maxSize,
30
- class: l(e.gap > 0 ? e.direction === "horizontal" ? "pr-[calc(var(--gap)/2)]" : "pb-[calc(var(--gap)/2)]" : ""),
31
- style: o(e.gap > 0 ? { "--gap": `${e.gap}px` } : {})
32
+ class: n(e.gap > 0 ? o.value === "horizontal" ? "pr-[calc(var(--gap)/2)]" : "pb-[calc(var(--gap)/2)]" : ""),
33
+ style: f(e.gap > 0 ? { "--gap": `${e.gap}px` } : {})
32
34
  }, {
33
- default: s(() => [
34
- i(a.$slots, "first", {}, void 0, !0),
35
- i(a.$slots, "left", {}, void 0, !0),
36
- i(a.$slots, "top", {}, void 0, !0)
35
+ default: r(() => [
36
+ a(t.$slots, "first", {}, void 0, !0),
37
+ a(t.$slots, "left", {}, void 0, !0),
38
+ a(t.$slots, "top", {}, void 0, !0)
37
39
  ]),
38
40
  _: 3
39
41
  }, 8, ["default-size", "min-size", "max-size", "class", "style"]),
40
- n(t(g), {
42
+ u(l(g), {
41
43
  "with-handle": e.withHandle,
42
44
  class: "jsplitter-handle"
43
45
  }, null, 8, ["with-handle"]),
44
- n(t(d), {
45
- "default-size": r.value,
46
+ u(l(m), {
47
+ "default-size": z.value,
46
48
  "min-size": e.secondMinSize,
47
49
  "max-size": e.secondMaxSize,
48
- class: l(e.gap > 0 ? e.direction === "horizontal" ? "pl-[calc(var(--gap)/2)]" : "pt-[calc(var(--gap)/2)]" : ""),
49
- style: o(e.gap > 0 ? { "--gap": `${e.gap}px` } : {})
50
+ class: n(e.gap > 0 ? o.value === "horizontal" ? "pl-[calc(var(--gap)/2)]" : "pt-[calc(var(--gap)/2)]" : ""),
51
+ style: f(e.gap > 0 ? { "--gap": `${e.gap}px` } : {})
50
52
  }, {
51
- default: s(() => [
52
- i(a.$slots, "second", {}, void 0, !0),
53
- i(a.$slots, "right", {}, void 0, !0),
54
- i(a.$slots, "bottom", {}, void 0, !0)
53
+ default: r(() => [
54
+ a(t.$slots, "second", {}, void 0, !0),
55
+ a(t.$slots, "right", {}, void 0, !0),
56
+ a(t.$slots, "bottom", {}, void 0, !0)
55
57
  ]),
56
58
  _: 3
57
59
  }, 8, ["default-size", "min-size", "max-size", "class", "style"])
@@ -61,6 +63,6 @@ const M = /* @__PURE__ */ m({
61
63
  }
62
64
  });
63
65
  export {
64
- M as default
66
+ D as default
65
67
  };
66
68
  //# sourceMappingURL=JSplitter.vue2.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"JSplitter.vue2.js","sources":["../../../../src/components/atoms/JSplitter.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { cn } from '@/lib/utils'\nimport {\n ResizablePanelGroup,\n ResizablePanel,\n ResizableHandle,\n} from '@/components/shadcn/resizable'\n\ntype Orientation = 'horizontal' | 'vertical'\n\nconst props = withDefaults(\n defineProps<{\n /** 분할 방향 (horizontal: 좌우, vertical: 상하) */\n direction?: Orientation\n /** 첫 번째 패널의 기본 크기 (%) */\n defaultSize?: number\n /** 첫 번째 패널의 최소 크기 (%) */\n minSize?: number\n /** 첫 번째 패널의 최대 크기 (%) */\n maxSize?: number\n /** 두 번째 패널의 최소 크기 (%) */\n secondMinSize?: number\n /** 두 번째 패널의 최대 크기 (%) */\n secondMaxSize?: number\n /** ResizableHandle에 grip 아이콘 표시 여부 */\n withHandle?: boolean\n /** 패널 간 여백 (px) */\n gap?: number\n /** 추가 CSS 클래스 */\n class?: string\n }>(),\n {\n direction: 'horizontal',\n defaultSize: 40,\n minSize: 20,\n withHandle: false,\n gap: 2,\n },\n)\n\n// 두 번째 패널의 기본 크기 계산\nconst secondDefaultSize = computed(() => 100 - props.defaultSize)\n</script>\n\n<template>\n <ResizablePanelGroup :direction=\"direction\" :class=\"cn('jsplitter-group min-h-0 min-w-0', $props.class)\">\n <!-- 첫 번째 패널 (좌측/상단) -->\n <ResizablePanel \n :default-size=\"defaultSize\" \n :min-size=\"minSize\" \n :max-size=\"maxSize\"\n :class=\"gap > 0 ? (direction === 'horizontal' ? 'pr-[calc(var(--gap)/2)]' : 'pb-[calc(var(--gap)/2)]') : ''\"\n :style=\"gap > 0 ? { '--gap': `${gap}px` } : {}\"\n >\n <slot name=\"first\" />\n <!-- direction=\"horizontal\"일 때 left, vertical일 때 top으로도 사용 가능 -->\n <slot name=\"left\" />\n <slot name=\"top\" />\n </ResizablePanel>\n\n <!-- 크기 조정 핸들 -->\n <ResizableHandle :with-handle=\"withHandle\" class=\"jsplitter-handle\" />\n\n <!-- 두 번째 패널 (우측/하단) -->\n <ResizablePanel\n :default-size=\"secondDefaultSize\"\n :min-size=\"secondMinSize\"\n :max-size=\"secondMaxSize\"\n :class=\"gap > 0 ? (direction === 'horizontal' ? 'pl-[calc(var(--gap)/2)]' : 'pt-[calc(var(--gap)/2)]') : ''\"\n :style=\"gap > 0 ? { '--gap': `${gap}px` } : {}\"\n >\n <slot name=\"second\" />\n <!-- direction=\"horizontal\"일 때 right, vertical일 때 bottom으로도 사용 가능 -->\n <slot name=\"right\" />\n <slot name=\"bottom\" />\n </ResizablePanel>\n </ResizablePanelGroup>\n</template>\n\n<style scoped>\n/* ── overflow 보호 ──────────────────────────────────────────────────── */\n:deep(.jsplitter-group [data-panel]) {\n min-width: 0;\n min-height: 0;\n}\n\n/* ── horizontal 핸들: 8px 히트영역 ─────────────────────────────────── */\n:deep(.jsplitter-handle[data-orientation=\"horizontal\"]) {\n width: 8px !important;\n background: transparent !important;\n position: relative;\n z-index: 3;\n transition: background 0.15s ease;\n}\n\n/* ── vertical 핸들: 8px 히트영역 ───────────────────────────────────── */\n:deep(.jsplitter-handle[data-orientation=\"vertical\"]) {\n height: 8px !important;\n background: transparent !important;\n position: relative;\n z-index: 3;\n transition: background 0.15s ease;\n}\n\n/* ── 시각선: 1px (after:w-1 override) ──────────────────────────────── */\n:deep(.jsplitter-handle::after) {\n width: 1px !important;\n background: hsl(var(--border)) !important;\n transition: background 0.15s ease, width 0.12s ease !important;\n}\n\n:deep(.jsplitter-handle[data-orientation=\"vertical\"]::after) {\n height: 1px !important;\n width: 100% !important;\n}\n\n/* ── 그립 도트 인디케이터 (::before) ───────────────────────────────── */\n:deep(.jsplitter-handle::before) {\n content: '';\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 3px;\n height: 20px;\n background-image: radial-gradient(circle, hsl(var(--muted-foreground) / 0.5) 1px, transparent 1px);\n background-size: 3px 6px;\n background-repeat: repeat-y;\n border-radius: 2px;\n opacity: 0.5;\n transition: opacity 0.15s ease;\n pointer-events: none;\n z-index: 4;\n}\n\n:deep(.jsplitter-handle[data-orientation=\"vertical\"]::before) {\n width: 20px;\n height: 3px;\n background-size: 6px 3px;\n background-repeat: repeat-x;\n background-image: radial-gradient(circle, hsl(var(--muted-foreground) / 0.5) 1px, transparent 1px);\n}\n\n/* ── hover ──────────────────────────────────────────────────────────── */\n:deep(.jsplitter-handle:hover) {\n background: hsl(var(--primary) / 0.04) !important;\n}\n\n:deep(.jsplitter-handle:hover::after) {\n width: 2px !important;\n background: hsl(var(--primary) / 0.55) !important;\n}\n\n:deep(.jsplitter-handle:hover::before) {\n opacity: 1;\n}\n\n/* ── drag / active ──────────────────────────────────────────────────── */\n:deep(.jsplitter-handle[data-state=\"drag\"]),\n:deep(.jsplitter-handle:active) {\n background: hsl(var(--primary) / 0.06) !important;\n}\n\n:deep(.jsplitter-handle[data-state=\"drag\"]::after),\n:deep(.jsplitter-handle:active::after) {\n width: 2px !important;\n background: hsl(var(--primary)) !important;\n}\n\n:deep(.jsplitter-handle[data-state=\"drag\"]::before),\n:deep(.jsplitter-handle:active::before) {\n opacity: 1;\n background-image: radial-gradient(circle, hsl(var(--primary) / 0.65) 1px, transparent 1px);\n}\n</style>\n"],"names":["props","__props","secondDefaultSize","computed","_createBlock","_unref","ResizablePanelGroup","_normalizeClass","cn","$props","_createVNode","ResizablePanel","_normalizeStyle","_renderSlot","_ctx","ResizableHandle"],"mappings":";;;;;;;;;;;;;;;;;;;AAWA,UAAMA,IAAQC,GA+BRC,IAAoBC,EAAS,MAAM,MAAMH,EAAM,WAAW;2BAI9DI,EA+BsBC,EAAAC,CAAA,GAAA;AAAA,MA/BA,WAAWL,EAAA;AAAA,MAAY,OAAKM,EAAEF,EAAAG,CAAA,EAAE,mCAAoCC,EAAAA,OAAO,KAAK,CAAA;AAAA,IAAA;iBAEpG,MAWiB;AAAA,QAXjBC,EAWiBL,EAAAM,CAAA,GAAA;AAAA,UAVd,gBAAcV,EAAA;AAAA,UACd,YAAUA,EAAA;AAAA,UACV,YAAUA,EAAA;AAAA,UACV,OAAKM,EAAEN,EAAA,MAAG,IAAQA,EAAA,cAAS,eAAA,4BAAA,4BAAA,EAAA;AAAA,UAC3B,OAAKW,EAAEX,EAAA,MAAG,IAAA,EAAA,SAAA,GAAqBA,EAAA,GAAG,KAAA,IAAA,CAAA,CAAA;AAAA,QAAA;qBAEnC,MAAqB;AAAA,YAArBY,EAAqBC,EAAA,QAAA,SAAA,CAAA,GAAA,QAAA,EAAA;AAAA,YAErBD,EAAoBC,EAAA,QAAA,QAAA,CAAA,GAAA,QAAA,EAAA;AAAA,YACpBD,EAAmBC,EAAA,QAAA,OAAA,CAAA,GAAA,QAAA,EAAA;AAAA,UAAA;;;QAIrBJ,EAAsEL,EAAAU,CAAA,GAAA;AAAA,UAApD,eAAad,EAAA;AAAA,UAAY,OAAM;AAAA,QAAA;QAGjDS,EAWiBL,EAAAM,CAAA,GAAA;AAAA,UAVd,gBAAcT,EAAA;AAAA,UACd,YAAUD,EAAA;AAAA,UACV,YAAUA,EAAA;AAAA,UACV,OAAKM,EAAEN,EAAA,MAAG,IAAQA,EAAA,cAAS,eAAA,4BAAA,4BAAA,EAAA;AAAA,UAC3B,OAAKW,EAAEX,EAAA,MAAG,IAAA,EAAA,SAAA,GAAqBA,EAAA,GAAG,KAAA,IAAA,CAAA,CAAA;AAAA,QAAA;qBAEnC,MAAsB;AAAA,YAAtBY,EAAsBC,EAAA,QAAA,UAAA,CAAA,GAAA,QAAA,EAAA;AAAA,YAEtBD,EAAqBC,EAAA,QAAA,SAAA,CAAA,GAAA,QAAA,EAAA;AAAA,YACrBD,EAAsBC,EAAA,QAAA,UAAA,CAAA,GAAA,QAAA,EAAA;AAAA,UAAA;;;;;;;;"}
1
+ {"version":3,"file":"JSplitter.vue2.js","sources":["../../../../src/components/atoms/JSplitter.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { cn } from '@/lib/utils'\nimport {\n ResizablePanelGroup,\n ResizablePanel,\n ResizableHandle,\n} from '@/components/shadcn/resizable'\nimport { useBreakpoint } from '@/composables/useBreakpoint'\n\ntype Orientation = 'horizontal' | 'vertical'\n\nconst props = withDefaults(\n defineProps<{\n /** 분할 방향 (horizontal: 좌우, vertical: 상하) */\n direction?: Orientation\n /** 첫 번째 패널의 기본 크기 (%) */\n defaultSize?: number\n /** 첫 번째 패널의 최소 크기 (%) */\n minSize?: number\n /** 첫 번째 패널의 최대 크기 (%) */\n maxSize?: number\n /** 두 번째 패널의 최소 크기 (%) */\n secondMinSize?: number\n /** 두 번째 패널의 최대 크기 (%) */\n secondMaxSize?: number\n /** ResizableHandle에 grip 아이콘 표시 여부 */\n withHandle?: boolean\n /** 패널 간 여백 (px) */\n gap?: number\n /** 추가 CSS 클래스 */\n class?: string\n /** 모바일에서 자동 수직 전환 (기본: true) */\n responsive?: boolean\n }>(),\n {\n direction: 'horizontal',\n defaultSize: 40,\n minSize: 20,\n withHandle: false,\n gap: 2,\n responsive: true,\n },\n)\n\nconst { isMobile } = useBreakpoint()\n\n// 모바일에서 horizontal → vertical 자동 전환\nconst effectiveDirection = computed<Orientation>(() => {\n if (props.responsive && isMobile.value && props.direction === 'horizontal') {\n return 'vertical'\n }\n return props.direction\n})\n\n// 모바일 수직 전환 시 50:50 균등 분할\nconst effectiveDefaultSize = computed(() => {\n if (props.responsive && isMobile.value && props.direction === 'horizontal') {\n return 50\n }\n return props.defaultSize\n})\n\n// 두 번째 패널의 기본 크기 계산\nconst secondDefaultSize = computed(() => 100 - effectiveDefaultSize.value)\n</script>\n\n<template>\n <ResizablePanelGroup :direction=\"effectiveDirection\" :class=\"cn('jsplitter-group min-h-0 min-w-0', $props.class)\">\n <!-- 첫 번째 패널 (좌측/상단) -->\n <ResizablePanel \n :default-size=\"effectiveDefaultSize\" \n :min-size=\"minSize\" \n :max-size=\"maxSize\"\n :class=\"gap > 0 ? (effectiveDirection === 'horizontal' ? 'pr-[calc(var(--gap)/2)]' : 'pb-[calc(var(--gap)/2)]') : ''\"\n :style=\"gap > 0 ? { '--gap': `${gap}px` } : {}\"\n >\n <slot name=\"first\" />\n <!-- direction=\"horizontal\"일 때 left, vertical일 때 top으로도 사용 가능 -->\n <slot name=\"left\" />\n <slot name=\"top\" />\n </ResizablePanel>\n\n <!-- 크기 조정 핸들 -->\n <ResizableHandle :with-handle=\"withHandle\" class=\"jsplitter-handle\" />\n\n <!-- 두 번째 패널 (우측/하단) -->\n <ResizablePanel\n :default-size=\"secondDefaultSize\"\n :min-size=\"secondMinSize\"\n :max-size=\"secondMaxSize\"\n :class=\"gap > 0 ? (effectiveDirection === 'horizontal' ? 'pl-[calc(var(--gap)/2)]' : 'pt-[calc(var(--gap)/2)]') : ''\"\n :style=\"gap > 0 ? { '--gap': `${gap}px` } : {}\"\n >\n <slot name=\"second\" />\n <!-- direction=\"horizontal\"일 때 right, vertical일 때 bottom으로도 사용 가능 -->\n <slot name=\"right\" />\n <slot name=\"bottom\" />\n </ResizablePanel>\n </ResizablePanelGroup>\n</template>\n\n<style scoped>\n/* ── overflow 보호 ──────────────────────────────────────────────────── */\n:deep(.jsplitter-group [data-panel]) {\n min-width: 0;\n min-height: 0;\n}\n\n/* ── horizontal 핸들: 8px 히트영역 ─────────────────────────────────── */\n:deep(.jsplitter-handle[data-orientation=\"horizontal\"]) {\n width: 8px !important;\n background: transparent !important;\n position: relative;\n z-index: 3;\n transition: background 0.15s ease;\n}\n\n/* ── vertical 핸들: 8px 히트영역 ───────────────────────────────────── */\n:deep(.jsplitter-handle[data-orientation=\"vertical\"]) {\n height: 8px !important;\n background: transparent !important;\n position: relative;\n z-index: 3;\n transition: background 0.15s ease;\n}\n\n/* ── 시각선: 1px (after:w-1 override) ──────────────────────────────── */\n:deep(.jsplitter-handle::after) {\n width: 1px !important;\n background: hsl(var(--border)) !important;\n transition: background 0.15s ease, width 0.12s ease !important;\n}\n\n:deep(.jsplitter-handle[data-orientation=\"vertical\"]::after) {\n height: 1px !important;\n width: 100% !important;\n}\n\n/* ── 그립 도트 인디케이터 (::before) ───────────────────────────────── */\n:deep(.jsplitter-handle::before) {\n content: '';\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 3px;\n height: 20px;\n background-image: radial-gradient(circle, hsl(var(--muted-foreground) / 0.5) 1px, transparent 1px);\n background-size: 3px 6px;\n background-repeat: repeat-y;\n border-radius: 2px;\n opacity: 0.5;\n transition: opacity 0.15s ease;\n pointer-events: none;\n z-index: 4;\n}\n\n:deep(.jsplitter-handle[data-orientation=\"vertical\"]::before) {\n width: 20px;\n height: 3px;\n background-size: 6px 3px;\n background-repeat: repeat-x;\n background-image: radial-gradient(circle, hsl(var(--muted-foreground) / 0.5) 1px, transparent 1px);\n}\n\n/* ── hover ──────────────────────────────────────────────────────────── */\n:deep(.jsplitter-handle:hover) {\n background: hsl(var(--primary) / 0.04) !important;\n}\n\n:deep(.jsplitter-handle:hover::after) {\n width: 2px !important;\n background: hsl(var(--primary) / 0.55) !important;\n}\n\n:deep(.jsplitter-handle:hover::before) {\n opacity: 1;\n}\n\n/* ── drag / active ──────────────────────────────────────────────────── */\n:deep(.jsplitter-handle[data-state=\"drag\"]),\n:deep(.jsplitter-handle:active) {\n background: hsl(var(--primary) / 0.06) !important;\n}\n\n:deep(.jsplitter-handle[data-state=\"drag\"]::after),\n:deep(.jsplitter-handle:active::after) {\n width: 2px !important;\n background: hsl(var(--primary)) !important;\n}\n\n:deep(.jsplitter-handle[data-state=\"drag\"]::before),\n:deep(.jsplitter-handle:active::before) {\n opacity: 1;\n background-image: radial-gradient(circle, hsl(var(--primary) / 0.65) 1px, transparent 1px);\n}\n</style>\n"],"names":["props","__props","isMobile","useBreakpoint","effectiveDirection","computed","effectiveDefaultSize","secondDefaultSize","_createBlock","_unref","ResizablePanelGroup","_normalizeClass","cn","$props","_createVNode","ResizablePanel","_normalizeStyle","_renderSlot","_ctx","ResizableHandle"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAYA,UAAMA,IAAQC,GAiCR,EAAE,UAAAC,EAAA,IAAaC,EAAA,GAGfC,IAAqBC,EAAsB,MAC3CL,EAAM,cAAcE,EAAS,SAASF,EAAM,cAAc,eACrD,aAEFA,EAAM,SACd,GAGKM,IAAuBD,EAAS,MAChCL,EAAM,cAAcE,EAAS,SAASF,EAAM,cAAc,eACrD,KAEFA,EAAM,WACd,GAGKO,IAAoBF,EAAS,MAAM,MAAMC,EAAqB,KAAK;2BAIvEE,EA+BsBC,EAAAC,CAAA,GAAA;AAAA,MA/BA,WAAWN,EAAA;AAAA,MAAqB,OAAKO,EAAEF,EAAAG,CAAA,EAAE,mCAAoCC,EAAAA,OAAO,KAAK,CAAA;AAAA,IAAA;iBAE7G,MAWiB;AAAA,QAXjBC,EAWiBL,EAAAM,CAAA,GAAA;AAAA,UAVd,gBAAcT,EAAA;AAAA,UACd,YAAUL,EAAA;AAAA,UACV,YAAUA,EAAA;AAAA,UACV,OAAKU,EAAEV,EAAA,MAAG,IAAQG,EAAA,UAAkB,eAAA,4BAAA,4BAAA,EAAA;AAAA,UACpC,OAAKY,EAAEf,EAAA,MAAG,IAAA,EAAA,SAAA,GAAqBA,EAAA,GAAG,KAAA,IAAA,CAAA,CAAA;AAAA,QAAA;qBAEnC,MAAqB;AAAA,YAArBgB,EAAqBC,EAAA,QAAA,SAAA,CAAA,GAAA,QAAA,EAAA;AAAA,YAErBD,EAAoBC,EAAA,QAAA,QAAA,CAAA,GAAA,QAAA,EAAA;AAAA,YACpBD,EAAmBC,EAAA,QAAA,OAAA,CAAA,GAAA,QAAA,EAAA;AAAA,UAAA;;;QAIrBJ,EAAsEL,EAAAU,CAAA,GAAA;AAAA,UAApD,eAAalB,EAAA;AAAA,UAAY,OAAM;AAAA,QAAA;QAGjDa,EAWiBL,EAAAM,CAAA,GAAA;AAAA,UAVd,gBAAcR,EAAA;AAAA,UACd,YAAUN,EAAA;AAAA,UACV,YAAUA,EAAA;AAAA,UACV,OAAKU,EAAEV,EAAA,MAAG,IAAQG,EAAA,UAAkB,eAAA,4BAAA,4BAAA,EAAA;AAAA,UACpC,OAAKY,EAAEf,EAAA,MAAG,IAAA,EAAA,SAAA,GAAqBA,EAAA,GAAG,KAAA,IAAA,CAAA,CAAA;AAAA,QAAA;qBAEnC,MAAsB;AAAA,YAAtBgB,EAAsBC,EAAA,QAAA,UAAA,CAAA,GAAA,QAAA,EAAA;AAAA,YAEtBD,EAAqBC,EAAA,QAAA,SAAA,CAAA,GAAA,QAAA,EAAA;AAAA,YACrBD,EAAsBC,EAAA,QAAA,UAAA,CAAA,GAAA,QAAA,EAAA;AAAA,UAAA;;;;;;;;"}
@@ -3,5 +3,5 @@
3
3
  for (const [t_key, t_val] of t_opts)
4
4
  t_merged[t_key] = t_val;
5
5
  return t_merged;
6
- };,u=t(e.default,[["__scopeId","data-v-df250d3d"]]);exports.default=u;
6
+ };,u=t(e.default,[["__scopeId","data-v-37f35843"]]);exports.default=u;
7
7
  //# sourceMappingURL=JFormField.vue.cjs.map
@@ -6,8 +6,8 @@ const r = (r_comp, r_opts) => {
6
6
  r_merged[r_key] = r_val;
7
7
  return r_merged;
8
8
  };
9
- const f = /* @__PURE__ */ r(o, [["__scopeId", "data-v-df250d3d"]]);
9
+ const p = /* @__PURE__ */ r(o, [["__scopeId", "data-v-37f35843"]]);
10
10
  export {
11
- f as default
11
+ p as default
12
12
  };
13
13
  //# sourceMappingURL=JFormField.vue.js.map
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue");require("../shadcn/index.cjs");require("lucide-vue-next");;/* empty css */const g=require("../atoms/JInput.vue.cjs"),N=require("../atoms/JTextarea.vue.cjs"),T=require("../atoms/JCheckbox.vue.cjs"),H=require("../atoms/JCombo.vue.cjs"),S=require("../atoms/JSearchCombo.vue.cjs"),J=require("../atoms/JRadio.vue.cjs"),E=require("../atoms/JSwitch.vue.cjs"),M=require("../atoms/JDatepicker.vue.cjs"),P=require("../atoms/JEditor.vue.cjs"),L=require("../../lib/utils.cjs");require("../shadcn/badge-variants.cjs");require("@vueuse/core");require("reka-ui");;/* empty css */require("../shadcn/avatar-variants.cjs");require("md-editor-v3");;/* empty css */require("dompurify");;/* empty css */require("ag-grid-vue3");require("ag-grid-community");require("ag-grid-enterprise");;/* empty css */;/* empty css */;/* empty css */require("vue-sonner");const c=require("../shadcn/FieldGroup.vue.cjs"),x=require("../shadcn/Field.vue.cjs"),k=require("../shadcn/FieldLabel.vue.cjs"),C=require("../shadcn/FieldContent.vue.cjs"),j=require("../shadcn/FieldDescription.vue.cjs"),A=require("../shadcn/FieldError.vue.cjs"),O={key:0,class:"text-destructive ml-0.5"},U=e.defineComponent({__name:"JFormField",props:{class:{},label:{},description:{},errorMsg:{},type:{default:"input"},inlineLabel:{},orientation:{default:"horizontal"},labelAlign:{default:"left"},labelWidth:{default:"80px"},id:{},modelValue:{},placeholder:{},disabled:{type:Boolean},readonly:{type:Boolean},required:{type:Boolean},name:{},styleType:{},inputType:{},options:{},multiple:{type:Boolean},radioDirection:{default:"horizontal"},editorHeight:{},fillHeight:{type:Boolean}},emits:["update:modelValue","change","focus","blur","save"],setup(r,{expose:q,emit:b}){const B=["class","label","description","errorMsg","type","inlineLabel","orientation","labelAlign","labelWidth","radioDirection","editorHeight","fillHeight"],t=r,o=b,i=e.ref(""),s=e.computed(()=>t.errorMsg||i.value),d=e.computed(()=>{const l={},a=t;for(const u in a)B.includes(u)||(l[u]=a[u]);if(t.inputType&&t.type==="input"&&(l.type=t.inputType,delete l.inputType,!t.placeholder)){const u={text:"텍스트를 입력하세요",email:"이메일을 입력하세요",password:"비밀번호를 입력하세요",tel:"전화번호를 입력하세요",url:"URL을 입력하세요",number:"숫자를 입력하세요",search:"검색어를 입력하세요",date:"날짜를 선택하세요",time:"시간을 선택하세요","datetime-local":"날짜와 시간을 선택하세요",month:"월을 선택하세요",week:"주를 선택하세요"};l.placeholder=u[t.inputType]||"입력하세요"}return t.radioDirection&&t.type==="radio"&&(l.styletype=t.radioDirection,delete l.radioDirection),t.type==="editor"&&(l.height=t.fillHeight?"100%":t.editorHeight??"300px",delete l.editorHeight,delete l.fillHeight),l}),f=l=>{if(!t.required)return;i.value="";const a=l!==void 0?l:t.modelValue;t.type==="checkbox"||t.type==="switch"?a!=="Y"&&(i.value="필수 항목입니다."):(a==null||a==="")&&(i.value="필수 입력 항목입니다.")},w=e.computed(()=>!(t.modelValue!==null&&t.modelValue!==void 0&&t.modelValue!=="")),p=l=>{o("update:modelValue",l),f(l)},m=l=>{o("change",l),f(l)},V=l=>{o("save",l)},h=l=>{o("focus",l)},y=l=>{w.value&&f(),o("blur",l)},z=e.computed(()=>{const l={left:"justify-start",middle:"justify-center",right:"justify-end"},a={left:"text-left",middle:"text-center",right:"text-right"};return t.orientation==="horizontal"?l[t.labelAlign]:a[t.labelAlign]}),D=e.computed(()=>{switch(t.styleType){case"md":return"form-density-md";case"lg":return"form-density-lg";default:return"form-density-sm"}}),v=e.computed(()=>{const l="h-[var(--ctl-h)] leading-[var(--ctl-h)]";return t.type==="datepicker"?`${l} max-w-xs`:t.type==="editor"?t.fillHeight?"h-full w-full":"":t.type==="radio"&&t.radioDirection==="vertical"?"":l}),F={input:g.default,textarea:N.default,checkbox:T.default,switch:E.default,combo:H.default,radio:J.default,searchCombo:S.default,datepicker:M.default,editor:P.default},_=e.computed(()=>F[t.type]||g.default),n=e.computed(()=>t.fillHeight&&t.type==="editor");return q({clearError:()=>{i.value=""}}),(l,a)=>(e.openBlock(),e.createElementBlock("div",{class:e.normalizeClass(e.unref(L.cn)(n.value?"flex-1 flex flex-col min-h-0":r.type==="editor"?"flex-1 min-w-0":"space-y-2 flex-1 min-w-0",D.value,t.class))},[e.createVNode(e.unref(c.default),{class:e.normalizeClass(n.value?"flex-1 flex flex-col min-h-0":"")},{default:e.withCtx(()=>[e.createVNode(e.unref(x.default),{class:e.normalizeClass([r.orientation==="horizontal"?"grid grid-cols-[var(--label-w,8rem)_1fr] items-start space-y-0 gap-2":r.type==="editor"?"space-y-0 gap-0":"space-y-1 gap-1",n.value?"flex-1 min-h-0":""]),style:e.normalizeStyle(r.orientation==="horizontal"&&r.labelWidth?`--label-w:${r.labelWidth};`:"")},{default:e.withCtx(()=>[e.createVNode(e.unref(k.default),{for:r.id,class:e.normalizeClass(["text-xs font-medium",r.orientation==="horizontal"?r.type==="editor"?"flex items-start pt-1 w-full":"flex items-center h-[var(--ctl-h)] w-full":"flex items-center",z.value])},{default:e.withCtx(()=>[e.createTextVNode(e.toDisplayString(r.label)+" ",1),r.required?(e.openBlock(),e.createElementBlock("span",O,"*")):e.createCommentVNode("",!0)]),_:1},8,["for","class"]),e.createVNode(e.unref(C.default),{class:e.normalizeClass([r.orientation==="horizontal"?"min-h-[var(--ctl-h)] flex flex-col justify-start gap-0.5 mt-0":"space-y-2 gap-0",n.value?"flex-1 flex flex-col min-h-0":""])},{default:e.withCtx(()=>[r.type==="checkbox"||r.type==="switch"?(e.openBlock(),e.createBlock(e.unref(c.default),{key:0,"data-slot":"checkbox-group"},{default:e.withCtx(()=>[e.createVNode(e.unref(x.default),{orientation:"horizontal",class:"flex gap-2 space-y-0 h-[var(--ctl-h)] leading-[var(--ctl-h)] items-center"},{default:e.withCtx(()=>[(e.openBlock(),e.createBlock(e.resolveDynamicComponent(_.value),e.mergeProps(d.value,{"onUpdate:modelValue":p,onChange:m,onFocus:h,onBlur:y}),null,16)),r.inlineLabel?(e.openBlock(),e.createBlock(e.unref(k.default),{key:0,for:r.id,class:"text-xs font-normal m-0 h-[var(--ctl-h)] leading-[var(--ctl-h)]"},{default:e.withCtx(()=>[e.createTextVNode(e.toDisplayString(r.inlineLabel),1)]),_:1},8,["for"])):e.createCommentVNode("",!0)]),_:1})]),_:1})):r.type==="radio"?(e.openBlock(),e.createBlock(e.unref(c.default),{key:1},{default:e.withCtx(()=>[(e.openBlock(),e.createBlock(e.resolveDynamicComponent(_.value),e.mergeProps(d.value,{"onUpdate:modelValue":p,onChange:m,onFocus:h,onBlur:y,class:v.value}),null,16,["class"]))]),_:1})):(e.openBlock(),e.createBlock(e.unref(c.default),{key:2,class:e.normalizeClass(n.value?"flex-1 flex flex-col min-h-0":"")},{default:e.withCtx(()=>[(e.openBlock(),e.createBlock(e.resolveDynamicComponent(_.value),e.mergeProps(d.value,{"onUpdate:modelValue":p,onChange:m,onFocus:h,onBlur:y,onSave:V,class:v.value}),null,16,["class"]))]),_:1},8,["class"])),r.description||s.value?(e.openBlock(),e.createBlock(e.unref(C.default),{key:3},{default:e.withCtx(()=>[r.description?(e.openBlock(),e.createBlock(e.unref(j.default),{key:0},{default:e.withCtx(()=>[e.createTextVNode(e.toDisplayString(r.description),1)]),_:1})):e.createCommentVNode("",!0),s.value?(e.openBlock(),e.createBlock(e.unref(A.default),{key:1},{default:e.withCtx(()=>[e.createTextVNode(e.toDisplayString(s.value),1)]),_:1})):e.createCommentVNode("",!0)]),_:1})):e.createCommentVNode("",!0)]),_:1},8,["class"])]),_:1},8,["class","style"])]),_:1},8,["class"])],2))}});exports.default=U;
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue");require("../shadcn/index.cjs");require("lucide-vue-next");;/* empty css */const x=require("../atoms/JInput.vue.cjs"),H=require("../atoms/JTextarea.vue.cjs"),S=require("../atoms/JCheckbox.vue.cjs"),J=require("../atoms/JCombo.vue.cjs"),E=require("../atoms/JSearchCombo.vue.cjs"),M=require("../atoms/JRadio.vue.cjs"),P=require("../atoms/JSwitch.vue.cjs"),L=require("../atoms/JDatepicker.vue.cjs"),j=require("../atoms/JEditor.vue.cjs"),O=require("../../lib/utils.cjs");require("../shadcn/badge-variants.cjs");require("@vueuse/core");require("reka-ui");;/* empty css */require("../shadcn/avatar-variants.cjs");require("md-editor-v3");;/* empty css */require("dompurify");;/* empty css */require("ag-grid-vue3");require("ag-grid-community");require("ag-grid-enterprise");;/* empty css */const A=require("../../composables/useBreakpoint.cjs");;/* empty css */;/* empty css */require("vue-sonner");const s=require("../shadcn/FieldGroup.vue.cjs"),k=require("../shadcn/Field.vue.cjs"),C=require("../shadcn/FieldLabel.vue.cjs"),q=require("../shadcn/FieldContent.vue.cjs"),U=require("../shadcn/FieldDescription.vue.cjs"),R=require("../shadcn/FieldError.vue.cjs"),W={key:0,class:"text-destructive ml-0.5"},I=e.defineComponent({__name:"JFormField",props:{class:{},label:{},description:{},errorMsg:{},type:{default:"input"},inlineLabel:{},orientation:{default:"horizontal"},labelAlign:{default:"left"},labelWidth:{default:"80px"},id:{},modelValue:{},placeholder:{},disabled:{type:Boolean},readonly:{type:Boolean},required:{type:Boolean},name:{},styleType:{},inputType:{},options:{},multiple:{type:Boolean},radioDirection:{default:"horizontal"},editorHeight:{},fillHeight:{type:Boolean}},emits:["update:modelValue","change","focus","blur","save"],setup(r,{expose:b,emit:B}){const w=["class","label","description","errorMsg","type","inlineLabel","orientation","labelAlign","labelWidth","radioDirection","editorHeight","fillHeight"],t=r,o=B,i=e.ref(""),d=e.computed(()=>t.errorMsg||i.value),p=e.computed(()=>{const l={},a=t;for(const c in a)w.includes(c)||(l[c]=a[c]);if(t.inputType&&t.type==="input"&&(l.type=t.inputType,delete l.inputType,!t.placeholder)){const c={text:"텍스트를 입력하세요",email:"이메일을 입력하세요",password:"비밀번호를 입력하세요",tel:"전화번호를 입력하세요",url:"URL을 입력하세요",number:"숫자를 입력하세요",search:"검색어를 입력하세요",date:"날짜를 선택하세요",time:"시간을 선택하세요","datetime-local":"날짜와 시간을 선택하세요",month:"월을 선택하세요",week:"주를 선택하세요"};l.placeholder=c[t.inputType]||"입력하세요"}return t.radioDirection&&t.type==="radio"&&(l.styletype=t.radioDirection,delete l.radioDirection),t.type==="editor"&&(l.height=t.fillHeight?"100%":t.editorHeight??"300px",delete l.editorHeight,delete l.fillHeight),l}),f=l=>{if(!t.required)return;i.value="";const a=l!==void 0?l:t.modelValue;t.type==="checkbox"||t.type==="switch"?a!=="Y"&&(i.value="필수 항목입니다."):(a==null||a==="")&&(i.value="필수 입력 항목입니다.")},V=e.computed(()=>!(t.modelValue!==null&&t.modelValue!==void 0&&t.modelValue!=="")),m=l=>{o("update:modelValue",l),f(l)},h=l=>{o("change",l),f(l)},z=l=>{o("save",l)},_=l=>{o("focus",l)},v=l=>{V.value&&f(),o("blur",l)},D=e.computed(()=>{const l={left:"justify-start",middle:"justify-center",right:"justify-end"},a={left:"text-left",middle:"text-center",right:"text-right"};return n.value==="horizontal"?l[t.labelAlign]:a[t.labelAlign]}),F=e.computed(()=>{switch(t.styleType){case"md":return"form-density-md";case"lg":return"form-density-lg";default:return"form-density-sm"}}),g=e.computed(()=>{const l="h-[var(--ctl-h)] leading-[var(--ctl-h)]";return t.type==="datepicker"?`${l} max-w-xs`:t.type==="editor"?t.fillHeight?"h-full w-full":"":t.type==="radio"&&t.radioDirection==="vertical"?"":l}),N={input:x.default,textarea:H.default,checkbox:S.default,switch:P.default,combo:J.default,radio:M.default,searchCombo:E.default,datepicker:L.default,editor:j.default},y=e.computed(()=>N[t.type]||x.default),u=e.computed(()=>t.fillHeight&&t.type==="editor"),{isDesktop:T}=A.useBreakpoint(),n=e.computed(()=>t.orientation==="responsive"?T.value?"horizontal":"vertical":t.orientation);return b({clearError:()=>{i.value=""}}),(l,a)=>(e.openBlock(),e.createElementBlock("div",{class:e.normalizeClass(e.unref(O.cn)(u.value?"flex-1 flex flex-col min-h-0":r.type==="editor"?"flex-1 min-w-0":"space-y-2 flex-1 min-w-0",F.value,t.class))},[e.createVNode(e.unref(s.default),{class:e.normalizeClass(u.value?"flex-1 flex flex-col min-h-0":"")},{default:e.withCtx(()=>[e.createVNode(e.unref(k.default),{class:e.normalizeClass([n.value==="horizontal"?"grid grid-cols-[var(--label-w,8rem)_1fr] items-start space-y-0 gap-2":r.type==="editor"?"space-y-0 gap-0":"space-y-1 gap-1",u.value?"flex-1 min-h-0":""]),style:e.normalizeStyle(n.value==="horizontal"&&r.labelWidth?`--label-w:${r.labelWidth};`:"")},{default:e.withCtx(()=>[e.createVNode(e.unref(C.default),{for:r.id,class:e.normalizeClass(["text-xs font-medium",n.value==="horizontal"?r.type==="editor"?"flex items-start pt-1 w-full":"flex items-center h-[var(--ctl-h)] w-full":"flex items-center",D.value])},{default:e.withCtx(()=>[e.createTextVNode(e.toDisplayString(r.label)+" ",1),r.required?(e.openBlock(),e.createElementBlock("span",W,"*")):e.createCommentVNode("",!0)]),_:1},8,["for","class"]),e.createVNode(e.unref(q.default),{class:e.normalizeClass([n.value==="horizontal"?"min-h-[var(--ctl-h)] flex flex-col justify-start gap-0.5 mt-0":"space-y-2 gap-0",u.value?"flex-1 flex flex-col min-h-0":""])},{default:e.withCtx(()=>[r.type==="checkbox"||r.type==="switch"?(e.openBlock(),e.createBlock(e.unref(s.default),{key:0,"data-slot":"checkbox-group"},{default:e.withCtx(()=>[e.createVNode(e.unref(k.default),{orientation:"horizontal",class:"flex gap-2 space-y-0 h-[var(--ctl-h)] leading-[var(--ctl-h)] items-center"},{default:e.withCtx(()=>[(e.openBlock(),e.createBlock(e.resolveDynamicComponent(y.value),e.mergeProps(p.value,{"onUpdate:modelValue":m,onChange:h,onFocus:_,onBlur:v}),null,16)),r.inlineLabel?(e.openBlock(),e.createBlock(e.unref(C.default),{key:0,for:r.id,class:"text-xs font-normal m-0 h-[var(--ctl-h)] leading-[var(--ctl-h)]"},{default:e.withCtx(()=>[e.createTextVNode(e.toDisplayString(r.inlineLabel),1)]),_:1},8,["for"])):e.createCommentVNode("",!0)]),_:1})]),_:1})):r.type==="radio"?(e.openBlock(),e.createBlock(e.unref(s.default),{key:1},{default:e.withCtx(()=>[(e.openBlock(),e.createBlock(e.resolveDynamicComponent(y.value),e.mergeProps(p.value,{"onUpdate:modelValue":m,onChange:h,onFocus:_,onBlur:v,class:g.value}),null,16,["class"]))]),_:1})):(e.openBlock(),e.createBlock(e.unref(s.default),{key:2,class:e.normalizeClass(u.value?"flex-1 flex flex-col min-h-0":"")},{default:e.withCtx(()=>[(e.openBlock(),e.createBlock(e.resolveDynamicComponent(y.value),e.mergeProps(p.value,{"onUpdate:modelValue":m,onChange:h,onFocus:_,onBlur:v,onSave:z,class:g.value}),null,16,["class"]))]),_:1},8,["class"])),r.description||d.value?(e.openBlock(),e.createBlock(e.unref(q.default),{key:3},{default:e.withCtx(()=>[r.description?(e.openBlock(),e.createBlock(e.unref(U.default),{key:0},{default:e.withCtx(()=>[e.createTextVNode(e.toDisplayString(r.description),1)]),_:1})):e.createCommentVNode("",!0),d.value?(e.openBlock(),e.createBlock(e.unref(R.default),{key:1},{default:e.withCtx(()=>[e.createTextVNode(e.toDisplayString(d.value),1)]),_:1})):e.createCommentVNode("",!0)]),_:1})):e.createCommentVNode("",!0)]),_:1},8,["class"])]),_:1},8,["class","style"])]),_:1},8,["class"])],2))}});exports.default=I;
2
2
  //# sourceMappingURL=JFormField.vue2.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"JFormField.vue2.cjs","sources":["../../../../src/components/molecules/JFormField.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport { Field, FieldContent, FieldLabel, FieldDescription, FieldError, FieldGroup } from '@/components/shadcn'\nimport { JInput, JTextarea, JCheckbox, JSwitch, JCombo, JRadio, JSearchCombo, JDatepicker, JEditor } from '@/components/atoms'\nimport { cn } from '@/lib/utils'\n\n// 컴포넌트 타입 정의\ntype ComponentType = 'input' | 'textarea' | 'checkbox' | 'switch' | 'combo' | 'radio' | 'searchCombo' | 'datepicker' | 'editor'\n\n// FormField 자체의 props (레이아웃 관련)\nconst FORM_FIELD_PROPS = [\n 'class',\n 'label',\n 'description',\n 'errorMsg',\n 'type',\n 'inlineLabel',\n 'orientation',\n 'labelAlign',\n 'labelWidth',\n 'radioDirection',\n 'editorHeight',\n 'fillHeight',\n] as const\n\nconst props = withDefaults(\n defineProps<{\n // ============ FormField 자체 props (레이아웃만) ============\n /** 추가 클래스 (외부 커스터마이징용) */\n class?: string\n /** 필드 레이블 */\n label?: string\n /** 필드 설명 (레이블 아래 표시) */\n description?: string\n /** 에러 메시지 */\n errorMsg?: string\n /** 컴포넌트 타입 (렌더링할 컴포넌트 지정) */\n type?: ComponentType\n /** 체크박스/스위치 타입일 때만 사용하는 옆 라벨 */\n inlineLabel?: string\n /** 레이블과 컨트롤의 배치 방향 */\n orientation?: 'vertical' | 'horizontal' | 'responsive'\n /** 레이블 텍스트 정렬 */\n labelAlign?: 'left' | 'middle' | 'right'\n /** 레이블 영역 너비 (horizontal orientation일 때만 적용) */\n labelWidth?: string\n\n // ============ 내부 컴포넌트로 전달할 공통 props ============\n /** Input 요소의 id (label for와 연결) */\n id?: string\n /** v-model로 양방향 데이터 바인딩 */\n modelValue?: any\n /** 입력 전 표시되는 안내문 (Input/Textarea/Select/Combobox) */\n placeholder?: string\n /** 비활성화 상태 (전체) */\n disabled?: boolean\n /** 읽기 전용 상태 (Input/Textarea) */\n readonly?: boolean\n /** 필수 입력/선택 여부 (전체) */\n required?: boolean\n /** form 데이터 전송 시 키 이름 (전체) */\n name?: string\n /** 스타일 테마 지정 (J-prefixed 컴포넌트의 styleType) */\n styleType?: string\n\n // ============ Input 전용 props ============\n /** Input 타입 (text, email, password 등) */\n inputType?: string\n\n\n // ============ Select/Combobox/Radio 전용 props ============\n /** 선택 가능한 항목 배열 */\n options?: Array<{ label: string; value: string | number; disabled?: boolean }>\n \n // ============ Select/Combobox 전용 props ============\n /** 다중 선택 허용 여부 */\n multiple?: boolean\n\n\n // ============ Radio 전용 props ============\n /** Radio 옵션 나열 방향 */\n radioDirection?: 'horizontal' | 'vertical'\n\n // ============ Editor 전용 props ============\n /** 에디터 높이 (기본값: '300px') */\n editorHeight?: string | number\n /** 에디터가 남은 세로 공간을 모두 채우도록 확장 (type='editor'일 때만 적용) */\n fillHeight?: boolean\n }>(),\n {\n type: 'input',\n orientation: 'horizontal',\n labelAlign: 'left',\n labelWidth: '80px',\n radioDirection: 'horizontal',\n }\n)\n\n// 이벤트 정의\nconst emit = defineEmits<{\n 'update:modelValue': [value: any]\n 'change': [value: any]\n 'focus': [event: FocusEvent]\n 'blur': [event: FocusEvent]\n 'save': [value: string]\n}>()\n\n// 내부 에러 상태 (props.error가 없을 때만 사용)\nconst internalError = ref<string>('')\n\n// 최종 에러 메시지 (외부 errorMsg가 우선)\nconst finalError = computed(() => props.errorMsg || internalError.value)\n\n// FormField 자체 props와 내부 컴포넌트 props 분리\nconst controlProps = computed(() => {\n const result: Record<string, any> = {}\n const propsObj = props as Record<string, any>\n \n for (const key in propsObj) {\n // FormField 자체 props는 제외\n if (!FORM_FIELD_PROPS.includes(key as any)) {\n result[key] = propsObj[key]\n }\n }\n \n // inputType을 type으로 변환 (JInput 컴포넌트에서 사용)\n if (props.inputType && props.type === 'input') {\n result.type = props.inputType\n delete result.inputType // inputType은 제거\n \n // placeholder가 없으면 inputType에 따라 기본값 설정\n if (!props.placeholder) {\n const defaultPlaceholders: Record<string, string> = {\n 'text': '텍스트를 입력하세요',\n 'email': '이메일을 입력하세요',\n 'password': '비밀번호를 입력하세요',\n 'tel': '전화번호를 입력하세요',\n 'url': 'URL을 입력하세요',\n 'number': '숫자를 입력하세요',\n 'search': '검색어를 입력하세요',\n 'date': '날짜를 선택하세요',\n 'time': '시간을 선택하세요',\n 'datetime-local': '날짜와 시간을 선택하세요',\n 'month': '월을 선택하세요',\n 'week': '주를 선택하세요',\n }\n \n result.placeholder = defaultPlaceholders[props.inputType] || '입력하세요'\n }\n }\n \n // radioDirection을 styletype으로 변환 (JRadio 컴포넌트에서 사용)\n if (props.radioDirection && props.type === 'radio') {\n result.styletype = props.radioDirection\n delete result.radioDirection // radioDirection은 제거\n }\n\n // editorHeight를 height로 변환 (JEditor 컴포넌트에서 사용)\n if (props.type === 'editor') {\n result.height = props.fillHeight ? '100%' : (props.editorHeight ?? '300px')\n delete result.editorHeight\n delete result.fillHeight\n }\n\n return result\n})\n\n // Built-in validation\n const validateField = (currentValue?: any) => {\n if (!props.required) return\n\n internalError.value = ''\n\n // 현재 값 또는 props.modelValue 사용\n const value = currentValue !== undefined ? currentValue : props.modelValue\n\n // Required 체크\n if (props.type === 'checkbox' || props.type === 'switch') {\n if (value !== 'Y') {\n internalError.value = '필수 항목입니다.'\n }\n } else {\n if (value === null || value === undefined || value === '') {\n internalError.value = '필수 입력 항목입니다.'\n }\n }\n }\n\n // 초기 로드 시 validation 실행 (blur 이벤트에서만)\n const shouldValidateOnMount = computed(() => {\n // Storybook에서 초기값이 있는 경우 validation 스킵\n if (props.modelValue !== null && props.modelValue !== undefined && props.modelValue !== '') {\n return false\n }\n return true\n })\n\n// 이벤트 핸들러\nconst handleUpdateModelValue = (value: any) => {\n emit('update:modelValue', value)\n \n // 값이 변경되면 즉시 validation 실행 (현재 값 전달)\n validateField(value)\n}\n\nconst handleChange = (value: any) => {\n emit('change', value)\n \n // change 이벤트에서도 validation 실행 (현재 값 전달)\n validateField(value)\n}\n\nconst handleSave = (value: string) => {\n emit('save', value)\n}\n\nconst handleFocus = (event: FocusEvent) => {\n emit('focus', event)\n}\n\n const handleBlur = (event: FocusEvent) => {\n // blur 시에만 validation 실행 (초기 로드 시에는 실행하지 않음)\n if (shouldValidateOnMount.value) {\n validateField()\n }\n emit('blur', event)\n }\n\n// 레이블 정렬 클래스 (레이블 영역 내에서 레이블의 위치 제어)\nconst labelAlignClass = computed(() => {\n // horizontal일 때는 레이블 영역 내에서 레이블의 위치를 제어\n const mapHorizontal = { \n left: 'justify-start', // 레이블 영역의 왼쪽에 레이블 위치\n middle: 'justify-center', // 레이블 영역의 가운데에 레이블 위치\n right: 'justify-end' // 레이블 영역의 오른쪽에 레이블 위치 (input과 가까이)\n }\n // vertical일 때는 텍스트 정렬만 제어\n const mapVertical = { left: 'text-left', middle: 'text-center', right: 'text-right' }\n return props.orientation === 'horizontal' ? mapHorizontal[props.labelAlign] : mapVertical[props.labelAlign]\n})\n\n/** 행높이 토큰 클래스 매핑 */\nconst densityClass = computed(() => {\n switch (props.styleType) {\n case 'md': return 'form-density-md'\n case 'lg': return 'form-density-lg'\n default: return 'form-density-sm' // 기본값 변경: md → sm (컴팩트 디자인)\n }\n})\n\n/** 컨트롤 클래스 (datepicker는 최대 너비 제한, radio vertical은 높이 제한 없음) */\nconst controlClass = computed(() => {\n const baseClass = 'h-[var(--ctl-h)] leading-[var(--ctl-h)]'\n \n if (props.type === 'datepicker') {\n return `${baseClass} max-w-xs`\n }\n\n // Editor: fillHeight면 h-full, 아니면 높이 제한 없음\n if (props.type === 'editor') {\n return props.fillHeight ? 'h-full w-full' : ''\n }\n\n // Radio vertical: 고정 높이 제한 없음\n if (props.type === 'radio' && props.radioDirection === 'vertical') {\n return ''\n }\n\n return baseClass\n})\n\n// 컴포넌트 매핑\nconst componentMap: Record<ComponentType, any> = {\n input: JInput,\n textarea: JTextarea,\n checkbox: JCheckbox,\n switch: JSwitch,\n combo: JCombo,\n radio: JRadio,\n searchCombo: JSearchCombo,\n datepicker: JDatepicker,\n editor: JEditor,\n}\n\n// type에 따라 렌더링할 컴포넌트 결정\nconst resolvedComponent = computed(() => {\n return componentMap[props.type!] || JInput\n})\n\n/** fillHeight + editor 조합 여부 */\nconst fillHeightEditor = computed(() => props.fillHeight && props.type === 'editor')\n\n// 외부에서 수동으로 에러 클리어 가능하도록 expose\ndefineExpose({\n clearError: () => { internalError.value = '' }\n})\n</script>\n\n<template>\n <div :class=\"cn(fillHeightEditor ? 'flex-1 flex flex-col min-h-0' : type === 'editor' ? 'flex-1 min-w-0' : 'space-y-2 flex-1 min-w-0', densityClass, props.class)\">\n <FieldGroup :class=\"fillHeightEditor ? 'flex-1 flex flex-col min-h-0' : ''\">\n <Field :class=\"[\n orientation === 'horizontal'\n ? 'grid grid-cols-[var(--label-w,8rem)_1fr] items-start space-y-0 gap-2'\n : type === 'editor' ? 'space-y-0 gap-0' : 'space-y-1 gap-1',\n fillHeightEditor ? 'flex-1 min-h-0' : ''\n ]\"\n :style=\"orientation === 'horizontal' && labelWidth ? `--label-w:${labelWidth};` : ''\"\n >\n <!-- 메인 라벨 (필수표기 포함) -->\n <FieldLabel \n :for=\"id\" \n :class=\"[\n 'text-xs font-medium', // 컴팩트 디자인 + 라벨-값 weight 차별화\n orientation === 'horizontal'\n ? (type === 'editor' ? 'flex items-start pt-1 w-full' : 'flex items-center h-[var(--ctl-h)] w-full')\n : 'flex items-center',\n labelAlignClass\n ]\"\n >\n {{ label }}\n <span v-if=\"required\" class=\"text-destructive ml-0.5\">*</span>\n </FieldLabel>\n\n <FieldContent\n :class=\"[\n orientation === 'horizontal'\n ? 'min-h-[var(--ctl-h)] flex flex-col justify-start gap-0.5 mt-0'\n : 'space-y-2 gap-0',\n fillHeightEditor ? 'flex-1 flex flex-col min-h-0' : ''\n ]\"\n >\n <!-- 체크박스/스위치: 항상 가로 정렬로 컨트롤 + 인라인 라벨 묶기 -->\n <FieldGroup v-if=\"type === 'checkbox' || type === 'switch'\" data-slot=\"checkbox-group\">\n <!-- 부모 orientation과 무관하게 수평 정렬 고정 -->\n <Field orientation=\"horizontal\" class=\"flex gap-2 space-y-0 h-[var(--ctl-h)] leading-[var(--ctl-h)] items-center\">\n <component\n :is=\"resolvedComponent\"\n v-bind=\"controlProps\"\n @update:modelValue=\"handleUpdateModelValue\"\n @change=\"handleChange\"\n @focus=\"handleFocus\"\n @blur=\"handleBlur\"\n />\n <FieldLabel\n v-if=\"inlineLabel\"\n :for=\"id\"\n class=\"text-xs font-normal m-0 h-[var(--ctl-h)] leading-[var(--ctl-h)]\"\n >\n {{ inlineLabel }}\n </FieldLabel>\n </Field>\n </FieldGroup>\n\n <!-- Radio 버튼: radioDirection에 따라 분기 처리 -->\n <FieldGroup v-else-if=\"type === 'radio'\">\n <component\n :is=\"resolvedComponent\"\n v-bind=\"controlProps\"\n @update:modelValue=\"handleUpdateModelValue\"\n @change=\"handleChange\"\n @focus=\"handleFocus\"\n @blur=\"handleBlur\"\n :class=\"controlClass\"\n />\n </FieldGroup>\n\n <!-- 그 외 컨트롤: orientation 규칙 그대로 따름 -->\n <FieldGroup v-else :class=\"fillHeightEditor ? 'flex-1 flex flex-col min-h-0' : ''\">\n <component\n :is=\"resolvedComponent\"\n v-bind=\"controlProps\"\n @update:modelValue=\"handleUpdateModelValue\"\n @change=\"handleChange\"\n @focus=\"handleFocus\"\n @blur=\"handleBlur\"\n @save=\"handleSave\"\n :class=\"controlClass\"\n />\n </FieldGroup>\n \n <!-- 설명/에러: 항상 컨트롤 '바로 아래'에 표시 -->\n <FieldContent v-if=\"description || finalError\">\n <FieldDescription v-if=\"description\">\n {{ description }}\n </FieldDescription>\n <FieldError v-if=\"finalError\">\n {{ finalError }}\n </FieldError>\n </FieldContent>\n </FieldContent>\n </Field>\n </FieldGroup>\n </div>\n</template>\n\n\n\n<style scoped>\n/* 높이 토큰(밀도) — :root의 --j-ctl-h-* 를 참조, 앱에서 override 가능 */\n.form-density-sm { --ctl-h: var(--j-ctl-h-sm, 1.75rem); }\n.form-density-md { --ctl-h: var(--j-ctl-h-md, 2.25rem); }\n.form-density-lg { --ctl-h: var(--j-ctl-h-lg, 2.5rem); }\n</style>"],"names":["FORM_FIELD_PROPS","props","__props","emit","__emit","internalError","ref","finalError","computed","controlProps","result","propsObj","key","defaultPlaceholders","validateField","currentValue","value","shouldValidateOnMount","handleUpdateModelValue","handleChange","handleSave","handleFocus","event","handleBlur","labelAlignClass","mapHorizontal","mapVertical","densityClass","controlClass","baseClass","componentMap","JInput","JTextarea","JCheckbox","JSwitch","JCombo","JRadio","JSearchCombo","JDatepicker","JEditor","resolvedComponent","fillHeightEditor","__expose","_createElementBlock","_normalizeClass","_unref","cn","_createVNode","FieldGroup","Field","_normalizeStyle","FieldLabel","_createTextVNode","_toDisplayString","_hoisted_1","FieldContent","_createBlock","_openBlock","_resolveDynamicComponent","_mergeProps","FieldDescription","FieldError"],"mappings":"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"}
1
+ {"version":3,"file":"JFormField.vue2.cjs","sources":["../../../../src/components/molecules/JFormField.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport { Field, FieldContent, FieldLabel, FieldDescription, FieldError, FieldGroup } from '@/components/shadcn'\nimport { JInput, JTextarea, JCheckbox, JSwitch, JCombo, JRadio, JSearchCombo, JDatepicker, JEditor } from '@/components/atoms'\nimport { cn } from '@/lib/utils'\nimport { useBreakpoint } from '@/composables/useBreakpoint'\n\n// 컴포넌트 타입 정의\ntype ComponentType = 'input' | 'textarea' | 'checkbox' | 'switch' | 'combo' | 'radio' | 'searchCombo' | 'datepicker' | 'editor'\n\n// FormField 자체의 props (레이아웃 관련)\nconst FORM_FIELD_PROPS = [\n 'class',\n 'label',\n 'description',\n 'errorMsg',\n 'type',\n 'inlineLabel',\n 'orientation',\n 'labelAlign',\n 'labelWidth',\n 'radioDirection',\n 'editorHeight',\n 'fillHeight',\n] as const\n\nconst props = withDefaults(\n defineProps<{\n // ============ FormField 자체 props (레이아웃만) ============\n /** 추가 클래스 (외부 커스터마이징용) */\n class?: string\n /** 필드 레이블 */\n label?: string\n /** 필드 설명 (레이블 아래 표시) */\n description?: string\n /** 에러 메시지 */\n errorMsg?: string\n /** 컴포넌트 타입 (렌더링할 컴포넌트 지정) */\n type?: ComponentType\n /** 체크박스/스위치 타입일 때만 사용하는 옆 라벨 */\n inlineLabel?: string\n /** 레이블과 컨트롤의 배치 방향 */\n orientation?: 'vertical' | 'horizontal' | 'responsive'\n /** 레이블 텍스트 정렬 */\n labelAlign?: 'left' | 'middle' | 'right'\n /** 레이블 영역 너비 (horizontal orientation일 때만 적용) */\n labelWidth?: string\n\n // ============ 내부 컴포넌트로 전달할 공통 props ============\n /** Input 요소의 id (label for와 연결) */\n id?: string\n /** v-model로 양방향 데이터 바인딩 */\n modelValue?: any\n /** 입력 전 표시되는 안내문 (Input/Textarea/Select/Combobox) */\n placeholder?: string\n /** 비활성화 상태 (전체) */\n disabled?: boolean\n /** 읽기 전용 상태 (Input/Textarea) */\n readonly?: boolean\n /** 필수 입력/선택 여부 (전체) */\n required?: boolean\n /** form 데이터 전송 시 키 이름 (전체) */\n name?: string\n /** 스타일 테마 지정 (J-prefixed 컴포넌트의 styleType) */\n styleType?: string\n\n // ============ Input 전용 props ============\n /** Input 타입 (text, email, password 등) */\n inputType?: string\n\n\n // ============ Select/Combobox/Radio 전용 props ============\n /** 선택 가능한 항목 배열 */\n options?: Array<{ label: string; value: string | number; disabled?: boolean }>\n \n // ============ Select/Combobox 전용 props ============\n /** 다중 선택 허용 여부 */\n multiple?: boolean\n\n\n // ============ Radio 전용 props ============\n /** Radio 옵션 나열 방향 */\n radioDirection?: 'horizontal' | 'vertical'\n\n // ============ Editor 전용 props ============\n /** 에디터 높이 (기본값: '300px') */\n editorHeight?: string | number\n /** 에디터가 남은 세로 공간을 모두 채우도록 확장 (type='editor'일 때만 적용) */\n fillHeight?: boolean\n }>(),\n {\n type: 'input',\n orientation: 'horizontal',\n labelAlign: 'left',\n labelWidth: '80px',\n radioDirection: 'horizontal',\n }\n)\n\n// 이벤트 정의\nconst emit = defineEmits<{\n 'update:modelValue': [value: any]\n 'change': [value: any]\n 'focus': [event: FocusEvent]\n 'blur': [event: FocusEvent]\n 'save': [value: string]\n}>()\n\n// 내부 에러 상태 (props.error가 없을 때만 사용)\nconst internalError = ref<string>('')\n\n// 최종 에러 메시지 (외부 errorMsg가 우선)\nconst finalError = computed(() => props.errorMsg || internalError.value)\n\n// FormField 자체 props와 내부 컴포넌트 props 분리\nconst controlProps = computed(() => {\n const result: Record<string, any> = {}\n const propsObj = props as Record<string, any>\n \n for (const key in propsObj) {\n // FormField 자체 props는 제외\n if (!FORM_FIELD_PROPS.includes(key as any)) {\n result[key] = propsObj[key]\n }\n }\n \n // inputType을 type으로 변환 (JInput 컴포넌트에서 사용)\n if (props.inputType && props.type === 'input') {\n result.type = props.inputType\n delete result.inputType // inputType은 제거\n \n // placeholder가 없으면 inputType에 따라 기본값 설정\n if (!props.placeholder) {\n const defaultPlaceholders: Record<string, string> = {\n 'text': '텍스트를 입력하세요',\n 'email': '이메일을 입력하세요',\n 'password': '비밀번호를 입력하세요',\n 'tel': '전화번호를 입력하세요',\n 'url': 'URL을 입력하세요',\n 'number': '숫자를 입력하세요',\n 'search': '검색어를 입력하세요',\n 'date': '날짜를 선택하세요',\n 'time': '시간을 선택하세요',\n 'datetime-local': '날짜와 시간을 선택하세요',\n 'month': '월을 선택하세요',\n 'week': '주를 선택하세요',\n }\n \n result.placeholder = defaultPlaceholders[props.inputType] || '입력하세요'\n }\n }\n \n // radioDirection을 styletype으로 변환 (JRadio 컴포넌트에서 사용)\n if (props.radioDirection && props.type === 'radio') {\n result.styletype = props.radioDirection\n delete result.radioDirection // radioDirection은 제거\n }\n\n // editorHeight를 height로 변환 (JEditor 컴포넌트에서 사용)\n if (props.type === 'editor') {\n result.height = props.fillHeight ? '100%' : (props.editorHeight ?? '300px')\n delete result.editorHeight\n delete result.fillHeight\n }\n\n return result\n})\n\n // Built-in validation\n const validateField = (currentValue?: any) => {\n if (!props.required) return\n\n internalError.value = ''\n\n // 현재 값 또는 props.modelValue 사용\n const value = currentValue !== undefined ? currentValue : props.modelValue\n\n // Required 체크\n if (props.type === 'checkbox' || props.type === 'switch') {\n if (value !== 'Y') {\n internalError.value = '필수 항목입니다.'\n }\n } else {\n if (value === null || value === undefined || value === '') {\n internalError.value = '필수 입력 항목입니다.'\n }\n }\n }\n\n // 초기 로드 시 validation 실행 (blur 이벤트에서만)\n const shouldValidateOnMount = computed(() => {\n // Storybook에서 초기값이 있는 경우 validation 스킵\n if (props.modelValue !== null && props.modelValue !== undefined && props.modelValue !== '') {\n return false\n }\n return true\n })\n\n// 이벤트 핸들러\nconst handleUpdateModelValue = (value: any) => {\n emit('update:modelValue', value)\n \n // 값이 변경되면 즉시 validation 실행 (현재 값 전달)\n validateField(value)\n}\n\nconst handleChange = (value: any) => {\n emit('change', value)\n \n // change 이벤트에서도 validation 실행 (현재 값 전달)\n validateField(value)\n}\n\nconst handleSave = (value: string) => {\n emit('save', value)\n}\n\nconst handleFocus = (event: FocusEvent) => {\n emit('focus', event)\n}\n\n const handleBlur = (event: FocusEvent) => {\n // blur 시에만 validation 실행 (초기 로드 시에는 실행하지 않음)\n if (shouldValidateOnMount.value) {\n validateField()\n }\n emit('blur', event)\n }\n\n// 레이블 정렬 클래스 (레이블 영역 내에서 레이블의 위치 제어)\nconst labelAlignClass = computed(() => {\n // horizontal일 때는 레이블 영역 내에서 레이블의 위치를 제어\n const mapHorizontal = { \n left: 'justify-start', // 레이블 영역의 왼쪽에 레이블 위치\n middle: 'justify-center', // 레이블 영역의 가운데에 레이블 위치\n right: 'justify-end' // 레이블 영역의 오른쪽에 레이블 위치 (input과 가까이)\n }\n // vertical일 때는 텍스트 정렬만 제어\n const mapVertical = { left: 'text-left', middle: 'text-center', right: 'text-right' }\n return effectiveOrientation.value === 'horizontal' ? mapHorizontal[props.labelAlign] : mapVertical[props.labelAlign]\n})\n\n/** 행높이 토큰 클래스 매핑 */\nconst densityClass = computed(() => {\n switch (props.styleType) {\n case 'md': return 'form-density-md'\n case 'lg': return 'form-density-lg'\n default: return 'form-density-sm' // 기본값 변경: md → sm (컴팩트 디자인)\n }\n})\n\n/** 컨트롤 클래스 (datepicker는 최대 너비 제한, radio vertical은 높이 제한 없음) */\nconst controlClass = computed(() => {\n const baseClass = 'h-[var(--ctl-h)] leading-[var(--ctl-h)]'\n \n if (props.type === 'datepicker') {\n return `${baseClass} max-w-xs`\n }\n\n // Editor: fillHeight면 h-full, 아니면 높이 제한 없음\n if (props.type === 'editor') {\n return props.fillHeight ? 'h-full w-full' : ''\n }\n\n // Radio vertical: 고정 높이 제한 없음\n if (props.type === 'radio' && props.radioDirection === 'vertical') {\n return ''\n }\n\n return baseClass\n})\n\n// 컴포넌트 매핑\nconst componentMap: Record<ComponentType, any> = {\n input: JInput,\n textarea: JTextarea,\n checkbox: JCheckbox,\n switch: JSwitch,\n combo: JCombo,\n radio: JRadio,\n searchCombo: JSearchCombo,\n datepicker: JDatepicker,\n editor: JEditor,\n}\n\n// type에 따라 렌더링할 컴포넌트 결정\nconst resolvedComponent = computed(() => {\n return componentMap[props.type!] || JInput\n})\n\n/** fillHeight + editor 조합 여부 */\nconst fillHeightEditor = computed(() => props.fillHeight && props.type === 'editor')\n\n/** responsive orientation: 모바일에서 vertical, 데스크톱에서 horizontal */\nconst { isDesktop } = useBreakpoint()\nconst effectiveOrientation = computed(() => {\n if (props.orientation === 'responsive') {\n return isDesktop.value ? 'horizontal' : 'vertical'\n }\n return props.orientation\n})\n\n// 외부에서 수동으로 에러 클리어 가능하도록 expose\ndefineExpose({\n clearError: () => { internalError.value = '' }\n})\n</script>\n\n<template>\n <div :class=\"cn(fillHeightEditor ? 'flex-1 flex flex-col min-h-0' : type === 'editor' ? 'flex-1 min-w-0' : 'space-y-2 flex-1 min-w-0', densityClass, props.class)\">\n <FieldGroup :class=\"fillHeightEditor ? 'flex-1 flex flex-col min-h-0' : ''\">\n <Field :class=\"[\n effectiveOrientation === 'horizontal'\n ? 'grid grid-cols-[var(--label-w,8rem)_1fr] items-start space-y-0 gap-2'\n : type === 'editor' ? 'space-y-0 gap-0' : 'space-y-1 gap-1',\n fillHeightEditor ? 'flex-1 min-h-0' : ''\n ]\"\n :style=\"effectiveOrientation === 'horizontal' && labelWidth ? `--label-w:${labelWidth};` : ''\"\n >\n <!-- 메인 라벨 (필수표기 포함) -->\n <FieldLabel \n :for=\"id\" \n :class=\"[\n 'text-xs font-medium', // 컴팩트 디자인 + 라벨-값 weight 차별화\n effectiveOrientation === 'horizontal'\n ? (type === 'editor' ? 'flex items-start pt-1 w-full' : 'flex items-center h-[var(--ctl-h)] w-full')\n : 'flex items-center',\n labelAlignClass\n ]\"\n >\n {{ label }}\n <span v-if=\"required\" class=\"text-destructive ml-0.5\">*</span>\n </FieldLabel>\n\n <FieldContent\n :class=\"[\n effectiveOrientation === 'horizontal'\n ? 'min-h-[var(--ctl-h)] flex flex-col justify-start gap-0.5 mt-0'\n : 'space-y-2 gap-0',\n fillHeightEditor ? 'flex-1 flex flex-col min-h-0' : ''\n ]\"\n >\n <!-- 체크박스/스위치: 항상 가로 정렬로 컨트롤 + 인라인 라벨 묶기 -->\n <FieldGroup v-if=\"type === 'checkbox' || type === 'switch'\" data-slot=\"checkbox-group\">\n <!-- 부모 orientation과 무관하게 수평 정렬 고정 -->\n <Field orientation=\"horizontal\" class=\"flex gap-2 space-y-0 h-[var(--ctl-h)] leading-[var(--ctl-h)] items-center\">\n <component\n :is=\"resolvedComponent\"\n v-bind=\"controlProps\"\n @update:modelValue=\"handleUpdateModelValue\"\n @change=\"handleChange\"\n @focus=\"handleFocus\"\n @blur=\"handleBlur\"\n />\n <FieldLabel\n v-if=\"inlineLabel\"\n :for=\"id\"\n class=\"text-xs font-normal m-0 h-[var(--ctl-h)] leading-[var(--ctl-h)]\"\n >\n {{ inlineLabel }}\n </FieldLabel>\n </Field>\n </FieldGroup>\n\n <!-- Radio 버튼: radioDirection에 따라 분기 처리 -->\n <FieldGroup v-else-if=\"type === 'radio'\">\n <component\n :is=\"resolvedComponent\"\n v-bind=\"controlProps\"\n @update:modelValue=\"handleUpdateModelValue\"\n @change=\"handleChange\"\n @focus=\"handleFocus\"\n @blur=\"handleBlur\"\n :class=\"controlClass\"\n />\n </FieldGroup>\n\n <!-- 그 외 컨트롤: orientation 규칙 그대로 따름 -->\n <FieldGroup v-else :class=\"fillHeightEditor ? 'flex-1 flex flex-col min-h-0' : ''\">\n <component\n :is=\"resolvedComponent\"\n v-bind=\"controlProps\"\n @update:modelValue=\"handleUpdateModelValue\"\n @change=\"handleChange\"\n @focus=\"handleFocus\"\n @blur=\"handleBlur\"\n @save=\"handleSave\"\n :class=\"controlClass\"\n />\n </FieldGroup>\n \n <!-- 설명/에러: 항상 컨트롤 '바로 아래'에 표시 -->\n <FieldContent v-if=\"description || finalError\">\n <FieldDescription v-if=\"description\">\n {{ description }}\n </FieldDescription>\n <FieldError v-if=\"finalError\">\n {{ finalError }}\n </FieldError>\n </FieldContent>\n </FieldContent>\n </Field>\n </FieldGroup>\n </div>\n</template>\n\n\n\n<style scoped>\n/* 높이 토큰(밀도) — :root의 --j-ctl-h-* 를 참조, 앱에서 override 가능 */\n.form-density-sm { --ctl-h: var(--j-ctl-h-sm, 1.75rem); }\n.form-density-md { --ctl-h: var(--j-ctl-h-md, 2.25rem); }\n.form-density-lg { --ctl-h: var(--j-ctl-h-lg, 2.5rem); }\n</style>"],"names":["FORM_FIELD_PROPS","props","__props","emit","__emit","internalError","ref","finalError","computed","controlProps","result","propsObj","key","defaultPlaceholders","validateField","currentValue","value","shouldValidateOnMount","handleUpdateModelValue","handleChange","handleSave","handleFocus","event","handleBlur","labelAlignClass","mapHorizontal","mapVertical","effectiveOrientation","densityClass","controlClass","baseClass","componentMap","JInput","JTextarea","JCheckbox","JSwitch","JCombo","JRadio","JSearchCombo","JDatepicker","JEditor","resolvedComponent","fillHeightEditor","isDesktop","useBreakpoint","__expose","_createElementBlock","_normalizeClass","_unref","cn","_createVNode","FieldGroup","Field","_normalizeStyle","FieldLabel","_createTextVNode","_toDisplayString","_hoisted_1","FieldContent","_createBlock","_openBlock","_resolveDynamicComponent","_mergeProps","FieldDescription","FieldError"],"mappings":"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"}