@j-solution/components 1.2.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +6 -14
  2. package/USAGE_GUIDE.md +772 -1480
  3. package/assets/jwms-portal-frontend-Cs1trVbC.css +1 -0
  4. package/assets/styles/j-components.css +1 -1
  5. package/components/atoms/JEditor.vue.cjs +1 -1
  6. package/components/atoms/JEditor.vue.js +2 -2
  7. package/components/atoms/JEditor.vue2.cjs +1 -1
  8. package/components/atoms/JEditor.vue2.cjs.map +1 -1
  9. package/components/atoms/JEditor.vue2.js +1 -0
  10. package/components/atoms/JEditor.vue2.js.map +1 -1
  11. package/components/atoms/JPreview.vue.cjs +1 -1
  12. package/components/atoms/JPreview.vue.js +1 -1
  13. package/components/atoms/JPreview.vue2.cjs +1 -1
  14. package/components/atoms/JPreview.vue2.cjs.map +1 -1
  15. package/components/atoms/JPreview.vue2.js +2 -1
  16. package/components/atoms/JPreview.vue2.js.map +1 -1
  17. package/components/molecules/JFormField.vue.cjs +1 -1
  18. package/components/molecules/JFormField.vue.cjs.map +1 -1
  19. package/components/molecules/JFormField.vue.js +1 -1
  20. package/components/molecules/JFormField.vue.js.map +1 -1
  21. package/components/organisms/JFilterBar.vue.cjs +2 -0
  22. package/components/organisms/JFilterBar.vue.cjs.map +1 -0
  23. package/components/organisms/JFilterBar.vue.js +51 -0
  24. package/components/organisms/JFilterBar.vue.js.map +1 -0
  25. package/components/organisms/JFilterBar.vue2.cjs +2 -0
  26. package/components/organisms/JFilterBar.vue2.cjs.map +1 -0
  27. package/components/organisms/JFilterBar.vue2.js +5 -0
  28. package/components/organisms/JFilterBar.vue2.js.map +1 -0
  29. package/components/organisms/JSidebarSimple/JDynamicMenuItem.vue.cjs +1 -1
  30. package/components/organisms/JSidebarSimple/JDynamicMenuItem.vue.cjs.map +1 -1
  31. package/components/organisms/JSidebarSimple/JDynamicMenuItem.vue.js +45 -40
  32. package/components/organisms/JSidebarSimple/JDynamicMenuItem.vue.js.map +1 -1
  33. package/components/organisms/JSidebarSimple.vue.cjs +1 -1
  34. package/components/organisms/JSidebarSimple.vue.js +2 -2
  35. package/components/organisms/JSidebarSimple.vue2.cjs +1 -1
  36. package/components/organisms/JSidebarSimple.vue2.cjs.map +1 -1
  37. package/components/organisms/JSidebarSimple.vue2.js +40 -70
  38. package/components/organisms/JSidebarSimple.vue2.js.map +1 -1
  39. package/components/organisms/JTree.vue.cjs +2 -0
  40. package/components/organisms/JTree.vue.cjs.map +1 -0
  41. package/components/organisms/JTree.vue.js +83 -0
  42. package/components/organisms/JTree.vue.js.map +1 -0
  43. package/components/organisms/JTree.vue2.cjs +2 -0
  44. package/components/organisms/JTree.vue2.cjs.map +1 -0
  45. package/components/organisms/JTree.vue2.js +5 -0
  46. package/components/organisms/JTree.vue2.js.map +1 -0
  47. package/index.cjs +1 -1
  48. package/index.js +37 -33
  49. package/lib/menu-utils.cjs +2 -0
  50. package/lib/menu-utils.cjs.map +1 -0
  51. package/lib/menu-utils.js +33 -0
  52. package/lib/menu-utils.js.map +1 -0
  53. package/package.json +1 -1
  54. package/types/index.d.ts +156 -34
  55. package/assets/jwms-portal-frontend-D8DdrheA.css +0 -1
@@ -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-8cd2903a"]]);exports.default=u;
6
+ };,u=t(e.default,[["__scopeId","data-v-daf6a5db"]]);exports.default=u;
7
7
  //# sourceMappingURL=JEditor.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-8cd2903a"]]);
9
+ const f = /* @__PURE__ */ t(o, [["__scopeId", "data-v-daf6a5db"]]);
10
10
  export {
11
- p as default
11
+ f as default
12
12
  };
13
13
  //# sourceMappingURL=JEditor.vue.js.map
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const l=require("vue"),i=require("md-editor-v3");;/* empty css */const s=l.defineComponent({__name:"JEditor",props:{modelValue:{default:""},placeholder:{default:"마크다운을 입력하세요..."},disabled:{type:Boolean,default:!1},readonly:{type:Boolean,default:!1},height:{default:500},theme:{default:"light"},class:{}},emits:["update:modelValue","change","save"],setup(e,{emit:d}){const o=e,t=d,n=a=>{t("update:modelValue",a),t("change",a)},r=a=>{t("save",a)};return(a,u)=>(l.openBlock(),l.createElementBlock("div",{class:l.normalizeClass(["j-editor-wrapper",o.class])},[l.createVNode(l.unref(i.MdEditor),{"model-value":e.modelValue,placeholder:e.placeholder,disabled:e.disabled,readonly:e.readonly,"editor-class-name":"j-editor","preview-class-name":"j-editor-preview",height:e.height,theme:e.theme,"onUpdate:modelValue":n,onOnSave:r},null,8,["model-value","placeholder","disabled","readonly","height","theme"])],2))}});exports.default=s;
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const a=require("vue"),i=require("md-editor-v3");;/* empty css */const u=a.defineComponent({__name:"JEditor",props:{modelValue:{default:""},placeholder:{default:"마크다운을 입력하세요..."},disabled:{type:Boolean,default:!1},readonly:{type:Boolean,default:!1},height:{default:500},theme:{default:"light"},class:{}},emits:["update:modelValue","change","save"],setup(e,{emit:d}){const o=e,t=d,n=l=>{t("update:modelValue",l),t("change",l)},r=l=>{t("save",l)};return(l,s)=>(a.openBlock(),a.createElementBlock("div",{class:a.normalizeClass(["j-editor-wrapper",o.class])},[a.createVNode(a.unref(i.MdEditor),{"model-value":e.modelValue,placeholder:e.placeholder,disabled:e.disabled,readonly:e.readonly,"editor-class-name":"j-editor","preview-class-name":"j-editor-preview",height:e.height,theme:e.theme,language:"en-US","onUpdate:modelValue":n,onOnSave:r},null,8,["model-value","placeholder","disabled","readonly","height","theme"])],2))}});exports.default=u;
2
2
  //# sourceMappingURL=JEditor.vue2.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"JEditor.vue2.cjs","sources":["../../../../src/components/atoms/JEditor.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport { computed } from 'vue'\r\nimport { MdEditor } from 'md-editor-v3'\r\nimport 'md-editor-v3/lib/style.css'\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n /** 마크다운 내용 (v-model) */\r\n modelValue?: string\r\n /** 플레이스홀더 텍스트 */\r\n placeholder?: string\r\n /** 비활성화 상태 */\r\n disabled?: boolean\r\n /** 읽기 전용 상태 */\r\n readonly?: boolean\r\n /** 에디터 높이 */\r\n height?: string | number\r\n /** 테마 (light/dark) */\r\n theme?: 'light' | 'dark'\r\n /** 추가 CSS 클래스 */\r\n class?: string\r\n }>(),\r\n {\r\n modelValue: '',\r\n placeholder: '마크다운을 입력하세요...',\r\n disabled: false,\r\n readonly: false,\r\n height: 500,\r\n theme: 'light',\r\n },\r\n)\r\n\r\nconst emit = defineEmits<{\r\n 'update:modelValue': [value: string]\r\n 'change': [value: string]\r\n 'save': [value: string]\r\n}>()\r\n\r\nconst handleChange = (value: string) => {\r\n emit('update:modelValue', value)\r\n emit('change', value)\r\n}\r\n\r\nconst handleSave = (value: string) => {\r\n emit('save', value)\r\n}\r\n</script>\r\n\r\n<template>\r\n <div :class=\"['j-editor-wrapper', props.class]\">\r\n <MdEditor\r\n :model-value=\"modelValue\"\r\n :placeholder=\"placeholder\"\r\n :disabled=\"disabled\"\r\n :readonly=\"readonly\"\r\n :editor-class-name=\"'j-editor'\"\r\n :preview-class-name=\"'j-editor-preview'\"\r\n :height=\"height\"\r\n :theme=\"theme\"\r\n @update:model-value=\"handleChange\"\r\n @on-save=\"handleSave\"\r\n />\r\n </div>\r\n</template>\r\n\r\n<style scoped>\r\n.j-editor-wrapper {\r\n @apply w-full;\r\n}\r\n</style>\r\n"],"names":["props","__props","emit","__emit","handleChange","value","handleSave","_createElementBlock","_normalizeClass","_createVNode","_unref","MdEditor"],"mappings":"0kBAKA,MAAMA,EAAQC,EA2BRC,EAAOC,EAMPC,EAAgBC,GAAkB,CACtCH,EAAK,oBAAqBG,CAAK,EAC/BH,EAAK,SAAUG,CAAK,CACtB,EAEMC,EAAcD,GAAkB,CACpCH,EAAK,OAAQG,CAAK,CACpB,8BAIEE,EAAAA,mBAaM,MAAA,CAbA,MAAKC,EAAAA,eAAA,CAAA,mBAAuBR,EAAM,KAAK,CAAA,CAAA,GAC3CS,cAWEC,EAAAA,MAAAC,EAAAA,QAAA,EAAA,CAVC,cAAaV,EAAA,WACb,YAAaA,EAAA,YACb,SAAUA,EAAA,SACV,SAAUA,EAAA,SACV,oBAAmB,WACnB,qBAAoB,mBACpB,OAAQA,EAAA,OACR,MAAOA,EAAA,MACP,sBAAoBG,EACpB,SAASE,CAAA"}
1
+ {"version":3,"file":"JEditor.vue2.cjs","sources":["../../../../src/components/atoms/JEditor.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport { computed } from 'vue'\r\nimport { MdEditor } from 'md-editor-v3'\r\nimport 'md-editor-v3/lib/style.css'\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n /** 마크다운 내용 (v-model) */\r\n modelValue?: string\r\n /** 플레이스홀더 텍스트 */\r\n placeholder?: string\r\n /** 비활성화 상태 */\r\n disabled?: boolean\r\n /** 읽기 전용 상태 */\r\n readonly?: boolean\r\n /** 에디터 높이 */\r\n height?: string | number\r\n /** 테마 (light/dark) */\r\n theme?: 'light' | 'dark'\r\n /** 추가 CSS 클래스 */\r\n class?: string\r\n }>(),\r\n {\r\n modelValue: '',\r\n placeholder: '마크다운을 입력하세요...',\r\n disabled: false,\r\n readonly: false,\r\n height: 500,\r\n theme: 'light',\r\n },\r\n)\r\n\r\nconst emit = defineEmits<{\r\n 'update:modelValue': [value: string]\r\n 'change': [value: string]\r\n 'save': [value: string]\r\n}>()\r\n\r\nconst handleChange = (value: string) => {\r\n emit('update:modelValue', value)\r\n emit('change', value)\r\n}\r\n\r\nconst handleSave = (value: string) => {\r\n emit('save', value)\r\n}\r\n</script>\r\n\r\n<template>\r\n <div :class=\"['j-editor-wrapper', props.class]\">\r\n <MdEditor\r\n :model-value=\"modelValue\"\r\n :placeholder=\"placeholder\"\r\n :disabled=\"disabled\"\r\n :readonly=\"readonly\"\r\n :editor-class-name=\"'j-editor'\"\r\n :preview-class-name=\"'j-editor-preview'\"\r\n :height=\"height\"\r\n :theme=\"theme\"\r\n language=\"en-US\"\r\n @update:model-value=\"handleChange\"\r\n @on-save=\"handleSave\"\r\n />\r\n </div>\r\n</template>\r\n\r\n<style scoped>\r\n.j-editor-wrapper {\r\n @apply w-full;\r\n}\r\n</style>\r\n"],"names":["props","__props","emit","__emit","handleChange","value","handleSave","_createElementBlock","_normalizeClass","_createVNode","_unref","MdEditor"],"mappings":"0kBAKA,MAAMA,EAAQC,EA2BRC,EAAOC,EAMPC,EAAgBC,GAAkB,CACtCH,EAAK,oBAAqBG,CAAK,EAC/BH,EAAK,SAAUG,CAAK,CACtB,EAEMC,EAAcD,GAAkB,CACpCH,EAAK,OAAQG,CAAK,CACpB,8BAIEE,EAAAA,mBAcM,MAAA,CAdA,MAAKC,EAAAA,eAAA,CAAA,mBAAuBR,EAAM,KAAK,CAAA,CAAA,GAC3CS,cAYEC,EAAAA,MAAAC,EAAAA,QAAA,EAAA,CAXC,cAAaV,EAAA,WACb,YAAaA,EAAA,YACb,SAAUA,EAAA,SACV,SAAUA,EAAA,SACV,oBAAmB,WACnB,qBAAoB,mBACpB,OAAQA,EAAA,OACR,MAAOA,EAAA,MACR,SAAS,QACR,sBAAoBG,EACpB,SAASE,CAAA"}
@@ -31,6 +31,7 @@ const y = /* @__PURE__ */ r({
31
31
  "preview-class-name": "j-editor-preview",
32
32
  height: e.height,
33
33
  theme: e.theme,
34
+ language: "en-US",
34
35
  "onUpdate:modelValue": o,
35
36
  onOnSave: n
36
37
  }, null, 8, ["model-value", "placeholder", "disabled", "readonly", "height", "theme"])
