@j-solution/components 2.0.3 → 2.0.5

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 (46) hide show
  1. package/README.md +7 -8
  2. package/assets/jwms-portal-frontend-CrtMPGot.css +1 -0
  3. package/assets/styles/j-components.css +1 -1
  4. package/components/atoms/JBadge.vue.cjs +1 -1
  5. package/components/atoms/JBadge.vue.cjs.map +1 -1
  6. package/components/atoms/JBadge.vue.js +4 -4
  7. package/components/atoms/JBadge.vue.js.map +1 -1
  8. package/components/atoms/JCombo.vue.cjs +1 -1
  9. package/components/atoms/JCombo.vue.cjs.map +1 -1
  10. package/components/atoms/JCombo.vue.js +1 -1
  11. package/components/atoms/JCombo.vue.js.map +1 -1
  12. package/components/atoms/JEditor.vue.cjs +1 -1
  13. package/components/atoms/JEditor.vue.js +2 -2
  14. package/components/atoms/JEditor.vue2.cjs.map +1 -1
  15. package/components/atoms/JEditor.vue2.js.map +1 -1
  16. package/components/atoms/JSplitter.vue.cjs +1 -1
  17. package/components/atoms/JSplitter.vue.js +1 -1
  18. package/components/atoms/JSplitter.vue2.cjs.map +1 -1
  19. package/components/atoms/JSplitter.vue2.js.map +1 -1
  20. package/components/molecules/JCard.vue.cjs +1 -1
  21. package/components/molecules/JCard.vue.cjs.map +1 -1
  22. package/components/molecules/JCard.vue.js +28 -25
  23. package/components/molecules/JCard.vue.js.map +1 -1
  24. package/components/molecules/JFormField.vue.cjs +1 -1
  25. package/components/molecules/JFormField.vue.js +2 -2
  26. package/components/molecules/JFormField.vue2.cjs +1 -1
  27. package/components/molecules/JFormField.vue2.cjs.map +1 -1
  28. package/components/molecules/JFormField.vue2.js +43 -43
  29. package/components/molecules/JFormField.vue2.js.map +1 -1
  30. package/components/molecules/JTabs.vue.cjs +1 -1
  31. package/components/molecules/JTabs.vue.js +2 -2
  32. package/components/molecules/JTabs.vue2.cjs +1 -1
  33. package/components/molecules/JTabs.vue2.cjs.map +1 -1
  34. package/components/molecules/JTabs.vue2.js +104 -60
  35. package/components/molecules/JTabs.vue2.js.map +1 -1
  36. package/components/shadcn/FieldDescription.vue.cjs +1 -1
  37. package/components/shadcn/FieldDescription.vue.cjs.map +1 -1
  38. package/components/shadcn/FieldDescription.vue.js +1 -1
  39. package/components/shadcn/FieldDescription.vue.js.map +1 -1
  40. package/components/shadcn/FieldError.vue.cjs +1 -1
  41. package/components/shadcn/FieldError.vue.cjs.map +1 -1
  42. package/components/shadcn/FieldError.vue.js +7 -7
  43. package/components/shadcn/FieldError.vue.js.map +1 -1
  44. package/package.json +1 -1
  45. package/types/index.d.ts +2 -0
  46. package/assets/jwms-portal-frontend-Ca90GVOc.css +0 -1