@@ -1 +1 @@
1
- {"version":3,"file":"JEditor.vue2.js","sources":["../../../../src/components/atoms/JEditor.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport { computed } from 'vue'\r\nimport { MdEditor } from 'md-editor-v3'\r\nimport 'md-editor-v3/lib/style.css'\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n /** 마크다운 내용 (v-model) */\r\n modelValue?: string\r\n /** 플레이스홀더 텍스트 */\r\n placeholder?: string\r\n /** 비활성화 상태 */\r\n disabled?: boolean\r\n /** 읽기 전용 상태 */\r\n readonly?: boolean\r\n /** 에디터 높이 */\r\n height?: string | number\r\n /** 테마 (light/dark) */\r\n theme?: 'light' | 'dark'\r\n /** 추가 CSS 클래스 */\r\n class?: string\r\n }>(),\r\n {\r\n modelValue: '',\r\n placeholder: '마크다운을 입력하세요...',\r\n disabled: false,\r\n readonly: false,\r\n height: 500,\r\n theme: 'light',\r\n },\r\n)\r\n\r\nconst emit = defineEmits<{\r\n 'update:modelValue': [value: string]\r\n 'change': [value: string]\r\n 'save': [value: string]\r\n}>()\r\n\r\nconst handleChange = (value: string) => {\r\n emit('update:modelValue', value)\r\n emit('change', value)\r\n}\r\n\r\nconst handleSave = (value: string) => {\r\n emit('save', value)\r\n}\r\n</script>\r\n\r\n<template>\r\n <div :class=\"['j-editor-wrapper', props.class]\">\r\n <MdEditor\r\n :model-value=\"modelValue\"\r\n :placeholder=\"placeholder\"\r\n :disabled=\"disabled\"\r\n :readonly=\"readonly\"\r\n :editor-class-name=\"'j-editor'\"\r\n :preview-class-name=\"'j-editor-preview'\"\r\n :height=\"height\"\r\n :theme=\"theme\"\r\n @update:model-value=\"handleChange\"\r\n @on-save=\"handleSave\"\r\n />\r\n </div>\r\n</template>\r\n\r\n<style scoped>\r\n.j-editor-wrapper {\r\n @apply w-full;\r\n}\r\n</style>\r\n"],"names":["props","__props","emit","__emit","handleChange","value","handleSave","_createElementBlock","_normalizeClass","_createVNode","_unref","MdEditor"],"mappings":";;;;;;;;;;;;;;;;AAKA,UAAMA,IAAQC,GA2BRC,IAAOC,GAMPC,IAAe,CAACC,MAAkB;AACtC,MAAAH,EAAK,qBAAqBG,CAAK,GAC/BH,EAAK,UAAUG,CAAK;AAAA,IACtB,GAEMC,IAAa,CAACD,MAAkB;AACpC,MAAAH,EAAK,QAAQG,CAAK;AAAA,IACpB;2BAIEE,EAaM,OAAA;AAAA,MAbA,OAAKC,EAAA,CAAA,oBAAuBR,EAAM,KAAK,CAAA;AAAA,IAAA;MAC3CS,EAWEC,EAAAC,CAAA,GAAA;AAAA,QAVC,eAAaV,EAAA;AAAA,QACb,aAAaA,EAAA;AAAA,QACb,UAAUA,EAAA;AAAA,QACV,UAAUA,EAAA;AAAA,QACV,qBAAmB;AAAA,QACnB,sBAAoB;AAAA,QACpB,QAAQA,EAAA;AAAA,QACR,OAAOA,EAAA;AAAA,QACP,uBAAoBG;AAAA,QACpB,UAASE;AAAA,MAAA;;;;"}
1
+ {"version":3,"file":"JEditor.vue2.js","sources":["../../../../src/components/atoms/JEditor.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport { computed } from 'vue'\r\nimport { MdEditor } from 'md-editor-v3'\r\nimport 'md-editor-v3/lib/style.css'\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n /** 마크다운 내용 (v-model) */\r\n modelValue?: string\r\n /** 플레이스홀더 텍스트 */\r\n placeholder?: string\r\n /** 비활성화 상태 */\r\n disabled?: boolean\r\n /** 읽기 전용 상태 */\r\n readonly?: boolean\r\n /** 에디터 높이 */\r\n height?: string | number\r\n /** 테마 (light/dark) */\r\n theme?: 'light' | 'dark'\r\n /** 추가 CSS 클래스 */\r\n class?: string\r\n }>(),\r\n {\r\n modelValue: '',\r\n placeholder: '마크다운을 입력하세요...',\r\n disabled: false,\r\n readonly: false,\r\n height: 500,\r\n theme: 'light',\r\n },\r\n)\r\n\r\nconst emit = defineEmits<{\r\n 'update:modelValue': [value: string]\r\n 'change': [value: string]\r\n 'save': [value: string]\r\n}>()\r\n\r\nconst handleChange = (value: string) => {\r\n emit('update:modelValue', value)\r\n emit('change', value)\r\n}\r\n\r\nconst handleSave = (value: string) => {\r\n emit('save', value)\r\n}\r\n</script>\r\n\r\n<template>\r\n <div :class=\"['j-editor-wrapper', props.class]\">\r\n <MdEditor\r\n :model-value=\"modelValue\"\r\n :placeholder=\"placeholder\"\r\n :disabled=\"disabled\"\r\n :readonly=\"readonly\"\r\n :editor-class-name=\"'j-editor'\"\r\n :preview-class-name=\"'j-editor-preview'\"\r\n :height=\"height\"\r\n :theme=\"theme\"\r\n language=\"en-US\"\r\n @update:model-value=\"handleChange\"\r\n @on-save=\"handleSave\"\r\n />\r\n </div>\r\n</template>\r\n\r\n<style scoped>\r\n.j-editor-wrapper {\r\n @apply w-full;\r\n}\r\n</style>\r\n"],"names":["props","__props","emit","__emit","handleChange","value","handleSave","_createElementBlock","_normalizeClass","_createVNode","_unref","MdEditor"],"mappings":";;;;;;;;;;;;;;;;AAKA,UAAMA,IAAQC,GA2BRC,IAAOC,GAMPC,IAAe,CAACC,MAAkB;AACtC,MAAAH,EAAK,qBAAqBG,CAAK,GAC/BH,EAAK,UAAUG,CAAK;AAAA,IACtB,GAEMC,IAAa,CAACD,MAAkB;AACpC,MAAAH,EAAK,QAAQG,CAAK;AAAA,IACpB;2BAIEE,EAcM,OAAA;AAAA,MAdA,OAAKC,EAAA,CAAA,oBAAuBR,EAAM,KAAK,CAAA;AAAA,IAAA;MAC3CS,EAYEC,EAAAC,CAAA,GAAA;AAAA,QAXC,eAAaV,EAAA;AAAA,QACb,aAAaA,EAAA;AAAA,QACb,UAAUA,EAAA;AAAA,QACV,UAAUA,EAAA;AAAA,QACV,qBAAmB;AAAA,QACnB,sBAAoB;AAAA,QACpB,QAAQA,EAAA;AAAA,QACR,OAAOA,EAAA;AAAA,QACR,UAAS;AAAA,QACR,uBAAoBG;AAAA,QACpB,UAASE;AAAA,MAAA;;;;"}
@@ -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-5129bb96"]]);exports.default=u;
6
+ };,u=t(e.default,[["__scopeId","data-v-b07e3dcb"]]);exports.default=u;
7
7
  //# sourceMappingURL=JPreview.vue.cjs.map
@@ -6,7 +6,7 @@ const r = (r_comp, r_opts) => {
6
6
  r_merged[r_key] = r_val;
7
7
  return r_merged;
8
8
  };
9
- const p = /* @__PURE__ */ r(o, [["__scopeId", "data-v-5129bb96"]]);
9
+ const p = /* @__PURE__ */ r(o, [["__scopeId", "data-v-b07e3dcb"]]);
10
10
  export {
11
11
  p as default
12
12
  };
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),o=require("md-editor-v3");;/* empty css */const m=require("dompurify"),c=["innerHTML"],u=e.defineComponent({__name:"JPreview",props:{modelValue:{default:""},theme:{default:"light"},class:{}},setup(l){const r=l;function i(s){const t=s.trim();return t.startsWith("<!DOCTYPE html")||t.startsWith("<!doctype html")||t.startsWith("<!DOCTYPE HTML")||t.startsWith("<!DOCTYPE Html")||t.startsWith("<html")||t.startsWith("<HTML")||t.startsWith("<Html")?"html":"markdown"}const a=e.computed(()=>i(r.modelValue||"")),n=e.computed(()=>a.value==="html"?m.sanitize(r.modelValue||"",{ALLOWED_TAGS:["p","br","strong","em","u","s","h1","h2","h3","h4","h5","h6","ul","ol","li","blockquote","pre","code","a","img","table","thead","tbody","tr","th","td","div","span","hr","blockquote","del","ins","sub","sup","kbd","mark","abbr","dfn","cite","q","samp","var","time","b","i","small","big","center","font","strike","tt","html","head","body","title","meta","link","style","script","noscript"],ALLOWED_ATTR:["href","src","alt","title","width","height","class","id","style","target","rel","type","charset","name","content","http-equiv","lang"],ALLOW_DATA_ATTR:!1}):"");return(s,t)=>(e.openBlock(),e.createElementBlock("div",{class:e.normalizeClass(["j-preview-wrapper",r.class])},[a.value==="html"?(e.openBlock(),e.createElementBlock("div",{key:0,class:e.normalizeClass(["j-preview-html",l.theme==="dark"?"dark":""]),innerHTML:n.value},null,10,c)):(e.openBlock(),e.createBlock(e.unref(o.MdPreview),{key:1,"model-value":l.modelValue,theme:l.theme,"preview-class-name":"j-preview-markdown"},null,8,["model-value","theme"]))],2))}});exports.default=u;
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),o=require("md-editor-v3");;/* empty css */const m=require("dompurify"),c=["innerHTML"],u=e.defineComponent({__name:"JPreview",props:{modelValue:{default:""},theme:{default:"light"},class:{}},setup(l){const r=l;function n(s){const t=s.trim();return t.startsWith("<!DOCTYPE html")||t.startsWith("<!doctype html")||t.startsWith("<!DOCTYPE HTML")||t.startsWith("<!DOCTYPE Html")||t.startsWith("<html")||t.startsWith("<HTML")||t.startsWith("<Html")?"html":"markdown"}const a=e.computed(()=>n(r.modelValue||"")),i=e.computed(()=>a.value==="html"?m.sanitize(r.modelValue||"",{ALLOWED_TAGS:["p","br","strong","em","u","s","h1","h2","h3","h4","h5","h6","ul","ol","li","blockquote","pre","code","a","img","table","thead","tbody","tr","th","td","div","span","hr","blockquote","del","ins","sub","sup","kbd","mark","abbr","dfn","cite","q","samp","var","time","b","i","small","big","center","font","strike","tt","html","head","body","title","meta","link","style","script","noscript"],ALLOWED_ATTR:["href","src","alt","title","width","height","class","id","style","target","rel","type","charset","name","content","http-equiv","lang"],ALLOW_DATA_ATTR:!1}):"");return(s,t)=>(e.openBlock(),e.createElementBlock("div",{class:e.normalizeClass(["j-preview-wrapper",r.class])},[a.value==="html"?(e.openBlock(),e.createElementBlock("div",{key:0,class:e.normalizeClass(["j-preview-html",l.theme==="dark"?"dark":""]),innerHTML:i.value},null,10,c)):(e.openBlock(),e.createBlock(e.unref(o.MdPreview),{key:1,"model-value":l.modelValue,theme:l.theme,"preview-class-name":"j-preview-markdown",language:"en-US"},null,8,["model-value","theme"]))],2))}});exports.default=u;
2
2
  //# sourceMappingURL=JPreview.vue2.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"JPreview.vue2.cjs","sources":["../../../../src/components/atoms/JPreview.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport { computed } from 'vue'\r\nimport { MdPreview } from 'md-editor-v3'\r\nimport 'md-editor-v3/lib/style.css'\r\nimport DOMPurify from 'dompurify'\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n /** 마크다운 또는 HTML 내용 */\r\n modelValue?: string\r\n /** 테마 (light/dark) */\r\n theme?: 'light' | 'dark'\r\n /** 추가 CSS 클래스 */\r\n class?: string\r\n }>(),\r\n {\r\n modelValue: '',\r\n theme: 'light',\r\n },\r\n)\r\n\r\n/**\r\n * 콘텐츠 타입 자동 감지\r\n */\r\nfunction detectContentType(content: string): 'markdown' | 'html' {\r\n const trimmed = content.trim()\r\n \r\n // HTML 문서 시작 태그 확인\r\n if (\r\n trimmed.startsWith('<!DOCTYPE html') ||\r\n trimmed.startsWith('<!doctype html') ||\r\n trimmed.startsWith('<!DOCTYPE HTML') ||\r\n trimmed.startsWith('<!DOCTYPE Html') ||\r\n trimmed.startsWith('<html') ||\r\n trimmed.startsWith('<HTML') ||\r\n trimmed.startsWith('<Html')\r\n ) {\r\n return 'html'\r\n }\r\n \r\n return 'markdown'\r\n}\r\n\r\nconst contentType = computed(() => detectContentType(props.modelValue || ''))\r\n\r\nconst sanitizedHtml = computed(() => {\r\n if (contentType.value === 'html') {\r\n return DOMPurify.sanitize(props.modelValue || '', {\r\n ALLOWED_TAGS: [\r\n 'p', 'br', 'strong', 'em', 'u', 's', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',\r\n 'ul', 'ol', 'li', 'blockquote', 'pre', 'code', 'a', 'img', 'table', 'thead',\r\n 'tbody', 'tr', 'th', 'td', 'div', 'span', 'hr', 'blockquote', 'del', 'ins',\r\n 'sub', 'sup', 'kbd', 'mark', 'abbr', 'dfn', 'cite', 'q', 'samp', 'var',\r\n 'time', 'b', 'i', 'small', 'big', 'center', 'font', 'strike', 'tt',\r\n 'html', 'head', 'body', 'title', 'meta', 'link', 'style', 'script', 'noscript'\r\n ],\r\n ALLOWED_ATTR: [\r\n 'href', 'src', 'alt', 'title', 'width', 'height', 'class', 'id', 'style',\r\n 'target', 'rel', 'type', 'charset', 'name', 'content', 'http-equiv', 'lang'\r\n ],\r\n ALLOW_DATA_ATTR: false,\r\n })\r\n }\r\n return ''\r\n})\r\n</script>\r\n\r\n<template>\r\n <div :class=\"['j-preview-wrapper', props.class]\">\r\n <!-- HTML 렌더링 -->\r\n <div\r\n v-if=\"contentType === 'html'\"\r\n :class=\"['j-preview-html', theme === 'dark' ? 'dark' : '']\"\r\n v-html=\"sanitizedHtml\"\r\n />\r\n \r\n <!-- 마크다운 렌더링 -->\r\n <MdPreview\r\n v-else\r\n :model-value=\"modelValue\"\r\n :theme=\"theme\"\r\n :preview-class-name=\"'j-preview-markdown'\"\r\n />\r\n </div>\r\n</template>\r\n\r\n<style scoped>\r\n.j-preview-wrapper {\r\n @apply w-full;\r\n}\r\n\r\n.j-preview-html {\r\n @apply prose prose-slate max-w-none;\r\n}\r\n\r\n.j-preview-html.dark {\r\n @apply prose-invert;\r\n}\r\n\r\n:deep(.j-preview-markdown) {\r\n @apply w-full;\r\n}\r\n</style>\r\n"],"names":["props","__props","detectContentType","content","trimmed","contentType","computed","sanitizedHtml","DOMPurify","_createElementBlock","_normalizeClass","_createBlock","_unref","MdPreview"],"mappings":"2bAMA,MAAMA,EAAQC,EAkBd,SAASC,EAAkBC,EAAsC,CAC/D,MAAMC,EAAUD,EAAQ,KAAA,EAGxB,OACEC,EAAQ,WAAW,gBAAgB,GACnCA,EAAQ,WAAW,gBAAgB,GACnCA,EAAQ,WAAW,gBAAgB,GACnCA,EAAQ,WAAW,gBAAgB,GACnCA,EAAQ,WAAW,OAAO,GAC1BA,EAAQ,WAAW,OAAO,GAC1BA,EAAQ,WAAW,OAAO,EAEnB,OAGF,UACT,CAEA,MAAMC,EAAcC,EAAAA,SAAS,IAAMJ,EAAkBF,EAAM,YAAc,EAAE,CAAC,EAEtEO,EAAgBD,EAAAA,SAAS,IACzBD,EAAY,QAAU,OACjBG,EAAU,SAASR,EAAM,YAAc,GAAI,CAChD,aAAc,CACZ,IAAK,KAAM,SAAU,KAAM,IAAK,IAAK,KAAM,KAAM,KAAM,KAAM,KAAM,KACnE,KAAM,KAAM,KAAM,aAAc,MAAO,OAAQ,IAAK,MAAO,QAAS,QACpE,QAAS,KAAM,KAAM,KAAM,MAAO,OAAQ,KAAM,aAAc,MAAO,MACrE,MAAO,MAAO,MAAO,OAAQ,OAAQ,MAAO,OAAQ,IAAK,OAAQ,MACjE,OAAQ,IAAK,IAAK,QAAS,MAAO,SAAU,OAAQ,SAAU,KAC9D,OAAQ,OAAQ,OAAQ,QAAS,OAAQ,OAAQ,QAAS,SAAU,UAAA,EAEtE,aAAc,CACZ,OAAQ,MAAO,MAAO,QAAS,QAAS,SAAU,QAAS,KAAM,QACjE,SAAU,MAAO,OAAQ,UAAW,OAAQ,UAAW,aAAc,MAAA,EAEvE,gBAAiB,EAAA,CAClB,EAEI,EACR,8BAICS,EAAAA,mBAeM,MAAA,CAfA,MAAKC,EAAAA,eAAA,CAAA,oBAAwBV,EAAM,KAAK,CAAA,CAAA,GAGpCK,EAAA,QAAW,sBADnBI,EAAAA,mBAIE,MAAA,OAFC,yCAA0BR,EAAA,QAAK,OAAA,OAAA,EAAA,CAAA,EAChC,UAAQM,EAAA,KAAA,6BAIVI,EAAAA,YAKEC,QAAAC,EAAAA,SAAA,EAAA,OAHC,cAAaZ,EAAA,WACb,MAAOA,EAAA,MACP,qBAAoB,oBAAA"}
1
+ {"version":3,"file":"JPreview.vue2.cjs","sources":["../../../../src/components/atoms/JPreview.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport { computed } from 'vue'\r\nimport { MdPreview } from 'md-editor-v3'\r\nimport 'md-editor-v3/lib/style.css'\r\nimport DOMPurify from 'dompurify'\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n /** 마크다운 또는 HTML 내용 */\r\n modelValue?: string\r\n /** 테마 (light/dark) */\r\n theme?: 'light' | 'dark'\r\n /** 추가 CSS 클래스 */\r\n class?: string\r\n }>(),\r\n {\r\n modelValue: '',\r\n theme: 'light',\r\n },\r\n)\r\n\r\n/**\r\n * 콘텐츠 타입 자동 감지\r\n */\r\nfunction detectContentType(content: string): 'markdown' | 'html' {\r\n const trimmed = content.trim()\r\n \r\n // HTML 문서 시작 태그 확인\r\n if (\r\n trimmed.startsWith('<!DOCTYPE html') ||\r\n trimmed.startsWith('<!doctype html') ||\r\n trimmed.startsWith('<!DOCTYPE HTML') ||\r\n trimmed.startsWith('<!DOCTYPE Html') ||\r\n trimmed.startsWith('<html') ||\r\n trimmed.startsWith('<HTML') ||\r\n trimmed.startsWith('<Html')\r\n ) {\r\n return 'html'\r\n }\r\n \r\n return 'markdown'\r\n}\r\n\r\nconst contentType = computed(() => detectContentType(props.modelValue || ''))\r\n\r\nconst sanitizedHtml = computed(() => {\r\n if (contentType.value === 'html') {\r\n return DOMPurify.sanitize(props.modelValue || '', {\r\n ALLOWED_TAGS: [\r\n 'p', 'br', 'strong', 'em', 'u', 's', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',\r\n 'ul', 'ol', 'li', 'blockquote', 'pre', 'code', 'a', 'img', 'table', 'thead',\r\n 'tbody', 'tr', 'th', 'td', 'div', 'span', 'hr', 'blockquote', 'del', 'ins',\r\n 'sub', 'sup', 'kbd', 'mark', 'abbr', 'dfn', 'cite', 'q', 'samp', 'var',\r\n 'time', 'b', 'i', 'small', 'big', 'center', 'font', 'strike', 'tt',\r\n 'html', 'head', 'body', 'title', 'meta', 'link', 'style', 'script', 'noscript'\r\n ],\r\n ALLOWED_ATTR: [\r\n 'href', 'src', 'alt', 'title', 'width', 'height', 'class', 'id', 'style',\r\n 'target', 'rel', 'type', 'charset', 'name', 'content', 'http-equiv', 'lang'\r\n ],\r\n ALLOW_DATA_ATTR: false,\r\n })\r\n }\r\n return ''\r\n})\r\n</script>\r\n\r\n<template>\r\n <div :class=\"['j-preview-wrapper', props.class]\">\r\n <!-- HTML 렌더링 -->\r\n <div\r\n v-if=\"contentType === 'html'\"\r\n :class=\"['j-preview-html', theme === 'dark' ? 'dark' : '']\"\r\n v-html=\"sanitizedHtml\"\r\n />\r\n \r\n <!-- 마크다운 렌더링 -->\r\n <MdPreview\r\n v-else\r\n :model-value=\"modelValue\"\r\n :theme=\"theme\"\r\n :preview-class-name=\"'j-preview-markdown'\"\r\n language=\"en-US\"\r\n />\r\n </div>\r\n</template>\r\n\r\n<style scoped>\r\n.j-preview-wrapper {\r\n @apply w-full;\r\n}\r\n\r\n.j-preview-html {\r\n @apply prose prose-slate max-w-none;\r\n}\r\n\r\n.j-preview-html.dark {\r\n @apply prose-invert;\r\n}\r\n\r\n:deep(.j-preview-markdown) {\r\n @apply w-full;\r\n}\r\n</style>\r\n"],"names":["props","__props","detectContentType","content","trimmed","contentType","computed","sanitizedHtml","DOMPurify","_createElementBlock","_normalizeClass","_createBlock","_unref","MdPreview"],"mappings":"2bAMA,MAAMA,EAAQC,EAkBd,SAASC,EAAkBC,EAAsC,CAC/D,MAAMC,EAAUD,EAAQ,KAAA,EAGxB,OACEC,EAAQ,WAAW,gBAAgB,GACnCA,EAAQ,WAAW,gBAAgB,GACnCA,EAAQ,WAAW,gBAAgB,GACnCA,EAAQ,WAAW,gBAAgB,GACnCA,EAAQ,WAAW,OAAO,GAC1BA,EAAQ,WAAW,OAAO,GAC1BA,EAAQ,WAAW,OAAO,EAEnB,OAGF,UACT,CAEA,MAAMC,EAAcC,EAAAA,SAAS,IAAMJ,EAAkBF,EAAM,YAAc,EAAE,CAAC,EAEtEO,EAAgBD,EAAAA,SAAS,IACzBD,EAAY,QAAU,OACjBG,EAAU,SAASR,EAAM,YAAc,GAAI,CAChD,aAAc,CACZ,IAAK,KAAM,SAAU,KAAM,IAAK,IAAK,KAAM,KAAM,KAAM,KAAM,KAAM,KACnE,KAAM,KAAM,KAAM,aAAc,MAAO,OAAQ,IAAK,MAAO,QAAS,QACpE,QAAS,KAAM,KAAM,KAAM,MAAO,OAAQ,KAAM,aAAc,MAAO,MACrE,MAAO,MAAO,MAAO,OAAQ,OAAQ,MAAO,OAAQ,IAAK,OAAQ,MACjE,OAAQ,IAAK,IAAK,QAAS,MAAO,SAAU,OAAQ,SAAU,KAC9D,OAAQ,OAAQ,OAAQ,QAAS,OAAQ,OAAQ,QAAS,SAAU,UAAA,EAEtE,aAAc,CACZ,OAAQ,MAAO,MAAO,QAAS,QAAS,SAAU,QAAS,KAAM,QACjE,SAAU,MAAO,OAAQ,UAAW,OAAQ,UAAW,aAAc,MAAA,EAEvE,gBAAiB,EAAA,CAClB,EAEI,EACR,8BAICS,EAAAA,mBAgBM,MAAA,CAhBA,MAAKC,EAAAA,eAAA,CAAA,oBAAwBV,EAAM,KAAK,CAAA,CAAA,GAGpCK,EAAA,QAAW,sBADnBI,EAAAA,mBAIE,MAAA,OAFC,yCAA0BR,EAAA,QAAK,OAAA,OAAA,EAAA,CAAA,EAChC,UAAQM,EAAA,KAAA,6BAIVI,EAAAA,YAMEC,QAAAC,EAAAA,SAAA,EAAA,OAJC,cAAaZ,EAAA,WACb,MAAOA,EAAA,MACP,qBAAoB,qBACrB,SAAS,OAAA"}
@@ -110,7 +110,8 @@ const v = ["innerHTML"], w = /* @__PURE__ */ c({
110
110
  key: 1,
111
111
  "model-value": e.modelValue,
112
112
  theme: e.theme,
113
- "preview-class-name": "j-preview-markdown"
113
+ "preview-class-name": "j-preview-markdown",
114
+ language: "en-US"
114
115
  }, null, 8, ["model-value", "theme"]))
115
116
  ], 2));
116
117
  }