@@ -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)},_=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},y=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":"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":"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(y.value),e.mergeProps(d.value,{"onUpdate:modelValue":p,onChange:m,onFocus:h,onBlur:_}),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(y.value),e.mergeProps(d.value,{"onUpdate:modelValue":p,onChange:m,onFocus:h,onBlur:_,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(y.value),e.mergeProps(d.value,{"onUpdate:modelValue":p,onChange:m,onFocus:h,onBlur:_,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 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;
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' : '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 : '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","_createVNode","FieldGroup","Field","_normalizeStyle","FieldLabel","_createTextVNode","_toDisplayString","_hoisted_1","FieldContent","_createBlock","_openBlock","_resolveDynamicComponent","_mergeProps","FieldDescription","FieldError"],"mappings":"iiEAUA,MAAMA,EAAmB,CACvB,QACA,QACA,cACA,WACA,OACA,cACA,cACA,aACA,aACA,iBACA,eACA,YAAA,EAGIC,EAAQC,EA0ERC,EAAOC,EASPC,EAAgBC,EAAAA,IAAY,EAAE,EAG9BC,EAAaC,EAAAA,SAAS,IAAMP,EAAM,UAAYI,EAAc,KAAK,EAGjEI,EAAeD,EAAAA,SAAS,IAAM,CAClC,MAAME,EAA8B,CAAA,EAC9BC,EAAWV,EAEjB,UAAWW,KAAOD,EAEXX,EAAiB,SAASY,CAAU,IACvCF,EAAOE,CAAG,EAAID,EAASC,CAAG,GAK9B,GAAIX,EAAM,WAAaA,EAAM,OAAS,UACpCS,EAAO,KAAOT,EAAM,UACpB,OAAOS,EAAO,UAGV,CAACT,EAAM,aAAa,CACtB,MAAMY,EAA8C,CAClD,KAAQ,aACR,MAAS,aACT,SAAY,cACZ,IAAO,cACP,IAAO,aACP,OAAU,YACV,OAAU,aACV,KAAQ,YACR,KAAQ,YACR,iBAAkB,gBAClB,MAAS,WACT,KAAQ,UAAA,EAGVH,EAAO,YAAcG,EAAoBZ,EAAM,SAAS,GAAK,OAC/D,CAIF,OAAIA,EAAM,gBAAkBA,EAAM,OAAS,UACzCS,EAAO,UAAYT,EAAM,eACzB,OAAOS,EAAO,gBAIZT,EAAM,OAAS,WACjBS,EAAO,OAAST,EAAM,WAAa,OAAUA,EAAM,cAAgB,QACnE,OAAOS,EAAO,aACd,OAAOA,EAAO,YAGTA,CACT,CAAC,EAGOI,EAAiBC,GAAuB,CAC5C,GAAI,CAACd,EAAM,SAAU,OAErBI,EAAc,MAAQ,GAGtB,MAAMW,EAAQD,IAAiB,OAAYA,EAAed,EAAM,WAG5DA,EAAM,OAAS,YAAcA,EAAM,OAAS,SAC1Ce,IAAU,MACZX,EAAc,MAAQ,cAGpBW,GAAU,MAA+BA,IAAU,MACrDX,EAAc,MAAQ,eAG5B,EAGMY,EAAwBT,EAAAA,SAAS,IAEjC,EAAAP,EAAM,aAAe,MAAQA,EAAM,aAAe,QAAaA,EAAM,aAAe,GAIzF,EAGGiB,EAA0BF,GAAe,CAC7Cb,EAAK,oBAAqBa,CAAK,EAG/BF,EAAcE,CAAK,CACrB,EAEMG,EAAgBH,GAAe,CACnCb,EAAK,SAAUa,CAAK,EAGpBF,EAAcE,CAAK,CACrB,EAEMI,EAAcJ,GAAkB,CACpCb,EAAK,OAAQa,CAAK,CACpB,EAEMK,EAAeC,GAAsB,CACzCnB,EAAK,QAASmB,CAAK,CACrB,EAEQC,EAAcD,GAAsB,CAEpCL,EAAsB,OACxBH,EAAA,EAEFX,EAAK,OAAQmB,CAAK,CACpB,EAGIE,EAAkBhB,EAAAA,SAAS,IAAM,CAErC,MAAMiB,EAAgB,CACpB,KAAM,gBACN,OAAQ,iBACR,MAAO,aAAA,EAGHC,EAAc,CAAE,KAAM,YAAa,OAAQ,cAAe,MAAO,YAAA,EACvE,OAAOzB,EAAM,cAAgB,aAAewB,EAAcxB,EAAM,UAAU,EAAIyB,EAAYzB,EAAM,UAAU,CAC5G,CAAC,EAGK0B,EAAenB,EAAAA,SAAS,IAAM,CAClC,OAAQP,EAAM,UAAA,CACZ,IAAK,KAAM,MAAO,kBAClB,IAAK,KAAM,MAAO,kBAClB,QAAW,MAAO,iBAAA,CAEtB,CAAC,EAGK2B,EAAepB,EAAAA,SAAS,IAAM,CAClC,MAAMqB,EAAY,0CAElB,OAAI5B,EAAM,OAAS,aACV,GAAG4B,CAAS,YAIjB5B,EAAM,OAAS,SACVA,EAAM,WAAa,gBAAkB,GAI1CA,EAAM,OAAS,SAAWA,EAAM,iBAAmB,WAC9C,GAGF4B,CACT,CAAC,EAGKC,EAA2C,CAC/C,MAAOC,EAAAA,QACP,SAAUC,EAAAA,QACV,SAAUC,EAAAA,QACV,OAAQC,EAAAA,QACR,MAAOC,EAAAA,QACP,MAAOC,EAAAA,QACP,YAAaC,EAAAA,QACb,WAAYC,EAAAA,QACZ,OAAQC,EAAAA,OAAA,EAIJC,EAAoBhC,EAAAA,SAAS,IAC1BsB,EAAa7B,EAAM,IAAK,GAAK8B,EAAAA,OACrC,EAGKU,EAAmBjC,EAAAA,SAAS,IAAMP,EAAM,YAAcA,EAAM,OAAS,QAAQ,EAGnF,OAAAyC,EAAa,CACX,WAAY,IAAM,CAAErC,EAAc,MAAQ,EAAG,CAAA,CAC9C,wBAICsC,EAAAA,mBA8FM,MAAA,CA9FA,MAAKC,EAAAA,eAAEC,EAAAA,YAAGJ,EAAA,gEAAgFd,EAAA,MAAc1B,EAAM,KAAK,CAAA,CAAA,GACvH6C,cA4FaD,EAAAA,MAAAE,EAAAA,OAAA,EAAA,CA5FA,uBAAON,EAAA,MAAgB,+BAAA,EAAA,CAAA,qBAClC,IA0FQ,CA1FRK,cA0FQD,EAAAA,MAAAG,EAAAA,OAAA,EAAA,CA1FA,MAAKJ,EAAAA,eAAA,CAAc1C,EAAA,cAAW,sGAAkJuC,EAAA,MAAgB,iBAAA,EAAA,GAMrM,MAAKQ,EAAAA,eAAE/C,EAAA,cAAW,cAAqBA,EAAA,wBAA0BA,EAAA,UAAU,IAAA,EAAA,CAAA,qBAG5E,IAYa,CAZb4C,cAYaD,EAAAA,MAAAK,EAAAA,OAAA,EAAA,CAXV,IAAKhD,EAAA,GACL,MAAK0C,EAAAA,eAAA,uBAAiF1C,EAAA,cAAW,aAAmCA,EAAA,OAAI,SAAA,+BAAA,gEAA+IsB,EAAA,KAAA,uBAQxR,IAAW,CAAR2B,EAAAA,gBAAAC,EAAAA,gBAAAlD,EAAA,KAAK,EAAG,IACX,CAAA,EAAYA,EAAA,wBAAZyC,qBAA8D,OAA9DU,EAAsD,GAAC,yDAGzDP,cAkEeD,EAAAA,MAAAS,EAAAA,OAAA,EAAA,CAjEZ,MAAKV,EAAAA,eAAA,CAAgB1C,EAAA,cAAW,+FAA6IuC,EAAA,MAAgB,+BAAA,EAAA,uBAQ9L,IAmBa,CAnBKvC,EAAA,mBAAuBA,EAAA,OAAI,wBAA7CqD,EAAAA,YAmBaV,EAAAA,MAAAE,EAAAA,OAAA,EAAA,OAnB+C,YAAU,gBAAA,qBAEpE,IAgBQ,CAhBRD,cAgBQD,EAAAA,MAAAG,EAAAA,OAAA,EAAA,CAhBD,YAAY,aAAa,MAAM,2EAAA,qBACpC,IAOE,EAPFQ,YAAA,EAAAD,EAAAA,YAOEE,0BANKjB,EAAA,KAAiB,EADxBkB,EAAAA,WAEUjD,EAKR,MALoB,CACnB,sBAAmBS,EACnB,SAAQC,EACR,QAAOE,EACP,OAAME,CAAA,aAGDrB,EAAA,2BADRqD,EAAAA,YAMaV,EAAAA,MAAAK,EAAAA,OAAA,EAAA,OAJV,IAAKhD,EAAA,GACN,MAAM,iEAAA,qBAEN,IAAiB,qCAAdA,EAAA,WAAW,EAAA,CAAA,CAAA,iEAMGA,EAAA,OAAI,uBAA3BqD,cAUaV,EAAAA,MAAAE,EAAAA,OAAA,EAAA,CAAA,IAAA,GAAA,mBATX,IAQE,EARFS,YAAA,EAAAD,EAAAA,YAQEE,0BAPKjB,EAAA,KAAiB,EADxBkB,EAAAA,WAEUjD,EAMR,MANoB,CACnB,sBAAmBS,EACnB,SAAQC,EACR,QAAOE,EACP,OAAME,EACN,MAAOK,EAAA,KAAA,+CAKZ2B,EAAAA,YAWaV,EAAAA,MAAAE,EAAAA,OAAA,EAAA,OAXO,uBAAON,EAAA,MAAgB,+BAAA,EAAA,CAAA,qBACzC,IASE,EATFe,YAAA,EAAAD,EAAAA,YASEE,0BARKjB,EAAA,KAAiB,EADxBkB,EAAAA,WAEUjD,EAOR,MAPoB,CACnB,sBAAmBS,EACnB,SAAQC,EACR,QAAOE,EACP,OAAME,EACN,OAAMH,EACN,MAAOQ,EAAA,KAAA,4CAKQ1B,EAAA,aAAeK,EAAA,qBAAnCgD,cAOeV,EAAAA,MAAAS,EAAAA,OAAA,EAAA,CAAA,IAAA,GAAA,mBANb,IAEmB,CAFKpD,EAAA,2BAAxBqD,EAAAA,YAEmBV,QAAAc,EAAAA,OAAA,EAAA,CAAA,IAAA,GAAA,mBADjB,IAAiB,qCAAdzD,EAAA,WAAW,EAAA,CAAA,CAAA,sCAEEK,EAAA,qBAAlBgD,EAAAA,YAEaV,QAAAe,EAAAA,OAAA,EAAA,CAAA,IAAA,GAAA,mBADX,IAAgB,qCAAbrD,EAAA,KAAU,EAAA,CAAA,CAAA"}
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,8 +1,8 @@
1
- import { defineComponent as q, ref as N, computed as s, createElementBlock as D, openBlock as o, normalizeClass as c, unref as i, createVNode as p, withCtx as a, normalizeStyle as R, createTextVNode as y, createCommentVNode as h, toDisplayString as v, createBlock as n, resolveDynamicComponent as B, mergeProps as H } from "vue";
1
+ import { defineComponent as q, ref as N, computed as s, createElementBlock as F, openBlock as o, normalizeClass as c, unref as i, createVNode as p, withCtx as a, normalizeStyle as R, createTextVNode as y, createCommentVNode as h, toDisplayString as g, createBlock as n, resolveDynamicComponent as H, mergeProps as z } from "vue";
2
2
  import "../shadcn/index.js";
3
3
  import "lucide-vue-next";
4
4
  /* empty css */
5
- import F from "../atoms/JInput.vue.js";
5
+ import _ from "../atoms/JInput.vue.js";
6
6
  import J from "../atoms/JTextarea.vue.js";
7
7
  import I from "../atoms/JCheckbox.vue.js";
8
8
  import Y from "../atoms/JCombo.vue.js";
@@ -28,7 +28,7 @@ import "ag-grid-enterprise";
28
28
  /* empty css */
29
29
  /* empty css */
30
30
  import "vue-sonner";
31
- import g from "../shadcn/FieldGroup.vue.js";
31
+ import v from "../shadcn/FieldGroup.vue.js";
32
32
  import T from "../shadcn/Field.vue.js";
33
33
  import E from "../shadcn/FieldLabel.vue.js";
34
34
  import M from "../shadcn/FieldContent.vue.js";
@@ -81,10 +81,10 @@ const ie = {
81
81
  "fillHeight"
82
82
  ], e = l, m = j, d = N(""), x = s(() => e.errorMsg || d.value), b = s(() => {
83
83
  const t = {}, r = e;
84
- for (const f in r)
85
- A.includes(f) || (t[f] = r[f]);
84
+ for (const u in r)
85
+ A.includes(u) || (t[u] = r[u]);
86
86
  if (e.inputType && e.type === "input" && (t.type = e.inputType, delete t.inputType, !e.placeholder)) {
87
- const f = {
87
+ const u = {
88
88
  text: "텍스트를 입력하세요",
89
89
  email: "이메일을 입력하세요",
90
90
  password: "비밀번호를 입력하세요",
@@ -98,7 +98,7 @@ const ie = {
98
98
  month: "월을 선택하세요",
99
99
  week: "주를 선택하세요"
100
100
  };
101
- t.placeholder = f[e.inputType] || "입력하세요";
101
+ t.placeholder = u[e.inputType] || "입력하세요";
102
102
  }
103
103
  return e.radioDirection && e.type === "radio" && (t.styletype = e.radioDirection, delete t.radioDirection), e.type === "editor" && (t.height = e.fillHeight ? "100%" : e.editorHeight ?? "300px", delete t.editorHeight, delete t.fillHeight), t;
104
104
  }), k = (t) => {
@@ -108,13 +108,13 @@ const ie = {
108
108
  e.type === "checkbox" || e.type === "switch" ? r !== "Y" && (d.value = "필수 항목입니다.") : (r == null || r === "") && (d.value = "필수 입력 항목입니다.");
109
109
  }, P = s(() => !(e.modelValue !== null && e.modelValue !== void 0 && e.modelValue !== "")), $ = (t) => {
110
110
  m("update:modelValue", t), k(t);
111
- }, C = (t) => {
111
+ }, w = (t) => {
112
112
  m("change", t), k(t);
113
113
  }, S = (t) => {
114
114
  m("save", t);
115
- }, V = (t) => {
115
+ }, C = (t) => {
116
116
  m("focus", t);
117
- }, w = (t) => {
117
+ }, V = (t) => {
118
118
  P.value && k(), m("blur", t);
119
119
  }, U = s(() => {
120
120
  const t = {
@@ -135,11 +135,11 @@ const ie = {
135
135
  default:
136
136
  return "form-density-sm";
137
137
  }
138
- }), z = s(() => {
138
+ }), D = s(() => {
139
139
  const t = "h-[var(--ctl-h)] leading-[var(--ctl-h)]";
140
140
  return e.type === "datepicker" ? `${t} max-w-xs` : e.type === "editor" ? e.fillHeight ? "h-full w-full" : "" : e.type === "radio" && e.radioDirection === "vertical" ? "" : t;
141
141
  }), W = {
142
- input: F,
142
+ input: _,
143
143
  textarea: J,
144
144
  checkbox: I,
145
145
  switch: Q,
@@ -148,22 +148,22 @@ const ie = {
148
148
  searchCombo: G,
149
149
  datepicker: X,
150
150
  editor: Z
151
- }, _ = s(() => W[e.type] || F), u = s(() => e.fillHeight && e.type === "editor");
151
+ }, B = s(() => W[e.type] || _), f = s(() => e.fillHeight && e.type === "editor");
152
152
  return L({
153
153
  clearError: () => {
154
154
  d.value = "";
155
155
  }
156
- }), (t, r) => (o(), D("div", {
157
- class: c(i(ee)(u.value ? "flex-1 flex flex-col min-h-0" : "space-y-2 flex-1 min-w-0", O.value, e.class))
156
+ }), (t, r) => (o(), F("div", {
157
+ class: c(i(ee)(f.value ? "flex-1 flex flex-col min-h-0" : l.type === "editor" ? "flex-1 min-w-0" : "space-y-2 flex-1 min-w-0", O.value, e.class))
158
158
  }, [
159
- p(i(g), {
160
- class: c(u.value ? "flex-1 flex flex-col min-h-0" : "")
159
+ p(i(v), {
160
+ class: c(f.value ? "flex-1 flex flex-col min-h-0" : "")
161
161
  }, {
162
162
  default: a(() => [
163
163
  p(i(T), {
164
164
  class: c([
165
- l.orientation === "horizontal" ? "grid grid-cols-[var(--label-w,8rem)_1fr] items-start space-y-0 gap-2" : "space-y-1 gap-1",
166
- u.value ? "flex-1 min-h-0" : ""
165
+ l.orientation === "horizontal" ? "grid grid-cols-[var(--label-w,8rem)_1fr] items-start space-y-0 gap-2" : l.type === "editor" ? "space-y-0 gap-0" : "space-y-1 gap-1",
166
+ f.value ? "flex-1 min-h-0" : ""
167
167
  ]),
168
168
  style: R(l.orientation === "horizontal" && l.labelWidth ? `--label-w:${l.labelWidth};` : "")
169
169
  }, {
@@ -178,19 +178,19 @@ const ie = {
178
178
  ])
179
179
  }, {
180
180
  default: a(() => [
181
- y(v(l.label) + " ", 1),
182
- l.required ? (o(), D("span", ie, "*")) : h("", !0)
181
+ y(g(l.label) + " ", 1),
182
+ l.required ? (o(), F("span", ie, "*")) : h("", !0)
183
183
  ]),
184
184
  _: 1
185
185
  }, 8, ["for", "class"]),
186
186
  p(i(M), {
187
187
  class: c([
188
188
  l.orientation === "horizontal" ? "min-h-[var(--ctl-h)] flex flex-col justify-start gap-0.5 mt-0" : "space-y-2 gap-0",
189
- u.value ? "flex-1 flex flex-col min-h-0" : ""
189
+ f.value ? "flex-1 flex flex-col min-h-0" : ""
190
190
  ])
191
191
  }, {
192
192
  default: a(() => [
193
- l.type === "checkbox" || l.type === "switch" ? (o(), n(i(g), {
193
+ l.type === "checkbox" || l.type === "switch" ? (o(), n(i(v), {
194
194
  key: 0,
195
195
  "data-slot": "checkbox-group"
196
196
  }, {
@@ -200,11 +200,11 @@ const ie = {
200
200
  class: "flex gap-2 space-y-0 h-[var(--ctl-h)] leading-[var(--ctl-h)] items-center"
201
201
  }, {
202
202
  default: a(() => [
203
- (o(), n(B(_.value), H(b.value, {
203
+ (o(), n(H(B.value), z(b.value, {
204
204
  "onUpdate:modelValue": $,
205
- onChange: C,
206
- onFocus: V,
207
- onBlur: w
205
+ onChange: w,
206
+ onFocus: C,
207
+ onBlur: V
208
208
  }), null, 16)),
209
209
  l.inlineLabel ? (o(), n(i(E), {
210
210
  key: 0,
@@ -212,7 +212,7 @@ const ie = {
212
212
  class: "text-xs font-normal m-0 h-[var(--ctl-h)] leading-[var(--ctl-h)]"
213
213
  }, {
214
214
  default: a(() => [
215
- y(v(l.inlineLabel), 1)
215
+ y(g(l.inlineLabel), 1)
216
216
  ]),
217
217
  _: 1
218
218
  }, 8, ["for"])) : h("", !0)
@@ -221,29 +221,29 @@ const ie = {
221
221
  })
222
222
  ]),
223
223
  _: 1
224
- })) : l.type === "radio" ? (o(), n(i(g), { key: 1 }, {
224
+ })) : l.type === "radio" ? (o(), n(i(v), { key: 1 }, {
225
225
  default: a(() => [
226
- (o(), n(B(_.value), H(b.value, {
226
+ (o(), n(H(B.value), z(b.value, {
227
227
  "onUpdate:modelValue": $,
228
- onChange: C,
229
- onFocus: V,
230
- onBlur: w,
231
- class: z.value
228
+ onChange: w,
229
+ onFocus: C,
230
+ onBlur: V,
231
+ class: D.value
232
232
  }), null, 16, ["class"]))
233
233
  ]),
234
234
  _: 1
235
- })) : (o(), n(i(g), {
235
+ })) : (o(), n(i(v), {
236
236
  key: 2,
237
- class: c(u.value ? "flex-1 flex flex-col min-h-0" : "")
237
+ class: c(f.value ? "flex-1 flex flex-col min-h-0" : "")
238
238
  }, {
239
239
  default: a(() => [
240
- (o(), n(B(_.value), H(b.value, {
240
+ (o(), n(H(B.value), z(b.value, {
241
241
  "onUpdate:modelValue": $,
242
- onChange: C,
243
- onFocus: V,
244
- onBlur: w,
242
+ onChange: w,
243
+ onFocus: C,
244
+ onBlur: V,
245
245
  onSave: S,
246
- class: z.value
246
+ class: D.value
247
247
  }), null, 16, ["class"]))
248
248
  ]),
249
249
  _: 1
@@ -252,13 +252,13 @@ const ie = {
252
252
  default: a(() => [
253
253
  l.description ? (o(), n(i(te), { key: 0 }, {
254
254
  default: a(() => [
255
- y(v(l.description), 1)
255
+ y(g(l.description), 1)
256
256
  ]),
257
257
  _: 1
258
258
  })) : h("", !0),
259
259
  x.value ? (o(), n(i(le), { key: 1 }, {
260
260
  default: a(() => [
261
- y(v(x.value), 1)
261
+ y(g(x.value), 1)
262
262
  ]),
263
263
  _: 1
264
264
  })) : h("", !0)
@@ -1 +1 @@
1
- {"version":3,"file":"JFormField.vue2.js","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' : '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 : '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","_createVNode","FieldGroup","Field","_normalizeStyle","FieldLabel","_createTextVNode","_toDisplayString","_hoisted_1","FieldContent","_createBlock","_openBlock","_resolveDynamicComponent","_mergeProps","FieldDescription","FieldError"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAUA,UAAMA,IAAmB;AAAA,MACvB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA,GAGIC,IAAQC,GA0ERC,IAAOC,GASPC,IAAgBC,EAAY,EAAE,GAG9BC,IAAaC,EAAS,MAAMP,EAAM,YAAYI,EAAc,KAAK,GAGjEI,IAAeD,EAAS,MAAM;AAClC,YAAME,IAA8B,CAAA,GAC9BC,IAAWV;AAEjB,iBAAWW,KAAOD;AAEhB,QAAKX,EAAiB,SAASY,CAAU,MACvCF,EAAOE,CAAG,IAAID,EAASC,CAAG;AAK9B,UAAIX,EAAM,aAAaA,EAAM,SAAS,YACpCS,EAAO,OAAOT,EAAM,WACpB,OAAOS,EAAO,WAGV,CAACT,EAAM,cAAa;AACtB,cAAMY,IAA8C;AAAA,UAClD,MAAQ;AAAA,UACR,OAAS;AAAA,UACT,UAAY;AAAA,UACZ,KAAO;AAAA,UACP,KAAO;AAAA,UACP,QAAU;AAAA,UACV,QAAU;AAAA,UACV,MAAQ;AAAA,UACR,MAAQ;AAAA,UACR,kBAAkB;AAAA,UAClB,OAAS;AAAA,UACT,MAAQ;AAAA,QAAA;AAGV,QAAAH,EAAO,cAAcG,EAAoBZ,EAAM,SAAS,KAAK;AAAA,MAC/D;AAIF,aAAIA,EAAM,kBAAkBA,EAAM,SAAS,YACzCS,EAAO,YAAYT,EAAM,gBACzB,OAAOS,EAAO,iBAIZT,EAAM,SAAS,aACjBS,EAAO,SAAST,EAAM,aAAa,SAAUA,EAAM,gBAAgB,SACnE,OAAOS,EAAO,cACd,OAAOA,EAAO,aAGTA;AAAA,IACT,CAAC,GAGOI,IAAgB,CAACC,MAAuB;AAC5C,UAAI,CAACd,EAAM,SAAU;AAErB,MAAAI,EAAc,QAAQ;AAGtB,YAAMW,IAAQD,MAAiB,SAAYA,IAAed,EAAM;AAGhE,MAAIA,EAAM,SAAS,cAAcA,EAAM,SAAS,WAC1Ce,MAAU,QACZX,EAAc,QAAQ,gBAGpBW,KAAU,QAA+BA,MAAU,QACrDX,EAAc,QAAQ;AAAA,IAG5B,GAGMY,IAAwBT,EAAS,MAEjC,EAAAP,EAAM,eAAe,QAAQA,EAAM,eAAe,UAAaA,EAAM,eAAe,GAIzF,GAGGiB,IAAyB,CAACF,MAAe;AAC7C,MAAAb,EAAK,qBAAqBa,CAAK,GAG/BF,EAAcE,CAAK;AAAA,IACrB,GAEMG,IAAe,CAACH,MAAe;AACnC,MAAAb,EAAK,UAAUa,CAAK,GAGpBF,EAAcE,CAAK;AAAA,IACrB,GAEMI,IAAa,CAACJ,MAAkB;AACpC,MAAAb,EAAK,QAAQa,CAAK;AAAA,IACpB,GAEMK,IAAc,CAACC,MAAsB;AACzC,MAAAnB,EAAK,SAASmB,CAAK;AAAA,IACrB,GAEQC,IAAa,CAACD,MAAsB;AAExC,MAAIL,EAAsB,SACxBH,EAAA,GAEFX,EAAK,QAAQmB,CAAK;AAAA,IACpB,GAGIE,IAAkBhB,EAAS,MAAM;AAErC,YAAMiB,IAAgB;AAAA,QACpB,MAAM;AAAA;AAAA,QACN,QAAQ;AAAA;AAAA,QACR,OAAO;AAAA;AAAA,MAAA,GAGHC,IAAc,EAAE,MAAM,aAAa,QAAQ,eAAe,OAAO,aAAA;AACvE,aAAOzB,EAAM,gBAAgB,eAAewB,EAAcxB,EAAM,UAAU,IAAIyB,EAAYzB,EAAM,UAAU;AAAA,IAC5G,CAAC,GAGK0B,IAAenB,EAAS,MAAM;AAClC,cAAQP,EAAM,WAAA;AAAA,QACZ,KAAK;AAAM,iBAAO;AAAA,QAClB,KAAK;AAAM,iBAAO;AAAA,QAClB;AAAW,iBAAO;AAAA,MAAA;AAAA,IAEtB,CAAC,GAGK2B,IAAepB,EAAS,MAAM;AAClC,YAAMqB,IAAY;AAElB,aAAI5B,EAAM,SAAS,eACV,GAAG4B,CAAS,cAIjB5B,EAAM,SAAS,WACVA,EAAM,aAAa,kBAAkB,KAI1CA,EAAM,SAAS,WAAWA,EAAM,mBAAmB,aAC9C,KAGF4B;AAAA,IACT,CAAC,GAGKC,IAA2C;AAAA,MAC/C,OAAOC;AAAAA,MACP,UAAUC;AAAAA,MACV,UAAUC;AAAAA,MACV,QAAQC;AAAAA,MACR,OAAOC;AAAAA,MACP,OAAOC;AAAAA,MACP,aAAaC;AAAAA,MACb,YAAYC;AAAAA,MACZ,QAAQC;AAAA,IAAA,GAIJC,IAAoBhC,EAAS,MAC1BsB,EAAa7B,EAAM,IAAK,KAAK8B,CACrC,GAGKU,IAAmBjC,EAAS,MAAMP,EAAM,cAAcA,EAAM,SAAS,QAAQ;AAGnF,WAAAyC,EAAa;AAAA,MACX,YAAY,MAAM;AAAE,QAAArC,EAAc,QAAQ;AAAA,MAAG;AAAA,IAAA,CAC9C,mBAICsC,EA8FM,OAAA;AAAA,MA9FA,OAAKC,EAAEC,MAAGJ,EAAA,qEAAgFd,EAAA,OAAc1B,EAAM,KAAK,CAAA;AAAA,IAAA;MACvH6C,EA4FaD,EAAAE,CAAA,GAAA;AAAA,QA5FA,SAAON,EAAA,QAAgB,iCAAA,EAAA;AAAA,MAAA;mBAClC,MA0FQ;AAAA,UA1FRK,EA0FQD,EAAAG,CAAA,GAAA;AAAA,YA1FA,OAAKJ,EAAA;AAAA,cAAc1C,EAAA,gBAAW;cAAkJuC,EAAA,QAAgB,mBAAA;AAAA,YAAA;YAMrM,OAAKQ,EAAE/C,EAAA,gBAAW,gBAAqBA,EAAA,0BAA0BA,EAAA,UAAU,MAAA,EAAA;AAAA,UAAA;uBAG5E,MAYa;AAAA,cAZb4C,EAYaD,EAAAK,CAAA,GAAA;AAAA,gBAXV,KAAKhD,EAAA;AAAA,gBACL,OAAK0C,EAAA;AAAA;;kBAAiF1C,EAAA,gBAAW,eAAmCA,EAAA,SAAI,WAAA,iCAAA;kBAA+IsB,EAAA;AAAA,gBAAA;;2BAQxR,MAAW;AAAA,kBAAR2B,EAAAC,EAAAlD,EAAA,KAAK,IAAG,KACX,CAAA;AAAA,kBAAYA,EAAA,iBAAZyC,EAA8D,QAA9DU,IAAsD,GAAC;;;;cAGzDP,EAkEeD,EAAAS,CAAA,GAAA;AAAA,gBAjEZ,OAAKV,EAAA;AAAA,kBAAgB1C,EAAA,gBAAW;kBAA6IuC,EAAA,QAAgB,iCAAA;AAAA,gBAAA;;2BAQ9L,MAmBa;AAAA,kBAnBKvC,EAAA,uBAAuBA,EAAA,SAAI,iBAA7CqD,EAmBaV,EAAAE,CAAA,GAAA;AAAA;oBAnB+C,aAAU;AAAA,kBAAA;+BAEpE,MAgBQ;AAAA,sBAhBRD,EAgBQD,EAAAG,CAAA,GAAA;AAAA,wBAhBD,aAAY;AAAA,wBAAa,OAAM;AAAA,sBAAA;mCACpC,MAOE;AAAA,2BAPFQ,EAAA,GAAAD,EAOEE,EANKjB,EAAA,KAAiB,GADxBkB,EAEUjD,EAKR,OALoB;AAAA,4BACnB,uBAAmBS;AAAA,4BACnB,UAAQC;AAAA,4BACR,SAAOE;AAAA,4BACP,QAAME;AAAA,0BAAA;0BAGDrB,EAAA,oBADRqD,EAMaV,EAAAK,CAAA,GAAA;AAAA;4BAJV,KAAKhD,EAAA;AAAA,4BACN,OAAM;AAAA,0BAAA;uCAEN,MAAiB;AAAA,kCAAdA,EAAA,WAAW,GAAA,CAAA;AAAA,4BAAA;;;;;;;;wBAMGA,EAAA,SAAI,gBAA3BqD,EAUaV,EAAAE,CAAA,GAAA,EAAA,KAAA,KAAA;AAAA,+BATX,MAQE;AAAA,uBARFS,EAAA,GAAAD,EAQEE,EAPKjB,EAAA,KAAiB,GADxBkB,EAEUjD,EAMR,OANoB;AAAA,wBACnB,uBAAmBS;AAAA,wBACnB,UAAQC;AAAA,wBACR,SAAOE;AAAA,wBACP,QAAME;AAAA,wBACN,OAAOK,EAAA;AAAA,sBAAA;;;8BAKZ2B,EAWaV,EAAAE,CAAA,GAAA;AAAA;oBAXO,SAAON,EAAA,QAAgB,iCAAA,EAAA;AAAA,kBAAA;+BACzC,MASE;AAAA,uBATFe,EAAA,GAAAD,EASEE,EARKjB,EAAA,KAAiB,GADxBkB,EAEUjD,EAOR,OAPoB;AAAA,wBACnB,uBAAmBS;AAAA,wBACnB,UAAQC;AAAA,wBACR,SAAOE;AAAA,wBACP,QAAME;AAAA,wBACN,QAAMH;AAAA,wBACN,OAAOQ,EAAA;AAAA,sBAAA;;;;kBAKQ1B,EAAA,eAAeK,EAAA,cAAnCgD,EAOeV,EAAAS,CAAA,GAAA,EAAA,KAAA,KAAA;AAAA,+BANb,MAEmB;AAAA,sBAFKpD,EAAA,oBAAxBqD,EAEmBV,EAAAc,EAAA,GAAA,EAAA,KAAA,KAAA;AAAA,mCADjB,MAAiB;AAAA,8BAAdzD,EAAA,WAAW,GAAA,CAAA;AAAA,wBAAA;;;sBAEEK,EAAA,cAAlBgD,EAEaV,EAAAe,EAAA,GAAA,EAAA,KAAA,KAAA;AAAA,mCADX,MAAgB;AAAA,8BAAbrD,EAAA,KAAU,GAAA,CAAA;AAAA,wBAAA;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"JFormField.vue2.js","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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"}
@@ -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-0e5b57c9"]]);exports.default=u;
6
+ };,u=t(e.default,[["__scopeId","data-v-0ac3efe9"]]);exports.default=u;
7
7
  //# sourceMappingURL=JTabs.vue.cjs.map
@@ -6,8 +6,8 @@ const t = (t_comp, t_opts) => {
6
6
  t_merged[t_key] = t_val;
7
7
  return t_merged;
8
8
  };
9
- const p = /* @__PURE__ */ t(o, [["__scopeId", "data-v-0e5b57c9"]]);
9
+ const f = /* @__PURE__ */ t(o, [["__scopeId", "data-v-0ac3efe9"]]);
10
10
  export {
11
- p as default
11
+ f as default
12
12
  };
13
13
  //# sourceMappingURL=JTabs.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");const h=require("lucide-vue-next"),i=require("../../lib/utils.cjs"),x=require("../atoms/JIcon.vue.cjs"),y=require("../shadcn/Tabs.vue.cjs"),T=require("../shadcn/TabsList.vue.cjs"),b=require("../shadcn/TabsTrigger.vue.cjs"),B=require("../shadcn/TabsContent.vue.cjs"),S={class:"flex-1 truncate"},E=["aria-label","onClick"],P={class:"flex-1 w-full overflow-auto"},q={key:1,class:"p-4"},V={class:"text-muted-foreground"},z=e.defineComponent({__name:"JTabs",props:{tabs:{},activeTabId:{},class:{},listClass:{},styletype:{default:"default"}},emits:["tabChange","tabClose","update:activeTabId"],setup(f,{emit:v}){const n=f,r=v,u=e.computed(()=>Array.isArray(n.tabs)?n.tabs:[]),a=e.ref(n.activeTabId||(u.value.length>0?u.value[0]?.id:"")||"");let o=!1;e.watch(()=>n.activeTabId,t=>{t!==void 0&&t!==a.value&&(a.value=t)},{immediate:!0}),e.watch(u,t=>{!n.activeTabId&&t.length>0&&!t.find(s=>s.id===a.value)&&t[0]&&(a.value=t[0].id)});const _=t=>{if(o)return;const s=String(t);s!==a.value&&(o=!0,a.value=s,r("update:activeTabId",s),r("tabChange",s),e.nextTick(()=>{o=!1}))},m=t=>{o||t===a.value||(o=!0,a.value=t,r("update:activeTabId",t),r("tabChange",t),e.nextTick(()=>{o=!1}))},C=(t,s)=>{t.stopPropagation(),r("tabClose",s)},g=e.computed(()=>i.cn("flex flex-col w-full h-full",n.class)),d={default:{tabPaddingClass:"px-2 py-1",tabTextSizeClass:"text-xs",listPaddingClass:"px-1 py-1"},minimal:{tabPaddingClass:"px-1.5 py-1",tabTextSizeClass:"text-xs",listPaddingClass:"px-1 py-1"}},c=e.computed(()=>d[n.styletype]??d.default),k=e.computed(()=>i.cn("w-full justify-start",c.value.listPaddingClass,n.listClass));return(t,s)=>(e.openBlock(),e.createBlock(e.unref(y.default),{"model-value":a.value,"onUpdate:modelValue":_,orientation:"horizontal",class:e.normalizeClass(g.value)},{default:e.withCtx(()=>[e.createVNode(e.unref(T.default),{class:e.normalizeClass(k.value)},{default:e.withCtx(()=>[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(u.value,l=>(e.openBlock(),e.createBlock(e.unref(b.default),{key:l.id,value:l.id,onClick:p=>m(l.id),class:e.normalizeClass(e.unref(i.cn)("!flex !items-center !gap-2",c.value.tabPaddingClass,c.value.tabTextSizeClass))},{default:e.withCtx(()=>[l.icon?(e.openBlock(),e.createBlock(x.default,{key:0,name:l.icon,size:"sm",class:"flex-shrink-0"},null,8,["name"])):e.createCommentVNode("",!0),e.createElementVNode("span",S,e.toDisplayString(l.label),1),l.closable?(e.openBlock(),e.createElementBlock("button",{key:1,type:"button",class:"flex-shrink-0 h-3.5 w-3.5 rounded-sm hover:bg-destructive/10 hover:text-destructive transition-colors focus:outline-none focus:ring-2 focus:ring-ring flex items-center justify-center","aria-label":`${l.label} 탭 닫기`,onClick:p=>C(p,l.id)},[e.createVNode(e.unref(h.X),{class:"h-2.5 w-2.5"})],8,E)):e.createCommentVNode("",!0)]),_:2},1032,["value","onClick","class"]))),128))]),_:1},8,["class"]),e.createElementVNode("div",P,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(u.value,l=>(e.openBlock(),e.createBlock(e.unref(B.default),{key:`content-${l.id}`,value:l.id,class:"h-full mt-0 data-[state=active]:flex data-[state=active]:flex-col"},{default:e.withCtx(()=>[e.renderSlot(t.$slots,`content-${l.id}`,{tab:l},()=>[l.component?(e.openBlock(),e.createBlock(e.resolveDynamicComponent(l.component),e.mergeProps({key:0,ref_for:!0},l.props||{}),null,16)):(e.openBlock(),e.createElementBlock("div",q,[e.createElementVNode("p",V,e.toDisplayString(l.label)+" 콘텐츠",1)]))],!0)]),_:2},1032,["value"]))),128))])]),_:3},8,["model-value","class"]))}});exports.default=z;
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue");require("../shadcn/index.cjs");const y=require("lucide-vue-next"),u=require("../../lib/utils.cjs"),C=require("../atoms/JIcon.vue.cjs"),T=require("../shadcn/Tabs.vue.cjs"),B=require("../shadcn/TabsList.vue.cjs"),w=require("../shadcn/TabsTrigger.vue.cjs"),E=require("../shadcn/TabsContent.vue.cjs"),j={class:"flex-1 truncate"},q=["aria-label","onClick"],S={key:1,class:"p-4"},V={class:"text-muted-foreground"},N=e.defineComponent({__name:"JTabs",props:{tabs:{},activeTabId:{},class:{},listClass:{},styletype:{default:"default"}},emits:["tabChange","tabClose","update:activeTabId"],setup(p,{emit:f}){const o=p,s=f,i=e.computed(()=>Array.isArray(o.tabs)?o.tabs:[]),r=e.ref(o.activeTabId||(i.value.length>0?i.value[0]?.id:"")||"");let l=!1;e.watch(()=>o.activeTabId,t=>{t!==void 0&&t!==r.value&&(r.value=t)},{immediate:!0}),e.watch(i,t=>{!o.activeTabId&&t.length>0&&!t.find(n=>n.id===r.value)&&t[0]&&(r.value=t[0].id)});const m=t=>{if(l)return;const n=String(t);n!==r.value&&(l=!0,r.value=n,s("update:activeTabId",n),s("tabChange",n),e.nextTick(()=>{l=!1}))},_=t=>{l||t===r.value||(l=!0,r.value=t,s("update:activeTabId",t),s("tabChange",t),e.nextTick(()=>{l=!1}))},g=(t,n)=>{t.stopPropagation(),s("tabClose",n)},d={default:{list:"w-full justify-start gap-0 px-0 bg-transparent border-b border-border overflow-x-auto overflow-y-hidden h-auto min-h-[2rem]",trigger:["relative px-3 py-2 text-xs font-medium","rounded-none border-b-2 border-transparent -mb-px","transition-all duration-200","flex items-center gap-2"].join(" "),active:["data-[state=active]:text-foreground","data-[state=active]:border-b-primary","data-[state=active]:bg-transparent","data-[state=active]:shadow-none"].join(" "),inactive:["data-[state=inactive]:text-muted-foreground","data-[state=inactive]:bg-transparent","data-[state=inactive]:hover:text-foreground","data-[state=inactive]:hover:border-b-muted-foreground/30"].join(" "),content:""},minimal:{list:"w-full justify-start gap-0 px-0 bg-transparent border-b border-border overflow-x-auto overflow-y-hidden h-auto min-h-[1.75rem]",trigger:["relative px-2 py-1.5 text-xs font-medium","rounded-none border-b-2 border-transparent -mb-px","transition-all duration-200","flex items-center gap-1.5"].join(" "),active:["data-[state=active]:text-foreground","data-[state=active]:border-b-primary","data-[state=active]:bg-transparent","data-[state=active]:shadow-none"].join(" "),inactive:["data-[state=inactive]:text-muted-foreground","data-[state=inactive]:bg-transparent","data-[state=inactive]:hover:text-foreground","data-[state=inactive]:hover:border-b-muted-foreground/30"].join(" "),content:""}},c=e.computed(()=>d[o.styletype]??d.default),b=e.computed(()=>u.cn("flex flex-col w-full h-full",o.class)),h=e.computed(()=>u.cn(c.value.list,o.listClass)),x=e.computed(()=>u.cn(c.value.trigger,c.value.active,c.value.inactive)),k=e.computed(()=>u.cn("flex-1 w-full overflow-auto",c.value.content));return(t,n)=>(e.openBlock(),e.createBlock(e.unref(T.default),{"model-value":r.value,"onUpdate:modelValue":m,orientation:"horizontal",class:e.normalizeClass(b.value)},{default:e.withCtx(()=>[e.createVNode(e.unref(B.default),{class:e.normalizeClass(h.value)},{default:e.withCtx(()=>[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(i.value,a=>(e.openBlock(),e.createBlock(e.unref(w.default),{key:a.id,value:a.id,onClick:v=>_(a.id),class:e.normalizeClass(x.value)},{default:e.withCtx(()=>[a.icon?(e.openBlock(),e.createBlock(C.default,{key:0,name:a.icon,size:"sm",class:"flex-shrink-0"},null,8,["name"])):e.createCommentVNode("",!0),e.createElementVNode("span",j,e.toDisplayString(a.label),1),a.closable?(e.openBlock(),e.createElementBlock("button",{key:1,type:"button",class:"flex-shrink-0 h-3.5 w-3.5 rounded-sm hover:bg-destructive/10 hover:text-destructive transition-colors focus:outline-none focus:ring-2 focus:ring-ring flex items-center justify-center","aria-label":`${a.label} 탭 닫기`,onClick:v=>g(v,a.id)},[e.createVNode(e.unref(y.X),{class:"h-2.5 w-2.5"})],8,q)):e.createCommentVNode("",!0)]),_:2},1032,["value","onClick","class"]))),128))]),_:1},8,["class"]),e.createElementVNode("div",{class:e.normalizeClass(k.value)},[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(i.value,a=>(e.openBlock(),e.createBlock(e.unref(E.default),{key:`content-${a.id}`,value:a.id,class:"h-full mt-0 data-[state=active]:flex data-[state=active]:flex-col data-[state=active]:animate-tab-fade-in"},{default:e.withCtx(()=>[e.renderSlot(t.$slots,`content-${a.id}`,{tab:a},()=>[a.component?(e.openBlock(),e.createBlock(e.resolveDynamicComponent(a.component),e.mergeProps({key:0,ref_for:!0},a.props||{}),null,16)):(e.openBlock(),e.createElementBlock("div",S,[e.createElementVNode("p",V,e.toDisplayString(a.label)+" 콘텐츠",1)]))],!0)]),_:2},1032,["value"]))),128))],2)]),_:3},8,["model-value","class"]))}});exports.default=N;
2
2
  //# sourceMappingURL=JTabs.vue2.cjs.map