@@ -1 +1 @@
1
- {"version":3,"file":"JPreview.vue2.js","sources":["../../../../src/components/atoms/JPreview.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport { computed } from 'vue'\r\nimport { MdPreview } from 'md-editor-v3'\r\nimport 'md-editor-v3/lib/style.css'\r\nimport DOMPurify from 'dompurify'\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n /** 마크다운 또는 HTML 내용 */\r\n modelValue?: string\r\n /** 테마 (light/dark) */\r\n theme?: 'light' | 'dark'\r\n /** 추가 CSS 클래스 */\r\n class?: string\r\n }>(),\r\n {\r\n modelValue: '',\r\n theme: 'light',\r\n },\r\n)\r\n\r\n/**\r\n * 콘텐츠 타입 자동 감지\r\n */\r\nfunction detectContentType(content: string): 'markdown' | 'html' {\r\n const trimmed = content.trim()\r\n \r\n // HTML 문서 시작 태그 확인\r\n if (\r\n trimmed.startsWith('<!DOCTYPE html') ||\r\n trimmed.startsWith('<!doctype html') ||\r\n trimmed.startsWith('<!DOCTYPE HTML') ||\r\n trimmed.startsWith('<!DOCTYPE Html') ||\r\n trimmed.startsWith('<html') ||\r\n trimmed.startsWith('<HTML') ||\r\n trimmed.startsWith('<Html')\r\n ) {\r\n return 'html'\r\n }\r\n \r\n return 'markdown'\r\n}\r\n\r\nconst contentType = computed(() => detectContentType(props.modelValue || ''))\r\n\r\nconst sanitizedHtml = computed(() => {\r\n if (contentType.value === 'html') {\r\n return DOMPurify.sanitize(props.modelValue || '', {\r\n ALLOWED_TAGS: [\r\n 'p', 'br', 'strong', 'em', 'u', 's', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',\r\n 'ul', 'ol', 'li', 'blockquote', 'pre', 'code', 'a', 'img', 'table', 'thead',\r\n 'tbody', 'tr', 'th', 'td', 'div', 'span', 'hr', 'blockquote', 'del', 'ins',\r\n 'sub', 'sup', 'kbd', 'mark', 'abbr', 'dfn', 'cite', 'q', 'samp', 'var',\r\n 'time', 'b', 'i', 'small', 'big', 'center', 'font', 'strike', 'tt',\r\n 'html', 'head', 'body', 'title', 'meta', 'link', 'style', 'script', 'noscript'\r\n ],\r\n ALLOWED_ATTR: [\r\n 'href', 'src', 'alt', 'title', 'width', 'height', 'class', 'id', 'style',\r\n 'target', 'rel', 'type', 'charset', 'name', 'content', 'http-equiv', 'lang'\r\n ],\r\n ALLOW_DATA_ATTR: false,\r\n })\r\n }\r\n return ''\r\n})\r\n</script>\r\n\r\n<template>\r\n <div :class=\"['j-preview-wrapper', props.class]\">\r\n <!-- HTML 렌더링 -->\r\n <div\r\n v-if=\"contentType === 'html'\"\r\n :class=\"['j-preview-html', theme === 'dark' ? 'dark' : '']\"\r\n v-html=\"sanitizedHtml\"\r\n />\r\n \r\n <!-- 마크다운 렌더링 -->\r\n <MdPreview\r\n v-else\r\n :model-value=\"modelValue\"\r\n :theme=\"theme\"\r\n :preview-class-name=\"'j-preview-markdown'\"\r\n />\r\n </div>\r\n</template>\r\n\r\n<style scoped>\r\n.j-preview-wrapper {\r\n @apply w-full;\r\n}\r\n\r\n.j-preview-html {\r\n @apply prose prose-slate max-w-none;\r\n}\r\n\r\n.j-preview-html.dark {\r\n @apply prose-invert;\r\n}\r\n\r\n:deep(.j-preview-markdown) {\r\n @apply w-full;\r\n}\r\n</style>\r\n"],"names":["props","__props","detectContentType","content","trimmed","contentType","computed","sanitizedHtml","DOMPurify","_createElementBlock","_normalizeClass","_createBlock","_unref","MdPreview"],"mappings":";;;;;;;;;;;;AAMA,UAAMA,IAAQC;AAkBd,aAASC,EAAkBC,GAAsC;AAC/D,YAAMC,IAAUD,EAAQ,KAAA;AAGxB,aACEC,EAAQ,WAAW,gBAAgB,KACnCA,EAAQ,WAAW,gBAAgB,KACnCA,EAAQ,WAAW,gBAAgB,KACnCA,EAAQ,WAAW,gBAAgB,KACnCA,EAAQ,WAAW,OAAO,KAC1BA,EAAQ,WAAW,OAAO,KAC1BA,EAAQ,WAAW,OAAO,IAEnB,SAGF;AAAA,IACT;AAEA,UAAMC,IAAcC,EAAS,MAAMJ,EAAkBF,EAAM,cAAc,EAAE,CAAC,GAEtEO,IAAgBD,EAAS,MACzBD,EAAY,UAAU,SACjBG,EAAU,SAASR,EAAM,cAAc,IAAI;AAAA,MAChD,cAAc;AAAA,QACZ;AAAA,QAAK;AAAA,QAAM;AAAA,QAAU;AAAA,QAAM;AAAA,QAAK;AAAA,QAAK;AAAA,QAAM;AAAA,QAAM;AAAA,QAAM;AAAA,QAAM;AAAA,QAAM;AAAA,QACnE;AAAA,QAAM;AAAA,QAAM;AAAA,QAAM;AAAA,QAAc;AAAA,QAAO;AAAA,QAAQ;AAAA,QAAK;AAAA,QAAO;AAAA,QAAS;AAAA,QACpE;AAAA,QAAS;AAAA,QAAM;AAAA,QAAM;AAAA,QAAM;AAAA,QAAO;AAAA,QAAQ;AAAA,QAAM;AAAA,QAAc;AAAA,QAAO;AAAA,QACrE;AAAA,QAAO;AAAA,QAAO;AAAA,QAAO;AAAA,QAAQ;AAAA,QAAQ;AAAA,QAAO;AAAA,QAAQ;AAAA,QAAK;AAAA,QAAQ;AAAA,QACjE;AAAA,QAAQ;AAAA,QAAK;AAAA,QAAK;AAAA,QAAS;AAAA,QAAO;AAAA,QAAU;AAAA,QAAQ;AAAA,QAAU;AAAA,QAC9D;AAAA,QAAQ;AAAA,QAAQ;AAAA,QAAQ;AAAA,QAAS;AAAA,QAAQ;AAAA,QAAQ;AAAA,QAAS;AAAA,QAAU;AAAA,MAAA;AAAA,MAEtE,cAAc;AAAA,QACZ;AAAA,QAAQ;AAAA,QAAO;AAAA,QAAO;AAAA,QAAS;AAAA,QAAS;AAAA,QAAU;AAAA,QAAS;AAAA,QAAM;AAAA,QACjE;AAAA,QAAU;AAAA,QAAO;AAAA,QAAQ;AAAA,QAAW;AAAA,QAAQ;AAAA,QAAW;AAAA,QAAc;AAAA,MAAA;AAAA,MAEvE,iBAAiB;AAAA,IAAA,CAClB,IAEI,EACR;2BAICS,EAeM,OAAA;AAAA,MAfA,OAAKC,EAAA,CAAA,qBAAwBV,EAAM,KAAK,CAAA;AAAA,IAAA;MAGpCK,EAAA,UAAW,eADnBI,EAIE,OAAA;AAAA;QAFC,4BAA0BR,EAAA,UAAK,SAAA,SAAA,EAAA,CAAA;AAAA,QAChC,WAAQM,EAAA;AAAA,MAAA,yBAIVI,EAKEC,EAAAC,CAAA,GAAA;AAAA;QAHC,eAAaZ,EAAA;AAAA,QACb,OAAOA,EAAA;AAAA,QACP,sBAAoB;AAAA,MAAA;;;;"}
1
+ {"version":3,"file":"JPreview.vue2.js","sources":["../../../../src/components/atoms/JPreview.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport { computed } from 'vue'\r\nimport { MdPreview } from 'md-editor-v3'\r\nimport 'md-editor-v3/lib/style.css'\r\nimport DOMPurify from 'dompurify'\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n /** 마크다운 또는 HTML 내용 */\r\n modelValue?: string\r\n /** 테마 (light/dark) */\r\n theme?: 'light' | 'dark'\r\n /** 추가 CSS 클래스 */\r\n class?: string\r\n }>(),\r\n {\r\n modelValue: '',\r\n theme: 'light',\r\n },\r\n)\r\n\r\n/**\r\n * 콘텐츠 타입 자동 감지\r\n */\r\nfunction detectContentType(content: string): 'markdown' | 'html' {\r\n const trimmed = content.trim()\r\n \r\n // HTML 문서 시작 태그 확인\r\n if (\r\n trimmed.startsWith('<!DOCTYPE html') ||\r\n trimmed.startsWith('<!doctype html') ||\r\n trimmed.startsWith('<!DOCTYPE HTML') ||\r\n trimmed.startsWith('<!DOCTYPE Html') ||\r\n trimmed.startsWith('<html') ||\r\n trimmed.startsWith('<HTML') ||\r\n trimmed.startsWith('<Html')\r\n ) {\r\n return 'html'\r\n }\r\n \r\n return 'markdown'\r\n}\r\n\r\nconst contentType = computed(() => detectContentType(props.modelValue || ''))\r\n\r\nconst sanitizedHtml = computed(() => {\r\n if (contentType.value === 'html') {\r\n return DOMPurify.sanitize(props.modelValue || '', {\r\n ALLOWED_TAGS: [\r\n 'p', 'br', 'strong', 'em', 'u', 's', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',\r\n 'ul', 'ol', 'li', 'blockquote', 'pre', 'code', 'a', 'img', 'table', 'thead',\r\n 'tbody', 'tr', 'th', 'td', 'div', 'span', 'hr', 'blockquote', 'del', 'ins',\r\n 'sub', 'sup', 'kbd', 'mark', 'abbr', 'dfn', 'cite', 'q', 'samp', 'var',\r\n 'time', 'b', 'i', 'small', 'big', 'center', 'font', 'strike', 'tt',\r\n 'html', 'head', 'body', 'title', 'meta', 'link', 'style', 'script', 'noscript'\r\n ],\r\n ALLOWED_ATTR: [\r\n 'href', 'src', 'alt', 'title', 'width', 'height', 'class', 'id', 'style',\r\n 'target', 'rel', 'type', 'charset', 'name', 'content', 'http-equiv', 'lang'\r\n ],\r\n ALLOW_DATA_ATTR: false,\r\n })\r\n }\r\n return ''\r\n})\r\n</script>\r\n\r\n<template>\r\n <div :class=\"['j-preview-wrapper', props.class]\">\r\n <!-- HTML 렌더링 -->\r\n <div\r\n v-if=\"contentType === 'html'\"\r\n :class=\"['j-preview-html', theme === 'dark' ? 'dark' : '']\"\r\n v-html=\"sanitizedHtml\"\r\n />\r\n \r\n <!-- 마크다운 렌더링 -->\r\n <MdPreview\r\n v-else\r\n :model-value=\"modelValue\"\r\n :theme=\"theme\"\r\n :preview-class-name=\"'j-preview-markdown'\"\r\n language=\"en-US\"\r\n />\r\n </div>\r\n</template>\r\n\r\n<style scoped>\r\n.j-preview-wrapper {\r\n @apply w-full;\r\n}\r\n\r\n.j-preview-html {\r\n @apply prose prose-slate max-w-none;\r\n}\r\n\r\n.j-preview-html.dark {\r\n @apply prose-invert;\r\n}\r\n\r\n:deep(.j-preview-markdown) {\r\n @apply w-full;\r\n}\r\n</style>\r\n"],"names":["props","__props","detectContentType","content","trimmed","contentType","computed","sanitizedHtml","DOMPurify","_createElementBlock","_normalizeClass","_createBlock","_unref","MdPreview"],"mappings":";;;;;;;;;;;;AAMA,UAAMA,IAAQC;AAkBd,aAASC,EAAkBC,GAAsC;AAC/D,YAAMC,IAAUD,EAAQ,KAAA;AAGxB,aACEC,EAAQ,WAAW,gBAAgB,KACnCA,EAAQ,WAAW,gBAAgB,KACnCA,EAAQ,WAAW,gBAAgB,KACnCA,EAAQ,WAAW,gBAAgB,KACnCA,EAAQ,WAAW,OAAO,KAC1BA,EAAQ,WAAW,OAAO,KAC1BA,EAAQ,WAAW,OAAO,IAEnB,SAGF;AAAA,IACT;AAEA,UAAMC,IAAcC,EAAS,MAAMJ,EAAkBF,EAAM,cAAc,EAAE,CAAC,GAEtEO,IAAgBD,EAAS,MACzBD,EAAY,UAAU,SACjBG,EAAU,SAASR,EAAM,cAAc,IAAI;AAAA,MAChD,cAAc;AAAA,QACZ;AAAA,QAAK;AAAA,QAAM;AAAA,QAAU;AAAA,QAAM;AAAA,QAAK;AAAA,QAAK;AAAA,QAAM;AAAA,QAAM;AAAA,QAAM;AAAA,QAAM;AAAA,QAAM;AAAA,QACnE;AAAA,QAAM;AAAA,QAAM;AAAA,QAAM;AAAA,QAAc;AAAA,QAAO;AAAA,QAAQ;AAAA,QAAK;AAAA,QAAO;AAAA,QAAS;AAAA,QACpE;AAAA,QAAS;AAAA,QAAM;AAAA,QAAM;AAAA,QAAM;AAAA,QAAO;AAAA,QAAQ;AAAA,QAAM;AAAA,QAAc;AAAA,QAAO;AAAA,QACrE;AAAA,QAAO;AAAA,QAAO;AAAA,QAAO;AAAA,QAAQ;AAAA,QAAQ;AAAA,QAAO;AAAA,QAAQ;AAAA,QAAK;AAAA,QAAQ;AAAA,QACjE;AAAA,QAAQ;AAAA,QAAK;AAAA,QAAK;AAAA,QAAS;AAAA,QAAO;AAAA,QAAU;AAAA,QAAQ;AAAA,QAAU;AAAA,QAC9D;AAAA,QAAQ;AAAA,QAAQ;AAAA,QAAQ;AAAA,QAAS;AAAA,QAAQ;AAAA,QAAQ;AAAA,QAAS;AAAA,QAAU;AAAA,MAAA;AAAA,MAEtE,cAAc;AAAA,QACZ;AAAA,QAAQ;AAAA,QAAO;AAAA,QAAO;AAAA,QAAS;AAAA,QAAS;AAAA,QAAU;AAAA,QAAS;AAAA,QAAM;AAAA,QACjE;AAAA,QAAU;AAAA,QAAO;AAAA,QAAQ;AAAA,QAAW;AAAA,QAAQ;AAAA,QAAW;AAAA,QAAc;AAAA,MAAA;AAAA,MAEvE,iBAAiB;AAAA,IAAA,CAClB,IAEI,EACR;2BAICS,EAgBM,OAAA;AAAA,MAhBA,OAAKC,EAAA,CAAA,qBAAwBV,EAAM,KAAK,CAAA;AAAA,IAAA;MAGpCK,EAAA,UAAW,eADnBI,EAIE,OAAA;AAAA;QAFC,4BAA0BR,EAAA,UAAK,SAAA,SAAA,EAAA,CAAA;AAAA,QAChC,WAAQM,EAAA;AAAA,MAAA,yBAIVI,EAMEC,EAAAC,CAAA,GAAA;AAAA;QAJC,eAAaZ,EAAA;AAAA,QACb,OAAOA,EAAA;AAAA,QACP,sBAAoB;AAAA,QACrB,UAAS;AAAA,MAAA;;;;"}
@@ -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");const h=require("../atoms/JInput.vue.cjs"),F=require("../atoms/JTextarea.vue.cjs"),N=require("../atoms/JCheckbox.vue.cjs"),z=require("../atoms/JCombo.vue.cjs"),T=require("../atoms/JSearchCombo.vue.cjs"),S=require("../atoms/JRadio.vue.cjs"),J=require("../atoms/JSwitch.vue.cjs"),M=require("../atoms/JDatepicker.vue.cjs");require("md-editor-v3");;/* empty css */;/* empty css */require("clsx");require("tailwind-merge");require("../shadcn/badge-variants.cjs");require("@vueuse/core");require("reka-ui");;/* empty css */require("../shadcn/avatar-variants.cjs");require("dompurify");;/* empty css */require("ag-grid-vue3");require("ag-grid-community");require("ag-grid-enterprise");;/* empty css */;/* empty css */;/* empty css */;/* empty css */require("vue-sonner");const i=require("../shadcn/FieldGroup.vue.cjs"),g=require("../shadcn/Field.vue.cjs"),k=require("../shadcn/FieldLabel.vue.cjs"),q=require("../shadcn/FieldContent.vue.cjs"),P=require("../shadcn/FieldDescription.vue.cjs"),E=require("../shadcn/FieldError.vue.cjs"),L={key:0,class:"text-destructive ml-1"},j=e.defineComponent({__name:"JFormField",props:{label:{},description:{},errorMsg:{},type:{default:"input"},inlineLabel:{},orientation:{default:"vertical"},labelAlign:{default:"left"},labelWidth:{default:"8rem"},id:{},modelValue:{},placeholder:{},disabled:{type:Boolean},readonly:{type:Boolean},required:{type:Boolean},name:{},styleType:{},inputType:{},options:{},multiple:{type:Boolean},radioDirection:{default:"horizontal"}},emits:["update:modelValue","change","focus","blur"],setup(l,{expose:C,emit:x}){const b=["label","description","errorMsg","type","inlineLabel","orientation","labelAlign","labelWidth","radioDirection"],r=l,n=x,o=e.ref(""),c=e.computed(()=>r.errorMsg||o.value),s=e.computed(()=>{const t={},a=r;for(const u in a)b.includes(u)||(t[u]=a[u]);if(r.inputType&&r.type==="input"&&(t.type=r.inputType,delete t.inputType,!r.placeholder)){const u={text:"텍스트를 입력하세요",email:"이메일을 입력하세요",password:"비밀번호를 입력하세요",tel:"전화번호를 입력하세요",url:"URL을 입력하세요",number:"숫자를 입력하세요",search:"검색어를 입력하세요",date:"날짜를 선택하세요",time:"시간을 선택하세요","datetime-local":"날짜와 시간을 선택하세요",month:"월을 선택하세요",week:"주를 선택하세요"};t.placeholder=u[r.inputType]||"입력하세요"}return r.radioDirection&&r.type==="radio"&&(t.styletype=r.radioDirection,delete t.radioDirection),t}),d=t=>{if(!r.required)return;o.value="";const a=t!==void 0?t:r.modelValue;r.type==="checkbox"||r.type==="switch"?a!=="Y"&&(o.value="필수 항목입니다."):(a==null||a==="")&&(o.value="필수 입력 항목입니다.")},B=e.computed(()=>!(r.modelValue!==null&&r.modelValue!==void 0&&r.modelValue!=="")),p=t=>{n("update:modelValue",t),d(t)},f=t=>{n("change",t),d(t)},_=t=>{n("focus",t)},m=t=>{B.value&&d(),n("blur",t)},V=e.computed(()=>{const t={left:"justify-start",middle:"justify-center",right:"justify-end"},a={left:"text-left",middle:"text-center",right:"text-right"};return r.orientation==="horizontal"?t[r.labelAlign]:a[r.labelAlign]}),w=e.computed(()=>{switch(r.styleType){case"sm":return"form-density-sm";case"lg":return"form-density-lg";default:return"form-density-md"}}),v=e.computed(()=>{const t="h-[var(--ctl-h)] leading-[var(--ctl-h)]";return r.type==="datepicker"?`${t} max-w-xs`:r.type==="radio"&&r.radioDirection==="vertical"?"":t}),D={input:h.default,textarea:F.default,checkbox:N.default,switch:J.default,combo:z.default,radio:S.default,searchCombo:T.default,datepicker:M.default},y=e.computed(()=>D[r.type]||h.default);return C({clearError:()=>{o.value=""}}),(t,a)=>(e.openBlock(),e.createElementBlock("div",{class:e.normalizeClass(["space-y-2",w.value])},[e.createVNode(e.unref(i.default),null,{default:e.withCtx(()=>[e.createVNode(e.unref(g.default),{class:e.normalizeClass([l.orientation==="horizontal"?"grid grid-cols-[var(--label-w,8rem)_1fr] items-start space-y-0 gap-2":"space-y-1 gap-1"]),style:e.normalizeStyle(l.orientation==="horizontal"?`--label-w:${l.labelWidth};`:"")},{default:e.withCtx(()=>[e.createVNode(e.unref(k.default),{for:l.id,class:e.normalizeClass([l.orientation==="horizontal"?"flex items-center h-[var(--ctl-h)] w-full":"flex items-center",V.value])},{default:e.withCtx(()=>[e.createTextVNode(e.toDisplayString(l.label)+" ",1),l.required?(e.openBlock(),e.createElementBlock("span",L,"*")):e.createCommentVNode("",!0)]),_:1},8,["for","class"]),e.createVNode(e.unref(q.default),{class:e.normalizeClass([l.orientation==="horizontal"?"min-h-[var(--ctl-h)] flex flex-col justify-start gap-0.5 mt-0":"space-y-2 gap-0"])},{default:e.withCtx(()=>[l.type==="checkbox"||l.type==="switch"?(e.openBlock(),e.createBlock(e.unref(i.default),{key:0,"data-slot":"checkbox-group"},{default:e.withCtx(()=>[e.createVNode(e.unref(g.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(s.value,{"onUpdate:modelValue":p,onChange:f,onFocus:_,onBlur:m}),null,16)),l.inlineLabel?(e.openBlock(),e.createBlock(e.unref(k.default),{key:0,for:l.id,class:"font-normal m-0 h-[var(--ctl-h)] leading-[var(--ctl-h)]"},{default:e.withCtx(()=>[e.createTextVNode(e.toDisplayString(l.inlineLabel),1)]),_:1},8,["for"])):e.createCommentVNode("",!0)]),_:1})]),_:1})):l.type==="radio"?(e.openBlock(),e.createBlock(e.unref(i.default),{key:1},{default:e.withCtx(()=>[(e.openBlock(),e.createBlock(e.resolveDynamicComponent(y.value),e.mergeProps(s.value,{"onUpdate:modelValue":p,onChange:f,onFocus:_,onBlur:m,class:v.value}),null,16,["class"]))]),_:1})):(e.openBlock(),e.createBlock(e.unref(i.default),{key:2},{default:e.withCtx(()=>[(e.openBlock(),e.createBlock(e.resolveDynamicComponent(y.value),e.mergeProps(s.value,{"onUpdate:modelValue":p,onChange:f,onFocus:_,onBlur:m,class:v.value}),null,16,["class"]))]),_:1})),l.description||c.value?(e.openBlock(),e.createBlock(e.unref(q.default),{key:3},{default:e.withCtx(()=>[l.description?(e.openBlock(),e.createBlock(e.unref(P.default),{key:0},{default:e.withCtx(()=>[e.createTextVNode(e.toDisplayString(l.description),1)]),_:1})):e.createCommentVNode("",!0),c.value?(e.openBlock(),e.createBlock(e.unref(E.default),{key:1},{default:e.withCtx(()=>[e.createTextVNode(e.toDisplayString(c.value),1)]),_:1})):e.createCommentVNode("",!0)]),_:1})):e.createCommentVNode("",!0)]),_:1},8,["class"])]),_:1},8,["class","style"])]),_:1})],2))}});exports.default=j;
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");const h=require("../atoms/JInput.vue.cjs"),F=require("../atoms/JTextarea.vue.cjs"),N=require("../atoms/JCheckbox.vue.cjs"),z=require("../atoms/JCombo.vue.cjs"),T=require("../atoms/JSearchCombo.vue.cjs"),S=require("../atoms/JRadio.vue.cjs"),J=require("../atoms/JSwitch.vue.cjs"),M=require("../atoms/JDatepicker.vue.cjs");require("md-editor-v3");;/* empty css */;/* empty css */require("clsx");require("tailwind-merge");require("../shadcn/badge-variants.cjs");require("@vueuse/core");require("reka-ui");;/* empty css */require("../shadcn/avatar-variants.cjs");require("dompurify");;/* empty css */require("ag-grid-vue3");require("ag-grid-community");require("ag-grid-enterprise");;/* empty css */;/* empty css */;/* empty css */;/* empty css */require("vue-sonner");const i=require("../shadcn/FieldGroup.vue.cjs"),g=require("../shadcn/Field.vue.cjs"),k=require("../shadcn/FieldLabel.vue.cjs"),q=require("../shadcn/FieldContent.vue.cjs"),P=require("../shadcn/FieldDescription.vue.cjs"),E=require("../shadcn/FieldError.vue.cjs"),L={key:0,class:"text-destructive ml-1"},j=e.defineComponent({__name:"JFormField",props:{label:{},description:{},errorMsg:{},type:{default:"input"},inlineLabel:{},orientation:{default:"vertical"},labelAlign:{default:"left"},labelWidth:{default:"8rem"},id:{},modelValue:{},placeholder:{},disabled:{type:Boolean},readonly:{type:Boolean},required:{type:Boolean},name:{},styleType:{},inputType:{},options:{},multiple:{type:Boolean},radioDirection:{default:"horizontal"}},emits:["update:modelValue","change","focus","blur"],setup(l,{expose:C,emit:x}){const b=["label","description","errorMsg","type","inlineLabel","orientation","labelAlign","labelWidth","radioDirection"],r=l,n=x,o=e.ref(""),c=e.computed(()=>r.errorMsg||o.value),s=e.computed(()=>{const t={},a=r;for(const u in a)b.includes(u)||(t[u]=a[u]);if(r.inputType&&r.type==="input"&&(t.type=r.inputType,delete t.inputType,!r.placeholder)){const u={text:"텍스트를 입력하세요",email:"이메일을 입력하세요",password:"비밀번호를 입력하세요",tel:"전화번호를 입력하세요",url:"URL을 입력하세요",number:"숫자를 입력하세요",search:"검색어를 입력하세요",date:"날짜를 선택하세요",time:"시간을 선택하세요","datetime-local":"날짜와 시간을 선택하세요",month:"월을 선택하세요",week:"주를 선택하세요"};t.placeholder=u[r.inputType]||"입력하세요"}return r.radioDirection&&r.type==="radio"&&(t.styletype=r.radioDirection,delete t.radioDirection),t}),d=t=>{if(!r.required)return;o.value="";const a=t!==void 0?t:r.modelValue;r.type==="checkbox"||r.type==="switch"?a!=="Y"&&(o.value="필수 항목입니다."):(a==null||a==="")&&(o.value="필수 입력 항목입니다.")},B=e.computed(()=>!(r.modelValue!==null&&r.modelValue!==void 0&&r.modelValue!=="")),p=t=>{n("update:modelValue",t),d(t)},f=t=>{n("change",t),d(t)},_=t=>{n("focus",t)},m=t=>{B.value&&d(),n("blur",t)},V=e.computed(()=>{const t={left:"justify-start",middle:"justify-center",right:"justify-end"},a={left:"text-left",middle:"text-center",right:"text-right"};return r.orientation==="horizontal"?t[r.labelAlign]:a[r.labelAlign]}),w=e.computed(()=>{switch(r.styleType){case"sm":return"form-density-sm";case"lg":return"form-density-lg";default:return"form-density-md"}}),v=e.computed(()=>{const t="h-[var(--ctl-h)] leading-[var(--ctl-h)]";return r.type==="datepicker"?`${t} max-w-xs`:r.type==="radio"&&r.radioDirection==="vertical"?"":t}),D={input:h.default,textarea:F.default,checkbox:N.default,switch:J.default,combo:z.default,radio:S.default,searchCombo:T.default,datepicker:M.default},y=e.computed(()=>D[r.type]||h.default);return C({clearError:()=>{o.value=""}}),(t,a)=>(e.openBlock(),e.createElementBlock("div",{class:e.normalizeClass(["space-y-2 flex-1 min-w-0",w.value])},[e.createVNode(e.unref(i.default),null,{default:e.withCtx(()=>[e.createVNode(e.unref(g.default),{class:e.normalizeClass([l.orientation==="horizontal"?"grid grid-cols-[var(--label-w,8rem)_1fr] items-start space-y-0 gap-2":"space-y-1 gap-1"]),style:e.normalizeStyle(l.orientation==="horizontal"?`--label-w:${l.labelWidth};`:"")},{default:e.withCtx(()=>[e.createVNode(e.unref(k.default),{for:l.id,class:e.normalizeClass([l.orientation==="horizontal"?"flex items-center h-[var(--ctl-h)] w-full":"flex items-center",V.value])},{default:e.withCtx(()=>[e.createTextVNode(e.toDisplayString(l.label)+" ",1),l.required?(e.openBlock(),e.createElementBlock("span",L,"*")):e.createCommentVNode("",!0)]),_:1},8,["for","class"]),e.createVNode(e.unref(q.default),{class:e.normalizeClass([l.orientation==="horizontal"?"min-h-[var(--ctl-h)] flex flex-col justify-start gap-0.5 mt-0":"space-y-2 gap-0"])},{default:e.withCtx(()=>[l.type==="checkbox"||l.type==="switch"?(e.openBlock(),e.createBlock(e.unref(i.default),{key:0,"data-slot":"checkbox-group"},{default:e.withCtx(()=>[e.createVNode(e.unref(g.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(s.value,{"onUpdate:modelValue":p,onChange:f,onFocus:_,onBlur:m}),null,16)),l.inlineLabel?(e.openBlock(),e.createBlock(e.unref(k.default),{key:0,for:l.id,class:"font-normal m-0 h-[var(--ctl-h)] leading-[var(--ctl-h)]"},{default:e.withCtx(()=>[e.createTextVNode(e.toDisplayString(l.inlineLabel),1)]),_:1},8,["for"])):e.createCommentVNode("",!0)]),_:1})]),_:1})):l.type==="radio"?(e.openBlock(),e.createBlock(e.unref(i.default),{key:1},{default:e.withCtx(()=>[(e.openBlock(),e.createBlock(e.resolveDynamicComponent(y.value),e.mergeProps(s.value,{"onUpdate:modelValue":p,onChange:f,onFocus:_,onBlur:m,class:v.value}),null,16,["class"]))]),_:1})):(e.openBlock(),e.createBlock(e.unref(i.default),{key:2},{default:e.withCtx(()=>[(e.openBlock(),e.createBlock(e.resolveDynamicComponent(y.value),e.mergeProps(s.value,{"onUpdate:modelValue":p,onChange:f,onFocus:_,onBlur:m,class:v.value}),null,16,["class"]))]),_:1})),l.description||c.value?(e.openBlock(),e.createBlock(e.unref(q.default),{key:3},{default:e.withCtx(()=>[l.description?(e.openBlock(),e.createBlock(e.unref(P.default),{key:0},{default:e.withCtx(()=>[e.createTextVNode(e.toDisplayString(l.description),1)]),_:1})):e.createCommentVNode("",!0),c.value?(e.openBlock(),e.createBlock(e.unref(E.default),{key:1},{default:e.withCtx(()=>[e.createTextVNode(e.toDisplayString(c.value),1)]),_:1})):e.createCommentVNode("",!0)]),_:1})):e.createCommentVNode("",!0)]),_:1},8,["class"])]),_:1},8,["class","style"])]),_:1})],2))}});exports.default=j;
2
2
  //# sourceMappingURL=JFormField.vue.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"JFormField.vue.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 } from '@/components/atoms'\n\n// 컴포넌트 타입 정의\ntype ComponentType = 'input' | 'textarea' | 'checkbox' | 'switch' | 'combo' | 'radio' | 'searchCombo' | 'datepicker'\n\n// FormField 자체의 props (레이아웃 관련)\nconst FORM_FIELD_PROPS = [\n 'label',\n 'description',\n 'errorMsg',\n 'type',\n 'inlineLabel',\n 'orientation',\n 'labelAlign',\n 'labelWidth',\n 'radioDirection',\n] as const\n\nconst props = withDefaults(\n defineProps<{\n // ============ FormField 자체 props (레이아웃만) ============\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 {\n type: 'input',\n orientation: 'vertical',\n labelAlign: 'left',\n labelWidth: '8rem',\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}>()\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 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 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 'sm': return 'form-density-sm'\n case 'lg': return 'form-density-lg'\n default: return 'form-density-md'\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 // 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}\n\n// type에 따라 렌더링할 컴포넌트 결정\nconst resolvedComponent = computed(() => {\n return componentMap[props.type!] || JInput\n})\n\n// 외부에서 수동으로 에러 클리어 가능하도록 expose\ndefineExpose({\n clearError: () => { internalError.value = '' }\n})\n</script>\n\n<template>\n <div :class=\"['space-y-2', densityClass]\">\n <FieldGroup >\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 ]\"\n :style=\"orientation === 'horizontal' ? `--label-w:${labelWidth};` : ''\"\n >\n <!-- 메인 라벨 (필수표기 포함) -->\n <FieldLabel \n :for=\"id\" \n :class=\"[\n orientation === 'horizontal'\n ? '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-1\">*</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 ]\"\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=\"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>\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 <!-- 설명/에러: 항상 컨트롤 '바로 아래'에 표시 -->\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>\n/* ⬇⬇ 높이 토큰(밀도) — 프로젝트 전역/레이아웃에 한 번 정의해두고 재사용하세요 */\n.form-density-sm { --ctl-h: 2rem; /* 32px */ }\n.form-density-md { --ctl-h: 2.25rem; /* 36px (shadcn 기본에 근접) */ }\n.form-density-lg { --ctl-h: 2.5rem; /* 40px */ }\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","handleFocus","event","handleBlur","labelAlignClass","mapHorizontal","mapVertical","densityClass","controlClass","baseClass","componentMap","JInput","JTextarea","JCheckbox","JSwitch","JCombo","JRadio","JSearchCombo","JDatepicker","resolvedComponent","__expose","_createElementBlock","_createVNode","_unref","FieldGroup","Field","_normalizeClass","_normalizeStyle","FieldLabel","_createTextVNode","_toDisplayString","_hoisted_1","FieldContent","_createBlock","_openBlock","_resolveDynamicComponent","_mergeProps","FieldDescription","FieldError"],"mappings":"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"}
1
+ {"version":3,"file":"JFormField.vue.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 } from '@/components/atoms'\n\n// 컴포넌트 타입 정의\ntype ComponentType = 'input' | 'textarea' | 'checkbox' | 'switch' | 'combo' | 'radio' | 'searchCombo' | 'datepicker'\n\n// FormField 자체의 props (레이아웃 관련)\nconst FORM_FIELD_PROPS = [\n 'label',\n 'description',\n 'errorMsg',\n 'type',\n 'inlineLabel',\n 'orientation',\n 'labelAlign',\n 'labelWidth',\n 'radioDirection',\n] as const\n\nconst props = withDefaults(\n defineProps<{\n // ============ FormField 자체 props (레이아웃만) ============\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 {\n type: 'input',\n orientation: 'vertical',\n labelAlign: 'left',\n labelWidth: '8rem',\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}>()\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 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 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 'sm': return 'form-density-sm'\n case 'lg': return 'form-density-lg'\n default: return 'form-density-md'\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 // 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}\n\n// type에 따라 렌더링할 컴포넌트 결정\nconst resolvedComponent = computed(() => {\n return componentMap[props.type!] || JInput\n})\n\n// 외부에서 수동으로 에러 클리어 가능하도록 expose\ndefineExpose({\n clearError: () => { internalError.value = '' }\n})\n</script>\n\n<template>\n <div :class=\"['space-y-2 flex-1 min-w-0', densityClass]\">\n <FieldGroup >\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 ]\"\n :style=\"orientation === 'horizontal' ? `--label-w:${labelWidth};` : ''\"\n >\n <!-- 메인 라벨 (필수표기 포함) -->\n <FieldLabel \n :for=\"id\" \n :class=\"[\n orientation === 'horizontal'\n ? '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-1\">*</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 ]\"\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=\"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>\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 <!-- 설명/에러: 항상 컨트롤 '바로 아래'에 표시 -->\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>\n/* ⬇⬇ 높이 토큰(밀도) — 프로젝트 전역/레이아웃에 한 번 정의해두고 재사용하세요 */\n.form-density-sm { --ctl-h: 2rem; /* 32px */ }\n.form-density-md { --ctl-h: 2.25rem; /* 36px (shadcn 기본에 근접) */ }\n.form-density-lg { --ctl-h: 2.5rem; /* 40px */ }\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","handleFocus","event","handleBlur","labelAlignClass","mapHorizontal","mapVertical","densityClass","controlClass","baseClass","componentMap","JInput","JTextarea","JCheckbox","JSwitch","JCombo","JRadio","JSearchCombo","JDatepicker","resolvedComponent","__expose","_createElementBlock","_createVNode","_unref","FieldGroup","Field","_normalizeClass","_normalizeStyle","FieldLabel","_createTextVNode","_toDisplayString","_hoisted_1","FieldContent","_createBlock","_openBlock","_resolveDynamicComponent","_mergeProps","FieldDescription","FieldError"],"mappings":"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"}
@@ -146,7 +146,7 @@ const Z = {
146
146
  s.value = "";
147
147
  }
148
148
  }), (e, r) => (o(), D("div", {
149
- class: p(["space-y-2", O.value])
149
+ class: p(["space-y-2 flex-1 min-w-0", O.value])
150
150
  }, [
151
151
  u(a(v), null, {
152
152
  default: i(() => [
@@ -1 +1 @@
1
- {"version":3,"file":"JFormField.vue.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 } from '@/components/atoms'\n\n// 컴포넌트 타입 정의\ntype ComponentType = 'input' | 'textarea' | 'checkbox' | 'switch' | 'combo' | 'radio' | 'searchCombo' | 'datepicker'\n\n// FormField 자체의 props (레이아웃 관련)\nconst FORM_FIELD_PROPS = [\n 'label',\n 'description',\n 'errorMsg',\n 'type',\n 'inlineLabel',\n 'orientation',\n 'labelAlign',\n 'labelWidth',\n 'radioDirection',\n] as const\n\nconst props = withDefaults(\n defineProps<{\n // ============ FormField 자체 props (레이아웃만) ============\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 {\n type: 'input',\n orientation: 'vertical',\n labelAlign: 'left',\n labelWidth: '8rem',\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}>()\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 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 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 'sm': return 'form-density-sm'\n case 'lg': return 'form-density-lg'\n default: return 'form-density-md'\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 // 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}\n\n// type에 따라 렌더링할 컴포넌트 결정\nconst resolvedComponent = computed(() => {\n return componentMap[props.type!] || JInput\n})\n\n// 외부에서 수동으로 에러 클리어 가능하도록 expose\ndefineExpose({\n clearError: () => { internalError.value = '' }\n})\n</script>\n\n<template>\n <div :class=\"['space-y-2', densityClass]\">\n <FieldGroup >\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 ]\"\n :style=\"orientation === 'horizontal' ? `--label-w:${labelWidth};` : ''\"\n >\n <!-- 메인 라벨 (필수표기 포함) -->\n <FieldLabel \n :for=\"id\" \n :class=\"[\n orientation === 'horizontal'\n ? '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-1\">*</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 ]\"\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=\"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>\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 <!-- 설명/에러: 항상 컨트롤 '바로 아래'에 표시 -->\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>\n/* ⬇⬇ 높이 토큰(밀도) — 프로젝트 전역/레이아웃에 한 번 정의해두고 재사용하세요 */\n.form-density-sm { --ctl-h: 2rem; /* 32px */ }\n.form-density-md { --ctl-h: 2.25rem; /* 36px (shadcn 기본에 근접) */ }\n.form-density-lg { --ctl-h: 2.5rem; /* 40px */ }\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","handleFocus","event","handleBlur","labelAlignClass","mapHorizontal","mapVertical","densityClass","controlClass","baseClass","componentMap","JInput","JTextarea","JCheckbox","JSwitch","JCombo","JRadio","JSearchCombo","JDatepicker","resolvedComponent","__expose","_createElementBlock","_createVNode","_unref","FieldGroup","Field","_normalizeClass","_normalizeStyle","FieldLabel","_createTextVNode","_toDisplayString","_hoisted_1","FieldContent","_createBlock","_openBlock","_resolveDynamicComponent","_mergeProps","FieldDescription","FieldError"],"mappings":"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"}
1
+ {"version":3,"file":"JFormField.vue.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 } from '@/components/atoms'\n\n// 컴포넌트 타입 정의\ntype ComponentType = 'input' | 'textarea' | 'checkbox' | 'switch' | 'combo' | 'radio' | 'searchCombo' | 'datepicker'\n\n// FormField 자체의 props (레이아웃 관련)\nconst FORM_FIELD_PROPS = [\n 'label',\n 'description',\n 'errorMsg',\n 'type',\n 'inlineLabel',\n 'orientation',\n 'labelAlign',\n 'labelWidth',\n 'radioDirection',\n] as const\n\nconst props = withDefaults(\n defineProps<{\n // ============ FormField 자체 props (레이아웃만) ============\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 {\n type: 'input',\n orientation: 'vertical',\n labelAlign: 'left',\n labelWidth: '8rem',\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}>()\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 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 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 'sm': return 'form-density-sm'\n case 'lg': return 'form-density-lg'\n default: return 'form-density-md'\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 // 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}\n\n// type에 따라 렌더링할 컴포넌트 결정\nconst resolvedComponent = computed(() => {\n return componentMap[props.type!] || JInput\n})\n\n// 외부에서 수동으로 에러 클리어 가능하도록 expose\ndefineExpose({\n clearError: () => { internalError.value = '' }\n})\n</script>\n\n<template>\n <div :class=\"['space-y-2 flex-1 min-w-0', densityClass]\">\n <FieldGroup >\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 ]\"\n :style=\"orientation === 'horizontal' ? `--label-w:${labelWidth};` : ''\"\n >\n <!-- 메인 라벨 (필수표기 포함) -->\n <FieldLabel \n :for=\"id\" \n :class=\"[\n orientation === 'horizontal'\n ? '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-1\">*</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 ]\"\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=\"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>\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 <!-- 설명/에러: 항상 컨트롤 '바로 아래'에 표시 -->\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>\n/* ⬇⬇ 높이 토큰(밀도) — 프로젝트 전역/레이아웃에 한 번 정의해두고 재사용하세요 */\n.form-density-sm { --ctl-h: 2rem; /* 32px */ }\n.form-density-md { --ctl-h: 2.25rem; /* 36px (shadcn 기본에 근접) */ }\n.form-density-lg { --ctl-h: 2.5rem; /* 40px */ }\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","handleFocus","event","handleBlur","labelAlignClass","mapHorizontal","mapVertical","densityClass","controlClass","baseClass","componentMap","JInput","JTextarea","JCheckbox","JSwitch","JCombo","JRadio","JSearchCombo","JDatepicker","resolvedComponent","__expose","_createElementBlock","_createVNode","_unref","FieldGroup","Field","_normalizeClass","_normalizeStyle","FieldLabel","_createTextVNode","_toDisplayString","_hoisted_1","FieldContent","_createBlock","_openBlock","_resolveDynamicComponent","_mergeProps","FieldDescription","FieldError"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),a=require("lucide-vue-next"),d={class:"w-full rounded-lg border bg-card text-card-foreground"},i={class:"flex items-center justify-between px-4 py-2"},u={class:"flex items-center"},m={class:"flex items-center gap-2"},f={key:0,class:"px-4 pb-4"},p=e.defineComponent({__name:"JFilterBar",props:{mode:{default:"full"},collapsed:{type:Boolean,default:!1},collapsible:{type:Boolean,default:!0}},emits:["update:collapsed"],setup(t,{emit:n}){const o=t,c=n,l=e.computed(()=>o.collapsible?!o.collapsed:!0);function r(){c("update:collapsed",!o.collapsed)}return(s,v)=>(e.openBlock(),e.createElementBlock("div",d,[e.createElementVNode("div",i,[e.createElementVNode("div",u,[t.mode==="full"&&t.collapsible?(e.openBlock(),e.createElementBlock("button",{key:0,type:"button",class:"flex items-center justify-center h-7 w-7 rounded hover:bg-accent hover:text-accent-foreground transition-colors",onClick:r},[e.createVNode(e.unref(a.ChevronDown),{class:e.normalizeClass(["h-4 w-4 transition-transform",l.value?"rotate-0":"-rotate-90"])},null,8,["class"])])):e.createCommentVNode("",!0)]),e.createElementVNode("div",m,[e.renderSlot(s.$slots,"actions")])]),t.mode==="full"?e.withDirectives((e.openBlock(),e.createElementBlock("div",f,[e.renderSlot(s.$slots,"filters")],512)),[[e.vShow,l.value]]):e.createCommentVNode("",!0)]))}});exports.default=p;
2
+ //# sourceMappingURL=JFilterBar.vue.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JFilterBar.vue.cjs","sources":["../../../../src/components/organisms/JFilterBar.vue"],"sourcesContent":["<template>\r\n <div class=\"w-full rounded-lg border bg-card text-card-foreground\">\r\n <!-- Row 1: toolbar -->\r\n <div class=\"flex items-center justify-between px-4 py-2\">\r\n <div class=\"flex items-center\">\r\n <button\r\n v-if=\"mode === 'full' && collapsible\"\r\n type=\"button\"\r\n class=\"flex items-center justify-center h-7 w-7 rounded hover:bg-accent hover:text-accent-foreground transition-colors\"\r\n @click=\"toggleCollapsed\"\r\n >\r\n <ChevronDown\r\n :class=\"[\r\n 'h-4 w-4 transition-transform',\r\n isExpanded ? 'rotate-0' : '-rotate-90',\r\n ]\"\r\n />\r\n </button>\r\n </div>\r\n <div class=\"flex items-center gap-2\">\r\n <slot name=\"actions\" />\r\n </div>\r\n </div>\r\n\r\n <!-- Row 2: filters (full 모드에서만) -->\r\n <div v-if=\"mode === 'full'\" v-show=\"isExpanded\" class=\"px-4 pb-4\">\r\n <slot name=\"filters\" />\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed } from 'vue'\r\nimport { ChevronDown } from 'lucide-vue-next'\r\n\r\nexport interface JFilterBarProps {\r\n /** full: 토글+필터 있음, simple: 버튼만 */\r\n mode?: 'full' | 'simple'\r\n /** 필터 접힘 상태 (v-model 지원) */\r\n collapsed?: boolean\r\n /** 접기/펼치기 가능 여부. false면 토글 버튼 숨김 & 필터 항상 표시 */\r\n collapsible?: boolean\r\n}\r\n\r\nconst props = withDefaults(defineProps<JFilterBarProps>(), {\r\n mode: 'full',\r\n collapsed: false,\r\n collapsible: true,\r\n})\r\n\r\nconst emit = defineEmits<{\r\n 'update:collapsed': [value: boolean]\r\n}>()\r\n\r\nconst isExpanded = computed(() => {\r\n if (!props.collapsible) return true\r\n return !props.collapsed\r\n})\r\n\r\nfunction toggleCollapsed() {\r\n emit('update:collapsed', !props.collapsed)\r\n}\r\n</script>\r\n"],"names":["props","__props","emit","__emit","isExpanded","computed","toggleCollapsed","_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_hoisted_3","_createVNode","_unref","ChevronDown","_normalizeClass","_hoisted_4","_renderSlot","_ctx","_withDirectives","_hoisted_5"],"mappings":"sjBA4CA,MAAMA,EAAQC,EAMRC,EAAOC,EAIPC,EAAaC,EAAAA,SAAS,IACrBL,EAAM,YACJ,CAACA,EAAM,UADiB,EAEhC,EAED,SAASM,GAAkB,CACzBJ,EAAK,mBAAoB,CAACF,EAAM,SAAS,CAC3C,eA5DEO,YAAA,EAAAC,qBA2BM,MA3BNC,EA2BM,CAzBJC,EAAAA,mBAmBM,MAnBNC,EAmBM,CAlBJD,EAAAA,mBAcM,MAdNE,EAcM,CAZIX,EAAA,eAAmBA,EAAA,2BAD3BO,EAAAA,mBAYS,SAAA,OAVP,KAAK,SACL,MAAM,kHACL,QAAOF,CAAA,GAERO,cAKEC,EAAAA,MAAAC,EAAAA,WAAA,EAAA,CAJC,MAAKC,EAAAA,eAAA,gCAAkEZ,EAAA,MAAU,WAAA,YAAA,uDAOxFM,EAAAA,mBAEM,MAFNO,EAEM,CADJC,aAAuBC,EAAA,OAAA,SAAA,CAAA,KAKhBlB,EAAA,OAAI,OAAfmB,EAAAA,gBAAAb,EAAAA,YAAAC,EAAAA,mBAEM,MAFNa,EAEM,CADJH,aAAuBC,EAAA,OAAA,SAAA,CAAA,kBADWf,EAAA,KAAU,CAAA"}
@@ -0,0 +1,51 @@
1
+ import { defineComponent as f, computed as p, createElementBlock as o, openBlock as l, createElementVNode as s, withDirectives as m, createCommentVNode as a, createVNode as h, unref as v, normalizeClass as b, renderSlot as c, vShow as x } from "vue";
2
+ import { ChevronDown as g } from "lucide-vue-next";
3
+ const y = { class: "w-full rounded-lg border bg-card text-card-foreground" }, _ = { class: "flex items-center justify-between px-4 py-2" }, w = { class: "flex items-center" }, C = { class: "flex items-center gap-2" }, k = {
4
+ key: 0,
5
+ class: "px-4 pb-4"
6
+ }, V = /* @__PURE__ */ f({
7
+ __name: "JFilterBar",
8
+ props: {
9
+ mode: { default: "full" },
10
+ collapsed: { type: Boolean, default: !1 },
11
+ collapsible: { type: Boolean, default: !0 }
12
+ },
13
+ emits: ["update:collapsed"],
14
+ setup(e, { emit: d }) {
15
+ const t = e, i = d, n = p(() => t.collapsible ? !t.collapsed : !0);
16
+ function u() {
17
+ i("update:collapsed", !t.collapsed);
18
+ }
19
+ return (r, B) => (l(), o("div", y, [
20
+ s("div", _, [
21
+ s("div", w, [
22
+ e.mode === "full" && e.collapsible ? (l(), o("button", {
23
+ key: 0,
24
+ type: "button",
25
+ class: "flex items-center justify-center h-7 w-7 rounded hover:bg-accent hover:text-accent-foreground transition-colors",
26
+ onClick: u
27
+ }, [
28
+ h(v(g), {
29
+ class: b([
30
+ "h-4 w-4 transition-transform",
31
+ n.value ? "rotate-0" : "-rotate-90"
32
+ ])
33
+ }, null, 8, ["class"])
34
+ ])) : a("", !0)
35
+ ]),
36
+ s("div", C, [
37
+ c(r.$slots, "actions")
38
+ ])
39
+ ]),
40
+ e.mode === "full" ? m((l(), o("div", k, [
41
+ c(r.$slots, "filters")
42
+ ], 512)), [
43
+ [x, n.value]
44
+ ]) : a("", !0)
45
+ ]));
46
+ }
47
+ });
48
+ export {
49
+ V as default
50
+ };
51
+ //# sourceMappingURL=JFilterBar.vue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JFilterBar.vue.js","sources":["../../../../src/components/organisms/JFilterBar.vue"],"sourcesContent":["<template>\r\n <div class=\"w-full rounded-lg border bg-card text-card-foreground\">\r\n <!-- Row 1: toolbar -->\r\n <div class=\"flex items-center justify-between px-4 py-2\">\r\n <div class=\"flex items-center\">\r\n <button\r\n v-if=\"mode === 'full' && collapsible\"\r\n type=\"button\"\r\n class=\"flex items-center justify-center h-7 w-7 rounded hover:bg-accent hover:text-accent-foreground transition-colors\"\r\n @click=\"toggleCollapsed\"\r\n >\r\n <ChevronDown\r\n :class=\"[\r\n 'h-4 w-4 transition-transform',\r\n isExpanded ? 'rotate-0' : '-rotate-90',\r\n ]\"\r\n />\r\n </button>\r\n </div>\r\n <div class=\"flex items-center gap-2\">\r\n <slot name=\"actions\" />\r\n </div>\r\n </div>\r\n\r\n <!-- Row 2: filters (full 모드에서만) -->\r\n <div v-if=\"mode === 'full'\" v-show=\"isExpanded\" class=\"px-4 pb-4\">\r\n <slot name=\"filters\" />\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed } from 'vue'\r\nimport { ChevronDown } from 'lucide-vue-next'\r\n\r\nexport interface JFilterBarProps {\r\n /** full: 토글+필터 있음, simple: 버튼만 */\r\n mode?: 'full' | 'simple'\r\n /** 필터 접힘 상태 (v-model 지원) */\r\n collapsed?: boolean\r\n /** 접기/펼치기 가능 여부. false면 토글 버튼 숨김 & 필터 항상 표시 */\r\n collapsible?: boolean\r\n}\r\n\r\nconst props = withDefaults(defineProps<JFilterBarProps>(), {\r\n mode: 'full',\r\n collapsed: false,\r\n collapsible: true,\r\n})\r\n\r\nconst emit = defineEmits<{\r\n 'update:collapsed': [value: boolean]\r\n}>()\r\n\r\nconst isExpanded = computed(() => {\r\n if (!props.collapsible) return true\r\n return !props.collapsed\r\n})\r\n\r\nfunction toggleCollapsed() {\r\n emit('update:collapsed', !props.collapsed)\r\n}\r\n</script>\r\n"],"names":["props","__props","emit","__emit","isExpanded","computed","toggleCollapsed","_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_hoisted_3","_createVNode","_unref","ChevronDown","_normalizeClass","_hoisted_4","_renderSlot","_ctx","_withDirectives","_hoisted_5"],"mappings":";;;;;;;;;;;;;;AA4CA,UAAMA,IAAQC,GAMRC,IAAOC,GAIPC,IAAaC,EAAS,MACrBL,EAAM,cACJ,CAACA,EAAM,YADiB,EAEhC;AAED,aAASM,IAAkB;AACzB,MAAAJ,EAAK,oBAAoB,CAACF,EAAM,SAAS;AAAA,IAC3C;sBA5DEO,EAAA,GAAAC,EA2BM,OA3BNC,GA2BM;AAAA,MAzBJC,EAmBM,OAnBNC,GAmBM;AAAA,QAlBJD,EAcM,OAdNE,GAcM;AAAA,UAZIX,EAAA,mBAAmBA,EAAA,oBAD3BO,EAYS,UAAA;AAAA;YAVP,MAAK;AAAA,YACL,OAAM;AAAA,YACL,SAAOF;AAAA,UAAA;YAERO,EAKEC,EAAAC,CAAA,GAAA;AAAA,cAJC,OAAKC,EAAA;AAAA;gBAAkEZ,EAAA,QAAU,aAAA;AAAA,cAAA;;;;QAOxFM,EAEM,OAFNO,GAEM;AAAA,UADJC,EAAuBC,EAAA,QAAA,SAAA;AAAA,QAAA;;MAKhBlB,EAAA,SAAI,SAAfmB,GAAAb,KAAAC,EAEM,OAFNa,GAEM;AAAA,QADJH,EAAuBC,EAAA,QAAA,SAAA;AAAA,MAAA;YADWf,EAAA,KAAU;AAAA,MAAA;;;;"}
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("./JFilterBar.vue.cjs");exports.default=e.default;
2
+ //# sourceMappingURL=JFilterBar.vue2.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JFilterBar.vue2.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":""}
@@ -0,0 +1,5 @@
1
+ import f from "./JFilterBar.vue.js";
2
+ export {
3
+ f as default
4
+ };
5
+ //# sourceMappingURL=JFilterBar.vue2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JFilterBar.vue2.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),F=require("vue-router"),f=require("../../atoms/JIcon.vue.cjs"),u=require("../../../lib/utils.cjs"),P={key:1,class:"w-4 flex-shrink-0"},S={key:0,class:"w-full"},E=e.defineComponent({__name:"JDynamicMenuItem",props:{item:{},level:{default:0},permissions:{default:()=>[]},activePath:{},expandedKeys:{default:()=>new Set},favorites:{default:()=>[]},onFavoriteToggle:{},isFavorite:{},styletype:{default:"default"},className:{},maxDepth:{default:10}},emits:["menuClick","expandChange"],setup(t,{emit:h}){const n=t,s=h,k=F.useRouter(),x=e.computed(()=>u.hasMenuPermission(n.item.menuKey,n.permissions)),m=e.computed(()=>!n.item.path||!n.activePath?!1:n.activePath===n.item.path),c=e.computed(()=>n.item.menuType==="F"||Array.isArray(n.item.children)&&n.item.children.length>0),d=e.computed(()=>{if(!c.value)return!1;const l=n.item.menuKey||n.item.label;return n.expandedKeys?.has(l)??!1}),v=e.computed(()=>n.item.disabled||!x.value),C=e.computed(()=>({paddingLeft:`${12+(n.level||0)*16}px`})),p=l=>{if(v.value){l.preventDefault();return}if(c.value){const a=n.item.menuKey||n.item.label,r=!d.value;s("expandChange",a,r)}else n.item.path&&(k.push(n.item.path),s("menuClick",{menuItem:n.item,path:[n.item],event:l}))},g={default:{itemClass:"flex items-center gap-2 py-2 rounded-md cursor-pointer transition-colors group",labelClass:"flex-1 truncate",iconSize:"sm"},minimal:{itemClass:"flex items-center gap-1.5 py-1.5 rounded-md cursor-pointer transition-colors group",labelClass:"flex-1 truncate text-xs",iconSize:"sm"}},i=e.computed(()=>g[n.styletype]??g.default),b=e.computed(()=>d.value?"chevronDown":"chevronRight");return(l,a)=>{const r=e.resolveComponent("JDynamicMenuItem",!0);return e.openBlock(),e.createElementBlock("div",{class:e.normalizeClass(e.unref(u.cn)("w-full",t.className))},[e.createElementVNode("div",{class:e.normalizeClass(e.unref(u.cn)(i.value.itemClass,{"bg-accent text-accent-foreground":m.value,"hover:bg-accent/50":!v.value&&!m.value,"opacity-50 cursor-not-allowed":v.value,"font-medium":m.value})),style:e.normalizeStyle(C.value),onClick:p},[c.value?(e.openBlock(),e.createBlock(f.default,{key:0,name:b.value,size:i.value.iconSize,class:"flex-shrink-0"},null,8,["name","size"])):(e.openBlock(),e.createElementBlock("span",P)),t.item.icon?(e.openBlock(),e.createBlock(f.default,{key:2,name:t.item.icon,size:i.value.iconSize,class:"flex-shrink-0"},null,8,["name","size"])):e.createCommentVNode("",!0),e.createElementVNode("span",{class:e.normalizeClass(i.value.labelClass)},e.toDisplayString(t.item.label),3),t.item.menuKey&&t.item.menuType==="L"&&t.onFavoriteToggle?(e.openBlock(),e.createElementBlock("button",{key:3,class:e.normalizeClass(e.unref(u.cn)("opacity-0 group-hover:opacity-100 transition-opacity hover:bg-accent rounded flex-shrink-0",n.styletype==="minimal"?"p-0.5":"p-1",t.isFavorite&&t.isFavorite(t.item.menuKey)&&"opacity-100")),onClick:a[0]||(a[0]=e.withModifiers(o=>t.onFavoriteToggle(t.item.menuKey),["stop"]))},[e.createVNode(f.default,{name:(t.isFavorite&&t.isFavorite(t.item.menuKey),"star"),size:i.value.iconSize,class:e.normalizeClass(t.isFavorite&&t.isFavorite(t.item.menuKey)?"text-yellow-500 fill-yellow-500":"text-muted-foreground")},null,8,["name","size","class"])],2)):e.createCommentVNode("",!0)],6),c.value&&d.value&&t.item.children&&Array.isArray(t.item.children)&&t.item.children.length>0&&t.level+1<t.maxDepth?(e.openBlock(),e.createElementBlock("div",S,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(t.item.children,(o,z)=>(e.openBlock(),e.createBlock(r,{key:o.menuKey||o.label||z,item:o,level:t.level+1,"max-depth":t.maxDepth,permissions:t.permissions,"active-path":t.activePath,"expanded-keys":t.expandedKeys,favorites:t.favorites,"on-favorite-toggle":t.onFavoriteToggle,"is-favorite":t.isFavorite,styletype:t.styletype,onMenuClick:a[1]||(a[1]=y=>s("menuClick",y)),onExpandChange:a[2]||(a[2]=(y,B)=>s("expandChange",y,B))},null,8,["item","level","max-depth","permissions","active-path","expanded-keys","favorites","on-favorite-toggle","is-favorite","styletype"]))),128))])):e.createCommentVNode("",!0)],2)}}});exports.default=E;
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),B=require("vue-router"),f=require("../../atoms/JIcon.vue.cjs"),r=require("../../../lib/utils.cjs"),F={key:1,class:"w-4 flex-shrink-0"},P={key:0,class:"w-full"},S=e.defineComponent({__name:"JDynamicMenuItem",props:{item:{},level:{default:0},permissions:{default:()=>[]},activePath:{},expandedKeys:{default:()=>new Set},favorites:{default:()=>[]},onFavoriteToggle:{},isFavorite:{},styletype:{default:"default"},className:{},maxDepth:{default:10},disableNavigation:{type:Boolean,default:!1},activeKey:{default:null}},emits:["menuClick","expandChange"],setup(t,{emit:h}){const n=t,s=h,k=B.useRouter(),x=e.computed(()=>r.hasMenuPermission(n.item.menuKey,n.permissions)),m=e.computed(()=>n.activeKey!==void 0&&n.activeKey!==null?n.item.menuKey===n.activeKey:!n.item.path||!n.activePath?!1:n.activePath===n.item.path),c=e.computed(()=>n.item.menuType==="F"||Array.isArray(n.item.children)&&n.item.children.length>0),d=e.computed(()=>{if(!c.value)return!1;const i=n.item.menuKey||n.item.label;return n.expandedKeys?.has(i)??!1}),v=e.computed(()=>n.item.disabled||!x.value),C=e.computed(()=>({paddingLeft:`${12+(n.level||0)*16}px`})),b=i=>{if(v.value){i.preventDefault();return}if(c.value){const a=n.item.menuKey||n.item.label,u=!d.value;s("expandChange",a,u)}else!n.disableNavigation&&n.item.path&&k.push(n.item.path),s("menuClick",{menuItem:n.item,path:[n.item],event:i})},g={default:{itemClass:"flex items-center gap-2 py-2 rounded-md cursor-pointer transition-colors group",labelClass:"flex-1 truncate",iconSize:"sm"},minimal:{itemClass:"flex items-center gap-1.5 py-1.5 rounded-md cursor-pointer transition-colors group",labelClass:"flex-1 truncate text-xs",iconSize:"sm"}},l=e.computed(()=>g[n.styletype]??g.default),p=e.computed(()=>d.value?"chevronDown":"chevronRight");return(i,a)=>{const u=e.resolveComponent("JDynamicMenuItem",!0);return e.openBlock(),e.createElementBlock("div",{class:e.normalizeClass(e.unref(r.cn)("w-full",t.className))},[e.createElementVNode("div",{class:e.normalizeClass(e.unref(r.cn)(l.value.itemClass,{"bg-accent text-accent-foreground":m.value,"hover:bg-accent/50":!v.value&&!m.value,"opacity-50 cursor-not-allowed":v.value,"font-medium":m.value})),style:e.normalizeStyle(C.value),onClick:b},[c.value?(e.openBlock(),e.createBlock(f.default,{key:0,name:p.value,size:l.value.iconSize,class:"flex-shrink-0"},null,8,["name","size"])):(e.openBlock(),e.createElementBlock("span",F)),t.item.icon?(e.openBlock(),e.createBlock(f.default,{key:2,name:t.item.icon,size:l.value.iconSize,class:"flex-shrink-0"},null,8,["name","size"])):e.createCommentVNode("",!0),e.createElementVNode("span",{class:e.normalizeClass(l.value.labelClass)},e.toDisplayString(t.item.label),3),t.item.menuKey&&t.item.menuType==="L"&&t.onFavoriteToggle?(e.openBlock(),e.createElementBlock("button",{key:3,class:e.normalizeClass(e.unref(r.cn)("opacity-0 group-hover:opacity-100 transition-opacity hover:bg-accent rounded flex-shrink-0",n.styletype==="minimal"?"p-0.5":"p-1",t.isFavorite&&t.isFavorite(t.item.menuKey)&&"opacity-100")),onClick:a[0]||(a[0]=e.withModifiers(o=>t.onFavoriteToggle(t.item.menuKey),["stop"]))},[e.createVNode(f.default,{name:(t.isFavorite&&t.isFavorite(t.item.menuKey),"star"),size:l.value.iconSize,class:e.normalizeClass(t.isFavorite&&t.isFavorite(t.item.menuKey)?"text-yellow-500 fill-yellow-500":"text-muted-foreground")},null,8,["name","size","class"])],2)):e.createCommentVNode("",!0)],6),c.value&&d.value&&t.item.children&&Array.isArray(t.item.children)&&t.item.children.length>0&&t.level+1<t.maxDepth?(e.openBlock(),e.createElementBlock("div",P,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(t.item.children,(o,K)=>(e.openBlock(),e.createBlock(u,{key:o.menuKey||o.label||K,item:o,level:t.level+1,"max-depth":t.maxDepth,permissions:t.permissions,"active-path":t.activePath,"expanded-keys":t.expandedKeys,favorites:t.favorites,"on-favorite-toggle":t.onFavoriteToggle,"is-favorite":t.isFavorite,styletype:t.styletype,"disable-navigation":t.disableNavigation,"active-key":t.activeKey,onMenuClick:a[1]||(a[1]=y=>s("menuClick",y)),onExpandChange:a[2]||(a[2]=(y,z)=>s("expandChange",y,z))},null,8,["item","level","max-depth","permissions","active-path","expanded-keys","favorites","on-favorite-toggle","is-favorite","styletype","disable-navigation","active-key"]))),128))])):e.createCommentVNode("",!0)],2)}}});exports.default=S;
2
2
  //# sourceMappingURL=JDynamicMenuItem.vue.cjs.map