PageSourceSearch

https://ccrs.or.kr/type/www/js/component/ui-script.js?ver=20260924191211

js ccrs.or.kr collected 2026-09-24 10:12:36 UTC 87,345 bytes, 2,460 lines download raw bytes

1"use strict";
2
3// 윈도우 사이즈 체크
4const windowSize = {
5  winSize: null,
6  breakPoint: 1279,
7
8  setWinSize() {
9    this.winSize = window.innerWidth >= this.breakPoint ? "pc" : "mob";
10  },
11
12  getWinSize() {
13    return this.winSize;
14  },
15};
16
17// 스크롤 방향 체크
18const scrollManager = {
19  _scrollY: 0,
20  _scrollH: 0,
21  _lastScrollY: 0,
22
23  updateScrollValues() {
24    this._scrollY = window.scrollY;
25    this._scrollH = document.body.scrollHeight;
26  },
27
28  handleScrollDirection() {
29    const $wrap = document.querySelector("#wrap");
30    if ($wrap) {
31      const _conOffsetTop = document.querySelector("#container").offsetTop;
32      const _scrollY = window.scrollY;
33      const _scrollDown = _scrollY > this._lastScrollY;
34      const _scrollUp = _scrollY < this._lastScrollY;
35
36      if (_scrollY > _conOffsetTop + 50 && _scrollDown) {
37        $wrap.classList.add("scroll-down");
38        $wrap.classList.remove("scroll-up");
39      } else if (_scrollY > _conOffsetTop + 50 && _scrollUp) {
40        $wrap.classList.add("scroll-up");
41        $wrap.classList.remove("scroll-down");
42      } else {
43        $wrap.classList.remove("scroll-down", "scroll-up");
44      }
45
46      this._lastScrollY = _scrollY;
47    }
48  },
49
50  getscrollY() {
51    return this._scrollY;
52  },
53
54  getScrollH() {
55    return this._scrollH;
56  },
57};
58
59// common
60const common = {
61  focusTrap(trap) {
62    const focusableElements = trap.querySelectorAll(`a, button, [tabindex="0"], input, textarea, select`);
63
64    if (!focusableElements.length) return;
65
66    const firstFocusableElement = focusableElements[0];
67    const lastFocusableElement = focusableElements[focusableElements.length - 1];
68
69    trap.addEventListener("keydown", (event) => {
70      if (event.key === "Tab") {
71        if (event.shiftKey && document.activeElement === firstFocusableElement) {
72          event.preventDefault();
73          lastFocusableElement.focus();
74        } else if (!event.shiftKey && document.activeElement === lastFocusableElement) {
75          event.preventDefault();
76          firstFocusableElement.focus();
77          // 모달 오픈 후 첫 초점 역방향 제어(modal-content가 첫초점이 아니면 사용 안해도 됨)
78        } else if (event.key === "Tab" && event.shiftKey && document.activeElement === trap) {
79          event.preventDefault();
80          lastFocusableElement.focus();
81        }
82      }
83    });
84  },
85};
86
87/*** * krds_mainMenuPC * ***/
88const krds_mainMenuPC = {
89  init() {
90    const gnbMenu = document.querySelector(".krds-main-menu:not(.sample) .gnb-menu");
91
92    if (!gnbMenu) return;
93
94    // gnb 속성설정
95    gnbMenu.setAttribute("aria-label", "메인 메뉴");
96
97    // dimed 요소를 설정, 기존 dimed가 없을 경우 생성
98    this.backdrop = document.querySelector(".gnb-backdrop") || this.createBackdrop();
99
100    // 주 메뉴 및 서브 메뉴의 트리거를 설정하고, 각 트리거에 이벤트를 연결
101    const mainTriggers = gnbMenu.querySelectorAll(".gnb-main-trigger");
102    const subTriggers = gnbMenu.querySelectorAll(".gnb-sub-trigger:not(.is-link)");
103    mainTriggers.forEach((mainTrigger) => this.setupMainTrigger(mainTrigger));
104    this.attachEvents(mainTriggers, subTriggers);
105    this.setupKeyboardNavigation(mainTriggers);
106  },
107  setupMainTrigger(mainTrigger) {
108    const toggleWrap = mainTrigger.nextElementSibling;
109    if (toggleWrap) {
110      const uniqueIdx = `gnb-main-menu-${Math.random().toString(36).substring(2, 9)}`;
111      mainTrigger.setAttribute("aria-controls", uniqueIdx);
112      mainTrigger.setAttribute("aria-expanded", "false");
113      mainTrigger.setAttribute("aria-haspopup", "true");
114      toggleWrap.setAttribute("id", uniqueIdx);
115
116      // 하위 메뉴 설정
117      const mainList = toggleWrap.querySelector(".gnb-main-list");
118      if (mainList?.getAttribute("data-has-submenu") === "true") {
119        const subTriggers = mainList.querySelectorAll(".gnb-sub-trigger");
120        subTriggers.forEach((subTrigger) => this.setupSubTrigger(subTrigger));
121        if (subTriggers.length > 0 && !subTriggers[0].classList.contains("is-link")) {
122          subTriggers[0].classList.add("active");
123          subTriggers[0].setAttribute("aria-expanded", "true");
124          subTriggers[0].nextElementSibling?.classList.add("active");
125        }
126      }
127    }
128  },
129  setupSubTrigger(subTrigger) {
130    const hasMenu = subTrigger.nextElementSibling;
131    if (hasMenu) {
132      const uniqueIdx = `gnb-sub-menu-${Math.random().toString(36).substring(2, 9)}`;
133      subTrigger.setAttribute("aria-controls", uniqueIdx);
134      subTrigger.setAttribute("aria-expanded", "false");
135      subTrigger.setAttribute("aria-haspopup", "true");
136      hasMenu.setAttribute("id", uniqueIdx);
137    }
138  },
139  toggleMainMenu(mainTrigger) {
140    const isActive = mainTrigger.classList.contains("active");
141    const isDropDown = mainTrigger.classList.contains("is-dropdown");
142    if (!isActive && mainTrigger.nextElementSibling) {
143      this.resetMainMenu();
144      mainTrigger.setAttribute("aria-expanded", "true");
145      mainTrigger.classList.add("active");
146      mainTrigger.nextElementSibling.classList.add("is-open");
147      if (!isDropDown) {
148        this.toggleBackdrop(true);
149        this.toggleScrollbar(true);
150        this.adjustSubMenuHeight(mainTrigger.nextElementSibling.querySelector(".gnb-main-list"));
151      }
152    } else {
153      this.closeMainMenu();
154    }
155  },
156  toggleSubMenu(subTrigger) {
157    const otherSubTriggers = subTrigger.closest("ul").querySelectorAll(".gnb-sub-trigger:not(.is-link)");
158    otherSubTriggers.forEach((trigger) => {
159      trigger.classList.remove("active");
160      trigger.setAttribute("aria-expanded", "false");
161      trigger.nextElementSibling?.classList.remove("active");
162    });
163    subTrigger.classList.add("active");
164    subTrigger.setAttribute("aria-expanded", "true");
165    subTrigger.nextElementSibling?.classList.add("active");
166    this.adjustSubMenuHeight(subTrigger.closest(".gnb-main-list"));
167  },
168  createBackdrop() {
169    const backdrop = document.createElement("div");
170    backdrop.classList.add("gnb-backdrop");
171    document.body.appendChild(backdrop);
172    // backdrop.style.display = "none";
173    return backdrop;
174  },
175  toggleBackdrop(isOpen) {
176    this.backdrop?.classList.toggle("active", isOpen);
177    document.body.classList.toggle("is-gnb-web", isOpen);
178    // this.backdrop.style.display = isOpen ? "block" : "none";
179  },
180  adjustSubMenuHeight(target) {
181    // 서브 메뉴 높이를 활성 메뉴에 맞춰 조정
182    const activeSubList = target.querySelector(".gnb-sub-list.active");
183    const height = activeSubList?.scrollHeight || 0;
184    target.style.minHeight = `${height}px`;
185  },
186  toggleScrollbar(isEnabled) {
187    const isScrollNeeded = document.body.scrollHeight > window.innerHeight;
188    document.body.classList.toggle("hasScrollY", isEnabled && isScrollNeeded);
189  },
190  resetMainMenu() {
191    document.querySelectorAll(".krds-main-menu:not(.sample) .gnb-main-trigger:not(.is-link)").forEach((mainTrigger) => {
192      mainTrigger.classList.remove("active");
193      mainTrigger.setAttribute("aria-expanded", "false");
194    });
195    document.querySelectorAll(".krds-main-menu:not(.sample) .gnb-toggle-wrap").forEach((toggleWrap) => {
196      toggleWrap.classList.remove("is-open");
197    });
198  },
199  closeMainMenu() {
200    this.resetMainMenu();
201    this.toggleBackdrop(false);
202    this.toggleScrollbar(false);
203  },
204  attachEvents(mainTriggers, subTriggers) {
205    // krds-main-menu 외부 클릭시 닫기
206    document.addEventListener("click", ({ target }) => {
207      if (!target.closest(".krds-main-menu")) this.closeMainMenu();
208    });
209
210    // 백드롭 클릭 시 메뉴 닫기
211    // this.backdrop.addEventListener("click", () => this.closeMainMenu());
212
213    // ESC 키를 눌러 메뉴를 닫거나, TAB 키로 초점이 메뉴 외부로 이동했을 때 메뉴 닫기
214    document.addEventListener("keyup", (event) => {
215      if (event.code === "Escape" || !event.target.closest(".krds-main-menu")) {
216        this.closeMainMenu();
217      }
218    });
219
220    // 메인 메뉴 트리거 설정
221    mainTriggers.forEach((mainTrigger) => {
222      mainTrigger.addEventListener("click", () => this.toggleMainMenu(mainTrigger));
223    });
224
225    // 서브 메뉴 트리거 설정
226    subTriggers.forEach((subTrigger) => {
227      subTrigger.addEventListener("click", () => this.toggleSubMenu(subTrigger));
228    });
229  },
230  setupKeyboardNavigation(mainTriggers) {
231    const focusMenuItem = (element) => {
232      if (element) {
233        element.focus();
234      }
235    };
236    const findFocusableElement = (element, direction) => {
237      const sibling = direction === "next" ? "nextElementSibling" : "previousElementSibling";
238      const parent = element.closest("li")?.[sibling];
239      return parent ? parent.querySelector("[data-trigger]") : null;
240    };
241    // Home, End, 방향키를 통해 메뉴 항목 간의 이동을 처리
242    document.addEventListener("keydown", (event) => {
243      const target = event.target;
244      if (target.getAttribute("data-trigger")) {
245        switch (event.key) {
246          case "Home":
247            event.preventDefault();
248            focusMenuItem(mainTriggers[0]);
249            break;
250          case "End":
251            event.preventDefault();
252            focusMenuItem(mainTriggers[mainTriggers.length - 1]);
253            break;
254          case "ArrowRight":
255          case "ArrowDown":
256            event.preventDefault();
257            const nextElement = findFocusableElement(target, "next");
258            focusMenuItem(nextElement);
259            break;
260          case "ArrowLeft":
261          case "ArrowUp":
262            event.preventDefault();
263            const previousElement = findFocusableElement(target, "prev");
264            focusMenuItem(previousElement);
265            break;
266          default:
267            break;
268        }
269      }
270    });
271  },
272};
273
274/*** * krds_mainMenuMobile * ***/
275const krds_mainMenuMobile = {
276  init() {
277    const mobileGnb = document.querySelector(".krds-main-menu-mobile:not(.sample)");
278
279    if (!mobileGnb) return;
280
281    if (mobileGnb.classList.contains("is-open")) {
282      this.openMainMenu(mobileGnb);
283    } else {
284      mobileGnb.style.display = "none";
285    }
286
287    // gnb 외부 클릭 처리
288    mobileGnb.addEventListener("click", (event) => {
289      if (!event.target.closest(".gnb-wrap")) {
290        mobileGnb.querySelector(".gnb-wrap").focus();
291      }
292    });
293
294    // 접근성 설정(tab)
295    this.setupAriaAttributes(mobileGnb);
296
297    this.attachEvents(mobileGnb);
298  },
299  setupAriaAttributes(mobileGnb) {
300    const tabList = mobileGnb.querySelector(".menu-wrap");
301    if (tabList) {
302      tabList.querySelector(".menu-wrap ul").setAttribute("role", "tablist");
303      tabList.querySelectorAll(".menu-wrap li").forEach((li) => li.setAttribute("role", "none"));
304
305      const tabs = document.querySelectorAll(".menu-wrap .gnb-main-trigger");
306      tabs.forEach((item, idx) => {
307        item.setAttribute("role", "tab");
308        item.setAttribute("aria-selected", "false");
309        item.setAttribute("aria-controls", item.getAttribute("href").substring(1));
310        item.setAttribute("id", `tab-${idx}`);
311
312        // gnb-main-trigger 클릭시 해당 위치로 스크롤
313        item.addEventListener("click", (event) => {
314          event.preventDefault();
315          const id = item.getAttribute("aria-controls");
316          const top = document.getElementById(id).offsetTop
317          const gnbBody = document.querySelector(".gnb-body");
318          gnbBody.scrollTo({
319            left: 0,
320            top: top,
321            behavior: "smooth",
322          });
323        })
324      });
325
326      const tabPanels = document.querySelectorAll(".submenu-wrap .gnb-sub-list");
327      tabPanels.forEach((item, idx) => {
328        item.setAttribute("role", "tabpanel");
329        item.setAttribute("aria-labelledby", `tab-${idx}`);
330      });
331    }
332  },
333  attachEvents(mobileGnb) {
334    const id = mobileGnb.getAttribute("id");
335    const openGnb = document.querySelector(`[aria-controls=${id}]`);
336    const closeGnb = mobileGnb.querySelector("#close-nav");
337
338    openGnb.addEventListener("click", () => this.openMainMenu(mobileGnb));
339    closeGnb.addEventListener("click", () => this.closeMainMenu(mobileGnb));
340    this.setupAnchorScroll(mobileGnb);
341    this.setupAnchorLinks(mobileGnb);
342
343    // 반응형 처리
344    window.addEventListener("resize", () => {
345      const isPC = windowSize.getWinSize() === "pc";
346      if (isPC) this.closeMainMenu(mobileGnb);
347    });
348  },
349  openMainMenu(mobileGnb) {
350    const navContainer = mobileGnb.querySelector(".gnb-wrap");
351    // const id = mobileGnb.getAttribute("id");
352    // const openGnb = document.querySelector(`[aria-controls=${id}]`);
353    // const header = document.querySelector("#krds-header");
354
355    mobileGnb.style.display = "block";
356    navContainer.setAttribute("tabindex", 0);
357    // openGnb.setAttribute("aria-expanded", "true");
358    // header.style.zIndex = "1000";
359
360    // active 메뉴로 스크롤 이동
361    const activeTrigger = document.querySelector(".gnb-main-trigger.active");
362    if (activeTrigger) {
363      const id = activeTrigger.getAttribute("aria-controls");
364      const top = document.getElementById(id).offsetTop;
365      const gnbBody = document.querySelector(".gnb-body");
366      gnbBody.style.scrollBehavior = "auto";
367      gnbBody.scrollTop = top
368    } 
369    
370    setTimeout(() => {
371      mobileGnb.classList.add("is-backdrop");
372      mobileGnb.classList.add("is-open");
373      document.body.classList.add("is-gnb-mobile");
374    }, 100);
375
376    // transition 종료후 실행
377    mobileGnb.addEventListener("transitionend", function onTransitionEnd() {
378      navContainer.focus();
379      mobileGnb.removeEventListener("transitionend", onTransitionEnd);
380
381      // inert 설정
382      document.querySelector("#krds-header .header-in").setAttribute("inert", "");
383      document.getElementById("container")?.setAttribute("inert", "");
384      document.getElementById("footer")?.setAttribute("inert", "");
385      
386      // 포커스 트랩 설정
387      common.focusTrap(mobileGnb);
388    });
389
390  },
391  closeMainMenu(mobileGnb) {
392    const id = mobileGnb.getAttribute("id");
393    const openGnb = document.querySelector(`[aria-controls=${id}]`);
394    // const header = document.querySelector("#krds-header");
395
396    mobileGnb.classList.remove("is-backdrop");
397    mobileGnb.classList.remove("is-open");
398    // openGnb.setAttribute("aria-expanded", "false");
399    // header.style.zIndex = "";
400
401    // inert 설정
402    document.querySelector("#krds-header .header-in").removeAttribute("inert");
403    document.getElementById("container")?.removeAttribute("inert");
404    document.getElementById("footer")?.removeAttribute("inert");
405    
406    // transition 종료후 실행
407    mobileGnb.addEventListener("transitionend", function onTransitionEnd() {
408      openGnb.focus();
409      mobileGnb.removeEventListener("transitionend", onTransitionEnd);
410    });
411    
412    setTimeout(() => {
413      mobileGnb.style.display = "none";
414      document.body.classList.remove("is-gnb-mobile");
415    }, 400);
416  },
417  setupAnchorScroll(mobileGnb) {
418    const gnbBody = mobileGnb.querySelector(".gnb-body");
419    const navContainer = mobileGnb.querySelector(".gnb-wrap");
420    const navItems = mobileGnb.querySelectorAll(".submenu-wrap .gnb-sub-list");
421    const headerTabArea = mobileGnb.querySelector(".gnb-tab-nav");
422    const headerTabMenu = headerTabArea?.querySelector(".menu-wrap");
423
424    gnbBody.addEventListener("scroll", () => {
425      const scrollTop = gnbBody.scrollTop;
426      const scrollHeight = gnbBody.scrollHeight;
427      const bodyHeight = gnbBody.clientHeight;
428
429      navItems.forEach((item) => {
430        const id = item.getAttribute("id");
431        const menuLink = mobileGnb.querySelector(`[href="#${id}"]`);
432        const offset = item.offsetTop;
433        if (scrollTop >= offset || bodyHeight + scrollTop >= scrollHeight) {
434          this.resetAnchorMenu();
435          menuLink.classList.add("active");
436          menuLink.setAttribute("aria-selected", "true");
437          if (headerTabArea) {
438            const headerTabMenuUl = headerTabMenu.querySelector("ul");
439            gnbBody.addEventListener("scrollend", () => {
440              headerTabMenuUl.scrollLeft = menuLink.offsetLeft;
441            });
442          }
443        }
444      });
445
446      this.handleTopScroll(headerTabArea, navContainer, gnbBody);
447    });
448  },
449  handleTopScroll(headerTabArea, navContainer, gnbBody) {
450    // gnb-mobile-type1(headerTabArea: gnb-tab-nav)
451    let lastBodyScrollY = 0;
452
453    if (!headerTabArea) return; // 요소가 없을 경우 함수 종료
454
455    gnbBody.addEventListener("scroll", (event) => {
456      const bodyScrollY = event.target.scrollTop;
457
458      if (bodyScrollY > 0) {
459        headerTabArea.style.height = `${headerTabArea.scrollHeight}px`;
460        headerTabArea.style.transition = "ease-out .4s";
461        navContainer.classList.add("is-active");
462      } else if (bodyScrollY < 50 && bodyScrollY < lastBodyScrollY) {
463        headerTabArea.style.height = "";
464        headerTabArea.style.transition = "ease-out .4s .2s";
465        setTimeout(() => {
466          navContainer.classList.remove("is-active");
467        }, 600);
468      }
469
470      lastBodyScrollY = bodyScrollY;
471    });
472  },
473  setupAnchorLinks(mobileGnb) {
474    const menuItems = mobileGnb.querySelectorAll(".menu-wrap .gnb-main-trigger");
475    const navItems = mobileGnb.querySelectorAll(".submenu-wrap .gnb-sub-list");
476
477    if (!document.querySelector(".menu-wrap .gnb-main-trigger.active")) {
478      menuItems[0].classList.add("active");
479      menuItems[0].setAttribute("aria-selected", "true");
480    }
481
482    // 3depth
483    navItems.forEach((item) => {
484      const depth3Items = item.querySelectorAll(".has-depth3");
485      if (depth3Items.length > 0) {
486        depth3Items.forEach((item) => {
487          if (item.classList.contains("active")) {
488            item.classList.add("active");
489            item.setAttribute("aria-expanded", "true");
490            item.nextElementSibling.classList.add("is-open");
491          } else {
492            item.setAttribute("aria-expanded", "false");
493          }
494          item.addEventListener("click", (event) => this.handleDepth3Click(event, item));
495        });
496      }
497    });
498
499    // 4depth
500    navItems.forEach((item) => {
501      const depth4Items = item.querySelectorAll(".has-depth4");
502      if (depth4Items.length > 0) {
503        depth4Items.forEach((item) => {
504          item.addEventListener("click", (event) => this.handleDepth4Click(event, item));
505        });
506      }
507    });
508  },
509  handleDepth3Click(event) {
510    const isActive = event.target.classList.contains("active");
511
512    const resetList = () => {
513      document.querySelectorAll(".has-depth3").forEach((depth3) => {
514        depth3.classList.remove("active");
515        depth3.setAttribute("aria-expanded", "false");
516        depth3.nextElementSibling.classList.remove("is-open");
517      });
518    };
519
520    if (!isActive) {
521      // resetList();
522      event.target.classList.add("active");
523      event.target.setAttribute("aria-expanded", "true");
524      event.target.nextElementSibling.classList.add("is-open");
525    } else {
526      // resetList();
527      event.target.classList.remove("active");
528      event.target.setAttribute("aria-expanded", "false");
529      event.target.nextElementSibling.classList.remove("is-open");
530    }
531  },
532  handleDepth4Click(event) {
533    const target = event.target.nextElementSibling;
534    const prevButton = target.querySelector(".trigger-prev");
535    const closeButton = target.querySelector(".trigger-close");
536
537    target.style.display = "block";
538    setTimeout(() => {
539      target.classList.add("is-open");
540    }, 0);
541    prevButton.focus();
542
543    const depth4Close = () => {
544      target.classList.remove("is-open");
545      event.target.focus();
546      setTimeout(() => {
547        target.style.display = "";
548      }, 400);
549    };
550
551    prevButton.addEventListener("click", depth4Close);
552    closeButton.addEventListener("click", depth4Close);
553
554    // 포커스 트랩 설정
555    common.focusTrap(target);
556  },
557  resetAnchorMenu() {
558    document.querySelectorAll(".krds-main-menu-mobile .menu-wrap .gnb-main-trigger").forEach((menu) => {
559      menu.classList.remove("active");
560      menu.setAttribute("aria-selected", "false");
561    });
562  },
563};
564
565/*** * krds_sideNavigation * ***/
566const krds_sideNavigation = {
567  init() {
568    this.setupSideNavLists();
569    this.setupToggleEvents();
570    this.setupPopupEvents();
571  },
572  setupSideNavLists() {
573    const sideNavLists = document.querySelectorAll(".krds-side-navigation .lnb-list");
574    sideNavLists.forEach((navList) => {
575      const navItems = navList.querySelectorAll("li");
576      navItems.forEach((navItem) => this.setupNavItem(navItem));
577    });
578  },
579  setupNavItem(navItem) {
580    const navButton = navItem.querySelector(".lnb-btn");
581    if (!navButton || !navButton.className.includes("lnb-toggle")) return;
582
583    const uniqueIdx = `lnbmenu-${Math.random().toString(36).substring(2, 9)}`;
584    const navSubmenu = navButton.nextElementSibling;
585
586    // aria 설정
587    navButton.setAttribute("aria-controls", uniqueIdx);
588    navButton.setAttribute("aria-expanded", navButton.classList.contains("active"));
589
590    // 서브메뉴 id 설정 및 popup 처리
591    if (navButton.classList.contains("lnb-toggle-popup")) {
592      navButton.setAttribute("aria-haspopup", "true");
593    }
594    if (navSubmenu && navSubmenu.className.includes("lnb-submenu")) {
595      const navSubmenuList = navSubmenu.classList.contains("lnb-submenu-lv2") ? navSubmenu : navSubmenu.querySelector(":scope > ul");
596      navSubmenuList?.setAttribute("id", uniqueIdx);
597    }
598  },
599  setupToggleEvents() {
600    const toggleButtons = document.querySelectorAll(".krds-side-navigation .lnb-list:not(.exception-case) .lnb-toggle");
601    toggleButtons.forEach((toggleButton) => {
602      toggleButton.addEventListener("click", () => {
603        const expand = toggleButton.getAttribute("aria-expanded") !== "true";
604        this.toggleMenu(toggleButton, expand);
605        this.closeSiblingMenus(toggleButton);
606      });
607    });
608  },
609  setupPopupEvents() {
610    let lastClickedButton = null;
611
612    const popupToggleButtons = document.querySelectorAll(".lnb-toggle-popup");
613    const popupSubmenus = document.querySelectorAll(".lnb-submenu-lv2");
614
615    // 팝업 토글 버튼
616    popupToggleButtons.forEach((button) => {
617      button.addEventListener("click", () => {
618        const popupSubmenu = button.nextElementSibling;
619        if (popupSubmenu && popupSubmenu.classList.contains("lnb-submenu-lv2")) {
620          popupSubmenu.classList.add("active");
621          button.setAttribute("aria-expanded", "true");
622
623          popupSubmenu.addEventListener(
624            "transitionend",
625            () => {
626              popupSubmenu.querySelector(".lnb-btn-tit")?.focus();
627            },
628            { once: true }
629          );
630
631          lastClickedButton = button;
632        }
633      });
634    });
635
636    // 서브 메뉴가 포커스를 잃으면 비활성화
637    popupSubmenus.forEach((popupSubmenu) => {
638      popupSubmenu.addEventListener("focusout", (event) => {
639        // 포커스가 서브메뉴 밖으로 나갔는지 확인
640        if (!popupSubmenu.contains(event.relatedTarget)) {
641          popupSubmenu.classList.remove("active");
642
643          if (lastClickedButton) {
644            lastClickedButton.setAttribute("aria-expanded", "false");
645            popupSubmenu.addEventListener(
646              "transitionend",
647              () => {
648                lastClickedButton.focus();
649              },
650              { once: true }
651            );
652          }
653        }
654      });
655
656      // lnb-btn-tit 클릭 시 서브메뉴 닫기
657      const subMenuTitleButton = popupSubmenu.querySelector(".lnb-btn-tit");
658      subMenuTitleButton?.addEventListener("click", () => {
659        lastClickedButton?.focus();
660      });
661    });
662  },
663  toggleMenu(toggleButton, expand) {
664    const parentListItem = toggleButton.closest("li");
665    toggleButton.setAttribute("aria-expanded", expand);
666    toggleButton.classList.toggle("active", expand);
667    parentListItem.classList.toggle("active", expand);
668  },
669  closeSiblingMenus(toggleButton) {
670    const parentListItem = toggleButton.closest("li");
671    const siblingButtons = parentListItem.parentNode.querySelectorAll(":scope > li > .lnb-toggle");
672    siblingButtons.forEach((siblingButton) => {
673      if (siblingButton !== toggleButton) {
674        this.toggleMenu(siblingButton, false);
675      }
676    });
677  },
678  setActiveCurrentPage() {
679    // 활성화된 페이지를 찾는 예(개발 환경에 맞ê²
679Œ 수정)
680    const currentPage = window.location.pathname.split("/").slice(-1)[0].replace(".html", "");
681    const lnbLinks = document.querySelectorAll(".krds-side-navigation .lnb-link");
682    lnbLinks.forEach((link) => {
683      const linkPage = link.getAttribute("href").split("/").pop().replace(".html", "");
684      if (linkPage === currentPage) {
685        link.closest(".lnb-item").classList.add("active");
686        link.closest(".lnb-item").querySelector(".lnb-toggle")?.classList.add("active");
687        link.closest(".lnb-item").querySelector(".lnb-toggle")?.setAttribute("aria-expanded", "true");
688        link.closest("li").classList.add("active");
689        // 접근성 현재 페이지 표시 aria-current
690        link.setAttribute("aria-current", "page");
691      }
692    });
693  },
694};
695
696/*** * krds_tab * ***/
697const krds_tab = {
698  layerTabArea: null,
699  init() {
700    this.layerTabArea = document.querySelectorAll(".krds-tab-area.layer");
701
702    if (!this.layerTabArea.length) return;
703
704    this.setupTabs();
705  },
706  setupTabs() {
707    this.layerTabArea.forEach((tabArea) => {
708      const layerTabs = tabArea.querySelectorAll(".tab > ul > li");
709
710      // 탭 설정
711      layerTabs.forEach((tab) => {
712        // 이미 이벤트가 연결된 탭을 건너뜀
713        if (!tab.dataset.listenerAttached) {
714          // 연결된 탭 패널 찾기
715          const control = tab.getAttribute("aria-controls");
716          const selectedTabPanel = document.getElementById(control);
717
718          // aria 설정
719          tab.setAttribute("aria-selected", "false");
720          tab.setAttribute("role", "tab");
721          selectedTabPanel.setAttribute("role", "tabpanel");
722
723          // 초기 active 설정
724          if (tab.classList.contains("active")) {
725            if (!tab.querySelector("button .sr-only")) {
726              tab.setAttribute("aria-selected", "true");
727              tab.querySelector("button").append(this.createAccText()); // 초점이 버튼이라 aria-selected 대체 텍스트 필요
728            }
729          }
730
731          // 클릭 이벤트
732          tab.addEventListener("click", () => {
733            const closestTabs = tab.closest(".krds-tab-area.layer > .tab").querySelectorAll("li");
734            const closestTabPanels = tab.closest(".krds-tab-area.layer").querySelectorAll(":scope > .tab-conts-wrap > .tab-conts");
735
736            this.resetTabs(closestTabs, closestTabPanels);
737
738            tab.classList.add("active");
739            tab.querySelector("button").append(this.createAccText());
740            tab.setAttribute("aria-selected", "true");
741            selectedTabPanel.classList.add("active");
742          });
743
744          // 키보드 이벤트
745          this.setupKeyboardNavigation(tab);
746
747          // 이벤트가 추가된 탭을 표시
748          tab.dataset.listenerAttached = "true";
749        }
750      });
751    });
752  },
753  createAccText() {
754    const tabAccTag = document.createElement("i");
755    tabAccTag.classList.add("sr-only");
756    tabAccTag.textContent = "선택됨";
757    return tabAccTag;
758  },
759  resetTabs(closestTabs, closestTabPanels) {
760    closestTabs.forEach((tab) => {
761      tab.classList.remove("active");
762      tab.setAttribute("aria-selected", "false");
763      // 대체 텍스트 삭제
764      const srOnly = tab.querySelector(".sr-only");
765      if (srOnly) tab.querySelector("button").removeChild(srOnly);
766    });
767    closestTabPanels.forEach((panel) => {
768      panel.classList.remove("active");
769    });
770  },
771  setupKeyboardNavigation(tab) {
772    tab.addEventListener("keydown", function (event) {
773      let newTab;
774      if (event.key === "ArrowRight") {
775        event.preventDefault();
776        newTab = tab.nextElementSibling?.querySelector("button");
777      } else if (event.key === "ArrowLeft") {
778        newTab = tab.previousElementSibling?.querySelector("button");
779      }
780      newTab?.focus();
781    });
782  },
783};
784
785/*** * krds_accordion  수정버전* ***/
786const krds_accordion = {
787  accordionButtons: null,
788  accordionHandlers: new Map(),
789  init(mode) { // mode: 'admin' or 'front'
790    this.accordionButtons = document.querySelectorAll(".btn-accordion");
791    if (!this.accordionButtons.length) return;
792    this.setupAccordions(mode);
793  },
794  accordionToggle(button, accordionItems, accordionType, currentItem) {
795    const isExpanded = button.getAttribute("aria-expanded") === "true";
796    if (accordionType !== "multiOpen" && !currentItem.classList.contains("active")) {
797      accordionItems.forEach((otherItem) => {
798        const otherButton = otherItem.querySelector(".btn-accordion");
799        otherButton.setAttribute("aria-expanded", "false");
800        otherButton.classList.remove("active");
801        otherItem.classList.remove("active");
802      });
803    }
804    button.setAttribute("aria-expanded", !isExpanded);
805    button.classList.toggle("active", !isExpanded);
806    currentItem.classList.toggle("active", !isExpanded);
807  },
808  setupAccordions(mode) {
809    this.accordionButtons.forEach((button, idx) => {
810      const accordionContainer = button.closest(".krds-accordion");
811      const accordionItems = accordionContainer.querySelectorAll(".accordion-item");
812      const currentItem = button.closest(".accordion-item");
813      const accordionContent = currentItem.querySelector(".accordion-collapse");
814      const accordionType = accordionContainer.dataset.type || "singleOpen";
815      this.setupAriaAttributes(button, accordionContent, idx);
816
817      // 초기 상태 설정
818      if (mode === 'allclose') {
819        button.setAttribute("aria-expanded", "false");
820        button.classList.remove("active");
821        currentItem.classList.remove("active");
822      } else if (mode === 'allopen') {
823        button.setAttribute("aria-expanded", "true");
824        button.classList.add("active");
825        currentItem.classList.add("active");
826      } else if (mode === 'front') {
827        if (idx === 0) {
828          button.setAttribute("aria-expanded", "true");
829          button.classList.add("active");
830          currentItem.classList.add("active");
831        } else {
832          button.setAttribute("aria-expanded", "false");
833          button.classList.remove("active");
834          currentItem.classList.remove("active");
835        }
836      }
837
838      let toggleHandler = this.accordionHandlers.get(button);
839      if (!toggleHandler) {
840        toggleHandler = this.accordionToggle.bind(this, button, accordionItems, accordionType, currentItem);
841        this.accordionHandlers.set(button, toggleHandler);
842      }
843      button.removeEventListener("click", toggleHandler);
844      button.addEventListener("click", toggleHandler);
845    });
846  },
847  setupAriaAttributes(button, accordionContent, idx) {
848    // const uniqueIdx = `${idx}${Math.random().toString(36).substring(2, 9)}`;
849    const uniqueIdx = idx.toString().padStart(3, "0");;
850    button.setAttribute("aria-expanded", "false");
851    button.setAttribute("id", `accordionHeader-id-${uniqueIdx}`);
852    button.setAttribute("aria-controls", `accordionCollapse-id-${uniqueIdx}`);
853    accordionContent.setAttribute("role", "region");
854    accordionContent.setAttribute("id", `accordionCollapse-id-${uniqueIdx}`);
855    accordionContent.setAttribute("aria-labelledby", `accordionHeader-id-${uniqueIdx}`);
856  },
857};
858
859/*** * krds_modal * ***/
860const krds_modal = {
861  modalOpenTriggers: null,
862  modalCloseTriggers: null,
863  outsideClickHandlers: {},
864  init() {
865    this.modalOpenTriggers = document.querySelectorAll(".open-modal");
866    this.modalCloseTriggers = document.querySelectorAll(".close-modal");
867
868    if (!this.modalOpenTriggers.length || !this.modalCloseTriggers.length) return;
869
870    this.setupTriggers();
871  },
872  setupTriggers() {
873    // 모달 열기 이벤트 설정
874    this.modalOpenTriggers.forEach((trigger) => {
875      trigger.addEventListener("click", (event) => {
876        event.preventDefault();
877        const modalId = trigger.getAttribute("data-target");
878
879        if (modalId) {
880          // aria 설정
881          trigger.setAttribute("data-modal-id", modalId);
882          trigger.classList.add("modal-opened");
883          trigger.setAttribute("tabindex", "-1");
884
885          this.openModal(modalId);
886        }
887      });
888    });
889    // 모달 닫기 이벤트 설정
890    this.modalCloseTriggers.forEach((trigger) => {
891      trigger.addEventListener("click", (event) => {
892        event.preventDefault();
893        const modalId = trigger.closest(".krds-modal").getAttribute("id");
894
895        if (modalId) {
896          this.closeModal(modalId);
897        }
898      });
899    });
900  },
901  openModal(id) {
902    const modalElement = document.getElementById(id);
903    const dialogElement = modalElement.querySelector(".modal-content");
904    const modalBack = modalElement.querySelector(".modal-back");
905    // const modalTitle = modalElement.querySelector(".modal-title");
906    const modalConts = modalElement.querySelector(".modal-conts");
907
908    document.querySelector("body").classList.add("scroll-no");
909    dialogElement.removeAttribute("tabindex");
910    modalElement.setAttribute("role", "dialog");
911    modalElement.classList.add("shown");
912    modalBack.classList.add("in");
913    // modalTitle.setAttribute("tabindex", "0");
914
915    // modal-conts 스크롤 일때 tabindex 처리
916    if (modalConts.scrollHeight > modalConts.clientHeight) {
917      modalConts.setAttribute("tabindex", "0");
918    } else {
919      modalConts.removeAttribute("tabindex");
920    }
921
922    // css transition 딜레이
923    setTimeout(() => {
924      modalElement.classList.add("in");
925    }, 150);
926    
927    //열린 팝업창 포커스
928    const focusables = modalElement.querySelectorAll(`a, button, [tabindex="0"], input, textarea, select`);
929    setTimeout(() => {
930      // modalTitle.focus();
931      focusables[0].focus();
932    }, 350);
933
934    // ESC 모달 닫기
935    dialogElement.addEventListener(
936      "keydown",
937      (event) => {
938        if (event.key === "Escape" || event.key === "Esc") {
939          this.closeModal(dialogElement.closest(".krds-modal").id
939);
940        }
941      },
942      { once: true }
943    );
944
945    // 모달 외부 클릭 처리 핸들러 정의 및 저장
946    if (!this.outsideClickHandlers[id]) {
947      this.outsideClickHandlers[id] = (event) => {
948        if (!event.target.closest(".modal-content")) {
949          // modalTitle.focus();
950          focusables[0].focus();
951
952          // dialogElement.focus();
953          // this.closeModal(id);
954        }
955      };
956    }
957    // 이벤트 리스너 제거 후 다시 등록
958    modalElement.removeEventListener("click", this.outsideClickHandlers[id]);
959    modalElement.addEventListener("click", this.outsideClickHandlers[id]);
960
961    // 포커스 트랩 설정
962    common.focusTrap(dialogElement);
963
964    // 2개 이상의 모달이 열려 있는 경우 z-index 업데이트
965    this.updateZIndex(modalElement);
966
967    // inert 설정
968    document.getElementById("wrap")?.setAttribute("inert", "");
969  },
970  closeModal(id) {
971    const modalElement = document.getElementById(id);
972    const openModals = document.querySelectorAll(".modal.in:not(.sample)");
973    const modalBack = modalElement.querySelector(".modal-back");
974
975    modalElement.classList.remove("in");
976    modalBack.classList.remove("in");
977
978    // css transition 딜레이
979    setTimeout(() => {
980      modalElement.classList.remove("shown");
981    }, 350);
982
983    // 마지막 모달이 닫힐 때 페이지 스크롤 복원
984    if (openModals.length < 2) {
985      document.querySelector("body").classList.remove("scroll-no");
986    }
987    
988    // inert 설정
989    document.getElementById("wrap")?.removeAttribute("inert");
990
991    // 모달을 열었던 버튼으로 포커스 복귀
992    this.returnFocusToTrigger(id);
993  },
994  updateZIndex(modalElement) {
995    const openModals = document.querySelectorAll(".modal.in:not(.sample)");
996    const openModalsLengtn = openModals.length + 1;
997    const newZIndex = 1010 + openModalsLengtn;
998    if (openModalsLengtn > 1) {
999      modalElement.style.zIndex = newZIndex;
1000      modalElement.querySelector(".modal-back").classList.remove("in");
1001    }
1002  },
1003  returnFocusToTrigger(id) {
1004    const triggerButton = document.querySelector(`.modal-opened[data-modal-id="${id}"]`);
1005    if (triggerButton) {
1006      triggerButton.focus();
1007      triggerButton.setAttribute("tabindex", "0");
1008      triggerButton.classList.remove("modal-opened");
1009      triggerButton.removeAttribute("data-modal-id");
1010    }
1011  },
1012};
1013
1014/*** * krds_contextualHelp * ***/
1015const krds_contextualHelp = {
1016  tooltipButtons: null,
1017  init() {
1018    this.tooltipButtons = document.querySelectorAll(".krds-contextual-help .tooltip-btn");
1019
1020    if (!this.tooltipButtons.length) return;
1021
1022    this.setupTooltips();
1023    this.setupFocusOutEvent();
1024  },
1025  setupTooltips() {
1026    this.tooltipButtons.forEach((button) => {
1027      const tooltipContainer = button.closest(".krds-contextual-help");
1028      const tooltipPopover = tooltipContainer.querySelector(".tooltip-popover");
1029      const closeButton = tooltipPopover.querySelector(".tooltip-close");
1030
1031      button.setAttribute("aria-expanded", "false");
1032      tooltipPopover.setAttribute("role", "tooltip");
1033
1034      // tooltipWrap에 포지션이 없을때 기본값 설정
1035      if (tooltipContainer && tooltipContainer.classList.length === 1) {
1036        tooltipContainer.classList.add("top", "left");
1037      }
1038
1039      button.addEventListener("click", () => {
1040        this.toggleTooltip(button, tooltipPopover, tooltipContainer);
1041      });
1042      closeButton.addEventListener("click", () => {
1043        this.closeAllTooltips();
1044      });
1045
1046      window.addEventListener("resize", () => {
1047        this.adjustTooltipPosition(tooltipContainer, tooltipPopover);
1048      });
1049
1050      // ESC 닫기
1051      document.addEventListener("keydown", (event) => {
1052        if (event.key === "Escape" || event.key === "Esc") {
1053          this.closeAllTooltips();
1054        }
1055      });
1056    });
1057  },
1058  toggleTooltip(button, tooltipPopover, tooltipContainer) {
1059    const isVisible = tooltipPopover.style.display === "block";
1060
1061    this.closeAllTooltips();
1062
1063    if (!isVisible) {
1064      tooltipPopover.style.display = "block";
1065      const focusables = tooltipPopover.querySelector(`a, button, [tabindex="0"], input, textarea, select`);
1066      focusables?.focus();
1067      button.setAttribute("aria-expanded", "true");
1068
1069      this.adjustTooltipPosition(tooltipContainer, tooltipPopover);
1070    }
1071  },
1072  closeAllTooltips() {
1073    const otherPopovers = document.querySelectorAll(".krds-contextual-help .tooltip-popover");
1074    otherPopovers.forEach((popover) => {
1075      popover.style.display = "none";
1076    });
1077    this.tooltipButtons.forEach((button) => {
1078      button.setAttribute("aria-expanded", "false");
1079    });
1080  },
1081  adjustTooltipPosition(tooltipContainer, tooltipPopover) {
1082    // const isMobile = windowSize.getWinSize() === "mob";
1083    const isMobile = window.innerWidth <= 768;
1084    const tooltipAction = tooltipContainer.querySelector(".tooltip-action");
1085
1086    if (isMobile) {
1087      const rootStyles = getComputedStyle(document.querySelector(":root"));
1088      const contentsPaddingX = rootStyles.getPropertyValue("--krds-contents-padding-x").trim().split("px")[0];
1089      const tooltipActionRect = tooltipAction.getBoundingClientRect();
1090      const offsetLeft = tooltipActionRect.left - contentsPaddingX;
1091      const width = document.body.clientWidth - (contentsPaddingX * 2);
1092      tooltipPopover.style.left = `-${offsetLeft}px`;
1093      tooltipPopover.style.width = `${width}px`;
1094    } else {
1095      tooltipPopover.style.left = "";
1096      tooltipPopover.style.right = "";
1097      tooltipPopover.style.width = "360px";
1098    }
1099  },
1100  setupFocusOutEvent() {
1101    document.addEventListener("click", (event) => {
1102      const clickedInsideTooltip = event.target.closest(".tooltip-action");
1103      if (!clickedInsideTooltip) {
1104        this.closeAllTooltips();
1105      } else {
1106        const FocusPopover = clickedInsideTooltip.querySelector(".tooltip-popover");
1107        FocusPopover.addEventListener("focusout", (event) => {
1108          if (FocusPopover.contains(event.relatedTarget)) {
1109            return;
1110          }
1111          this.closeAllTooltips();
1112          clickedInsideTooltip.querySelector(".tooltip-btn")?.focus();
1113        });
1114      }
1115    });
1116  },
1117};
1118
1119/*** * krds_tooltip * ***/
1120const krds_tooltip = {
1121  tooltip: null,
1122  isMobile: null,
1123  init() {
1124    this.tooltip = document.querySelectorAll(".krds-tooltip");
1125    this.isMobile = windowSize.getWinSize() === "mob";
1126
1127    if (!this.tooltip.length) return;
1128
1129    this.setupTooltips();
1130    this.setupGlobalEvents();
1131  },
1132  setupTooltips() {
1133    this.tooltip.forEach((item, index) => {
1134      //  tooltipText
1135      const tooltipText = item.getAttribute("data-tooltip");
1136      const disabled = item.hasAttribute("disabled");
1137
1138      if (!tooltipText || disabled) return;
1139
1140      // ID 부여
1141      const uniqueIdx = `tooltip-popover-${index}${Math.random().toString(36).substring(2, 9)}`;
1142      item.setAttribute("aria-labelledby", uniqueIdx);
1143
1144      // TooltipPopover 생성
1145      const tooltipBtnText = item.innerText;
1146      const tooltipPopover = this.createTooltipPopover(uniqueIdx, tooltipBtnText, tooltipText);
1147      item.parentNode.insertBefore(tooltipPopover, item.nextSibling);
1148
1149      // Show/Hide 함수 정의
1150      const showTooltip = () => this.showTooltip(item, tooltipPopover);
1151
1152      // 이벤트 등록
1153      this.registerEvents(item, showTooltip);
1154
1155      // ESC 닫기
1156      document.addEventListener("keydown", (event) => {
1157        if (event.key === "Escape" || event.key === "Esc") {
1158          this.closeAllTooltips();
1159        }
1160      });
1161    });
1162  },
1163  createTooltipPopover(uniqueIdx, tooltipBtnText, tooltipText) {
1164    const tooltipPopover = document.createElement("div");
1165    tooltipPopover.classList.add("krds-tooltip-popover");
1166    tooltipPopover.setAttribute("id", uniqueIdx);
1167    tooltipPopover.setAttribute("aria-hidden", "true");
1168    tooltipPopover.innerHTML = "";
1169    tooltipPopover.innerHTML = `
1170      <span class="sr-only">${tooltipBtnText}</span>
1171      ${tooltipText}
1172    `;
1173
1174    return tooltipPopover;
1175  },
1176  registerEvents(item, showTooltip) {
1177    item.addEventListener("mouseover", showTooltip);
1178    item.addEventListener("mouseout", this.closeAllTooltips);
1179    item.addEventListener("focus", showTooltip);
1180    item.addEventListener("focusout", this.closeAllTooltips);
1181  },
1182  showTooltip(item, tooltipPopover) {
1183    // tooltip-box
1184    if (item.classList.contains("tooltip-box")) {
1185      tooltipPopover.classList.add("tooltip-box");
1186    }
1187    // tooltip-vertical
1188    if (item.classList.contains("tooltip-vertical")) {
1189      tooltipPopover.classList.add("tooltip-vertical");
1190    }
1191
1192    tooltipPopover.classList.add("active");
1193
1194    const { top, left } = this.calculateTooltipPosition(item, tooltipPopover);
1195    const mobileSmall = window.innerWidth <= 420;
1196    tooltipPopover.style.top = `${top}px`;
1197    tooltipPopover.style.left = mobileSmall ? "50%" : `${left}px`;
1198  },
1199  closeAllTooltips() {
1200    const otherPopovers = document.querySelectorAll(".krds-tooltip-popover");
1201    otherPopovers.forEach((popover) => {
1202      if (!popover.classList.contains("active")) return;
1203      popover.style = "";
1204      popover.className = "krds-tooltip-popover";
1205    });
1206  },
1207  calculateTooltipPosition(item, tooltipPopover) {
1208    // 툴팁과 기준 요소 간격
1209    const tooltipGap = 12;
1210    const { clientHeight: tooltipHeight, clientWidth: tooltipWidth } = tooltipPopover;
1211    const { top: itemTop, left: itemLeft, right: itemRight, height: itemHeight, width: itemWidth } = item.getBoundingClientRect();
1212    const halfWindowWidth = window.innerWidth / 2;
1213    const halfWindowHeight = window.innerHeight / 2;
1214
1215    let tooltipTop;
1216    let tooltipLeft;
1217
1218    const isVertical = this.isMobile || item.classList.contains("tooltip-box") || item.classList.contains("tooltip-vertical");
1219
1220    if (isVertical) {
1221      if (itemTop + itemHeight > halfWindowHeight) {
1222        tooltipTop = itemTop - tooltipHeight - tooltipGap; // 위쪽
1223        tooltipPopover.classList.add("top");
1224      } else {
1225        tooltipTop = itemTop + itemHeight + tooltipGap; // 아래쪽
1226        tooltipPopover.classList.add("bottom");
1227      }
1228      // 좌우 위치
1229      if (itemLeft + itemWidth > halfWindowWidth) {
1230        tooltipLeft = itemRight - tooltipWidth; // 오른쪽 정렬
1231        tooltipPopover.classList.add("right");
1232        // 화면 오른쪽 여유 공간이 충분할 때 가운데 정렬
1233        if (window.innerWidth - (itemLeft + itemWidth) > tooltipWidth / 2) {
1234          tooltipLeft = itemLeft + (itemWidth - tooltipWidth) / 2;
1235          tooltipPopover.classList.remove("right");
1236        }
1237      } else {
1238        // 화면 왼쪽 여유 공간이 충분할 때 가운데 정렬
1239        tooltipLeft = itemLeft + (itemWidth - tooltipWidth) / 2;
1240        // 왼쪽 공간 부족 시 보정
1241        if (tooltipLeft < 0) {
1242          tooltipLeft = itemLeft;
1243          tooltipPopover.classList.add("left");
1244        } else {
1245          tooltipPopover.classList.remove("left");
1246        }
1247      }
1248    } else {
1249      // 가로형 툴팁
1250      tooltipTop = itemTop + (itemHeight - tooltipHeight) / 2;
1251      if (itemLeft + itemWidth > halfWindowWidth) {
1252        tooltipLeft = itemLeft - tooltipWidth - tooltipGap; // 왼쪽
1253        tooltipPopover.classList.add("right");
1254      } else {
1255        tooltipLeft = itemRight + tooltipGap; // 오른쪽
1256        tooltipPopover.classList.remove("right");
1257      }
1258    }
1259    return { top: tooltipTop, left: tooltipLeft };
1260  },
1261  setupGlobalEvents() {
1262    window.addEventListener("scroll", () => {
1263      this.closeAllTooltips();
1264    });
1265    window.addEventListener("resize", () => {
1266      this.isMobile = windowSize.getWinSize() === "mob";
1267      this.closeAllTooltips();
1268    });
1269  },
1270};
1271
1272/*** * krds_calendar * ***/ // *btn-set-date: aria-pressed > aria-selected 변경 해야함(상위 속성도 같이)
1273const krds_calendar = {
1274  datePickerArea: null,
1275  activeDateSelector: null,
1276  init() {
1277    this.datePickerArea = document.querySelectorAll(".krds-calendar-area");
1278
1279    if (!this.datePickerArea.length) return;
1280
1281    this.setupDatePicker();
1282    this.setupGlobalListeners();
1283    this.setupOpenCloseEvents();
1284    this.setupDateComboBox("init");
1285    this.toggleDateSelector();
1286    this.actionDatePicker();
1287
1288    // ui 동작 확인용 테스트 코드
1289    this.example();
1290  },
1291  setupDatePicker() {
1292    this.datePickerArea.forEach((datePicker) => {
1293      datePicker.querySelector(".calendar-wrap").setAttribute("tabindex", "0");
1294
1295      // 접근성 초기값 설정
1296      datePicker.querySelectorAll(".calendar-tbl td").forEach((cell) => {
1297        const dateButton = cell.querySelector(".btn-set-date");
1298        if (!dateButton) return;
1299        if (cell.classList.contains("period")) {
1300          dateButton.setAttribute("aria-pressed", "true");
1301        }
1302        if (cell.classList.contains("day-event")) {
1303          dateButton.setAttribute("aria-label", `${dateButton.innerText} 일정있음`);
1304        }
1305        if (cell.classList.contains("today")) {
1306          dateButton.setAttribute("aria-label", `${dateButton.innerText} 오늘`);
1307        }
1308      });
1309    });
1310  },
1311  setupGlobalListeners() {
1312    document.addEventListener("click", (event) => {
1313      if (!event.target.closest(".calendar-conts")) {
1314        this.closeAllDatePickers();
1315      }
1316    });
1317  },
1318  setupOpenCloseEvents() {
1319    const datePickerButtons = document.querySelectorAll(".form-btn-datepicker");
1320    datePickerButtons.forEach((button) => {
1321      button.addEventListener("click", () => this.openDatePicker(button));
1322    });
1323
1324    // 탭 이동 닫기
1325    this.datePickerArea.forEach((datePicker) => {
1326      // 마지막 버튼이 blur될 때 datepicker 닫기(포커스 트랩ì
1326„ 사용하지 않을때 적용)
1327      // const lastActionButton = datePicker.querySelector(".calendar-btn-wrap .krds-btn:last-child");
1328      // if (lastActionButton) {
1329      //   lastActionButton.addEventListener("blur", () => this.closeAllDatePickers());
1330      // }
1331
1332      // calendar-select의 마지막 버튼이 blur될 때 선택기 닫기
1333      datePicker.querySelectorAll(".calendar-select").forEach((select) => {
1334        const lastSelectButton = select.querySelector(".sel > li:last-child > button");
1335        if (lastSelectButton) {
1336          lastSelectButton.addEventListener("blur", () => {
1337            this.resetDateSelector();
1338          });
1339        }
1340      });
1341    });
1342  },
1343  openDatePicker(button) {
1344    // 열려있던 달력 모두 닫기
1345    this.closeAllDatePickers();
1346
1347    const currentDatePicker = button.closest(".calendar-conts").querySelector(".krds-calendar-area");
1348    currentDatePicker.classList.add("active");
1349
1350    // 접근성
1351    button.setAttribute("aria-expanded", "true");
1352
1353    // 포커스 트랩 설정
1354    common.focusTrap(currentDatePicker);
1355
1356    // 포커스 이동
1357    setTimeout(() => {
1358      currentDatePicker.querySelector(".calendar-wrap").focus();
1359    }, 50);
1360  },
1361  resetDateSelector() {
1362    this.datePickerArea.forEach((datePicker) => {
1363      const selectMenus = datePicker.querySelectorAll(".calendar-select");
1364      selectMenus.forEach((select) => select.classList.remove("active"));
1365      this.setupDateComboBox("reset");
1366      this.activeDateSelector = null;
1367    });
1368  },
1369  closeAllDatePickers() {
1370    this.datePickerArea.forEach((datePicker) => {
1371      if (datePicker.classList.contains("active")) {
1372        const target = datePicker.closest(".calendar-conts").querySelector(".form-btn-datepicker");
1373        target.focus();
1374      }
1375      datePicker.classList.remove("active");
1376      this.resetDateSelector();
1377    });
1378
1379    // 접근성
1380    const datePickerButtons = document.querySelectorAll(".form-btn-datepicker");
1381    datePickerButtons.forEach((button) => {
1382      button.setAttribute("aria-expanded", "false");
1383    });
1384  },
1385  setupDateComboBox(option, target) {
1386    const comboBoxs = document.querySelectorAll(".calendar-drop-down > button");
1387    if (option === "init") {
1388      comboBoxs.forEach((comboBox, idx) => {
1389        const listBox = comboBox.nextElementSibling.querySelector(".sel");
1390        const listOptions = listBox.querySelectorAll("button");
1391        const uniqueIdx = `${idx}${Math.random().toString(36).substring(2, 9)}`;
1392
1393        comboBox.setAttribute("role", "combobox");
1394        comboBox.setAttribute("aria-haspopup", "listbox");
1395        comboBox.setAttribute("aria-expanded", "false");
1396        comboBox.setAttribute("aria-controls", `combo-list-${uniqueIdx}`);
1397
1398        listBox.setAttribute("role", "listbox");
1399        listBox.setAttribute("id", `combo-list-${uniqueIdx}`);
1400        listBox.querySelectorAll("li").forEach((li) => li.setAttribute("role", "none"));
1401
1402        listOptions.forEach((listOption) => {
1403          listOption.setAttribute("role", "option");
1404          listOption.setAttribute("aria-selected", "false");
1405          if (listOption.classList.contains("active")) {
1406            listOption.setAttribute("aria-selected", "true");
1407            comboBox.innerHTML = listOption.innerHTML;
1408          }
1409        });
1410      });
1411    }
1412    if (option === "reset") {
1413      comboBoxs.forEach((comboBox) => {
1414        comboBox.setAttribute("aria-expanded", "false");
1415      });
1416      if (target) {
1417        target.setAttribute("aria-expanded", "true");
1418      }
1419    }
1420    if (option === "change") {
1421      comboBoxs.forEach((comboBox) => {
1422        const listBox = comboBox.nextElementSibling.querySelector(".sel");
1423        const listOptions = listBox.querySelectorAll("button");
1424
1425        comboBox.setAttribute("aria-expanded", "false");
1426
1427        listOptions.forEach((listOption) => {
1428          listOption.classList.remove("active");
1429          listOption.setAttribute("aria-selected", "false");
1430        });
1431      });
1432      if (target) {
1433        target.setAttribute("aria-selected", "true");
1434        target.classList.add("active");
1435        target.closest(".calendar-drop-down").querySelector("button").innerHTML = target.innerHTML;
1436      }
1437
1438      // ui 동작 확인용 테스트 코드
1439      this.example();
1440    }
1441  },
1442  toggleDateSelector() {
1443    this.datePickerArea.forEach((datePicker) => {
1444      const selectToggleButtons = datePicker.querySelectorAll(".calendar-drop-down .btn-cal-switch");
1445      const selectOptions = datePicker.querySelectorAll(".calendar-select .sel button");
1446
1447      // 공통 이벤트 처리
1448      const handleBtnClick = (event, selectMenu) => {
1449        const layer = selectMenu;
1450
1451        // 이미 활성화된 레이어 닫기
1452        if (this.activeDateSelector === layer) {
1453          layer.classList.remove("active");
1454          this.setupDateComboBox("reset");
1455          this.activeDateSelector = null;
1456          return;
1457        }
1458
1459        // 현재 열려 있는 레이어가 있으면 닫음
1460        if (this.activeDateSelector) {
1461          this.activeDateSelector.classList.remove("active");
1462          this.setupDateComboBox("reset");
1463        }
1464
1465        // 클릭한 버튼에 해당하는 레이어 열기
1466        layer.classList.add("active");
1467        this.setupDateComboBox("reset", event.target);
1468        this.activeDateSelector = layer;
1469      };
1470
1471      // 셀렉터 설정
1472      selectToggleButtons.forEach((toggle) => {
1473        toggle.addEventListener("click", (event) => {
1474          const selectMenu = event.target.closest(".calendar-drop-down").querySelector(".calendar-select");
1475          handleBtnClick(event, selectMenu);
1476        });
1477      });
1478
1479      // 년도, 월 선택
1480      selectOptions.forEach((option) => {
1481        option.addEventListener("click", (event) => {
1482          this.resetDateSelector();
1483          this.setupDateComboBox("change", event.target);
1484
1485          // 포커스 이동
1486          setTimeout(() => {
1487            option.closest(".calendar-drop-down").querySelector(".btn-cal-switch")?.focus();
1488          }, 50);
1489        });
1490      });
1491
1492      // esc 닫기
1493      datePicker.addEventListener("keydown", (event) => {
1494        if (event.code === "Escape") {
1495          this.resetDateSelector();
1496        }
1497      });
1498    });
1499  },
1500  actionDatePicker() {
1501    this.datePickerArea.forEach((datePicker) => {
1502      const actionButtons = datePicker.querySelectorAll(".calendar-btn-wrap button:not(#get-today)");
1503      actionButtons.forEach((button) => {
1504        button.addEventListener("click", () => {
1505          this.closeAllDatePickers();
1506        });
1507      });
1508    });
1509  },
1510  // ui 동작 확인용 테스트 코드
1511  example() {
1512    this.datePickerArea.forEach((datePicker) => {
1513      const year = datePicker.querySelector(".calendar-switch-wrap .year").innerText.slice(0, -1);
1514      const month = datePicker.querySelector(".calendar-switch-wrap .month").innerText.slice(0, -1);
1515      const caption = datePicker.querySelector(".calendar-tbl caption");
1516      const tblCells = datePicker.querySelectorAll(".calendar-tbl td");
1517      const tblCellBtns = datePicker.querySelectorAll(".calendar-tbl td .btn-set-date");
1518      const actionBtns = datePicker.querySelectorAll(".calendar-btn-wrap button");
1519      let clickCount = 0;
1520      let startTd = null;
1521
1522      // 캡션 설정
1523      caption.innerHTML = `${year}년 ${month}월`;
1524
1525      // 테스트용 (실제 구현에서는 날짜 배열을 받아 처리함)
1526      tblCells.forEach((cell) => {
1527        const day = cell.querySelector(".btn-set-date").innerText.padStart(2, "0");
1528        let [numberYear, numberMonth] = [parseFloat(year), parseFloat(month)];
1529        if (cell.classList.contains("old")) {
1530          if (numberMonth === 1) {
1531            numberYear -= 1;
1532            numberMonth = 12;
1533          } else {
1534            numberMonth -= 1;
1535          }
1536        } else if (cell.classList.contains("new")) {
1537          if (numberMonth === 12) {
1538            numberYear += 1;
1539            numberMonth = 1;
1540          } else {
1541            numberMonth += 1;
1542          }
1543        }
1544        cell.setAttribute("data-date", `${numberYear}.${String(numberMonth).padStart(2, "0")}.${day}`);
1545      });
1546
1547      // action
1548      const accReset = (action, btn, type) => {
1549        // 인풋 단일
1550        const targetInput = btn.closest(".calendar-conts").querySelector("input.datepicker.cal");
1551        // 인풋 분할
1552        const targetInputStart = btn.closest(".calendar-conts").querySelector(".input-group.range.set li:first-child input.datepicker");
1553        const targetInputEnd = btn.closest(".calendar-conts").querySelector(".input-group.range.set li:last-child input.datepicker");
1554
1555        action.addEventListener("click", () => {
1556          if (targetInput) {
1557            targetInput.setAttribute("type", "text");
1558            const target = action.innerText;
1559            if (target === "오늘") {
1560              accSet();
1561            }
1562            if (target === "확인") {
1563              if (type === "single") {
1564                const value = action.closest(".krds-calendar-area").querySelector("td.period.start.end").getAttribute("data-date");
1565                targetInput.value = value;
1566              } else {
1567                const value1 = action.closest(".krds-calendar-area").querySelector("td.period.start")?.getAttribute("data-date");
1568                const value2 = action.closest(".krds-calendar-area").querySelector("td.period.end")?.getAttribute("data-date") || "";
1569                targetInput.value = `${value1} ~ ${value2}`;
1570              }
1571            }
1572          } else {
1573            targetInputStart.setAttribute("type", "text");
1574            targetInputEnd.setAttribute("type", "text");
1575            const target = action.innerText;
1576            if (target === "오늘") {
1577              accSet();
1578            }
1579            if (target === "확인") {
1580              const value1 = action.closest(".krds-calendar-area").querySelector("td.period.start")?.getAttribute("data-date");
1581              const value2 = action.closest(".krds-calendar-area").querySelector("td.period.end")?.getAttribute("data-date") || "";
1582              targetInputStart.value = value1;
1583              targetInputEnd.value = value2;
1584            }
1585          }
1586        });
1587        // 공통 접근성
1588        const accSet = () => {
1589          const prevItems = action.closest(".krds-calendar-area").querySelectorAll(".period");
1590          prevItems.forEach((prev) => {
1591            prev.classList.remove("period", "start", "end");
1592            prev.querySelector(".btn-set-date").removeAttribute("aria-pressed");
1593          });
1594          action.closest(".krds-calendar-area").querySelector("td.today").classList.add("period", "start", "end");
1595          action.closest(".krds-calendar-area").querySelector("td.today .btn-set-date").setAttribute("aria-pressed", "true");
1596        };
1597      };
1598
1599      // btn-set-date
1600      tblCellBtns.forEach((btn) => {
1601        // disabled 설정
1602        if (btn.closest("td.new, td.old")) {
1603          btn.setAttribute("disabled", "true");
1604        }
1605
1606        // var
1607        const isSingle = btn.closest(".calendar-wrap").classList.contains("single");
1608
1609        if (isSingle) {
1610          btn.addEventListener("click", () => {
1611            tblCellBtns.forEach((otherBtn) => {
1612              otherBtn.closest("td").classList.remove("period", "start", "end");
1613              otherBtn.removeAttribute("aria-pressed");
1614            });
1615            btn.closest("td").classList.add("period", "start", "end");
1616            btn.setAttribute("aria-pressed", "true");  
1617          });
1618          // action
1619          actionBtns.forEach((action) => {
1620            accReset(action, btn, "single");
1621          });
1622        } else {         
1623          btn.addEventListener("click", () => {
1624            const currentTd = btn.closest("td");
1625            // 현재 td의 날짜
1626            const currentDate = new Date(currentTd.getAttribute("data-date"));
1627            // 두 번째 클릭일 때, 시작날짜 이전 이면 초기화
1628            if (startTd) {
1629              const startDate = new Date(startTd.getAttribute("data-date"));
1630              if (currentDate < startDate) {
1631                console.log("시작날짜 이전은 선택할 수 없습니다.");
1632                startTd = null;
1633                clickCount = 0;
1634                // return;
1635              }
1636            }
1637
1638            clickCount++;
1639
1640            if (clickCount % 2 === 1) {
1641              tblCellBtns.forEach((otherBtn) => {
1642                otherBtn.closest("td").classList.remove("period", "start", "end");
1643                otherBtn.removeAttribute("aria-pressed");
1644              });
1645              btn.closest("td").classList.add("period", "start");
1646              btn.setAttribute("aria-pressed", "true");
1647              startTd = currentTd;
1648            } else {
1649              btn.closest("td").classList.add("period", "end");
1650              btn.setAttribute("aria-pressed", "true");
1651              let started = false;
1652              tblCellBtns.forEach((otherBtn) => {
1653                const td = otherBtn.closest("td");
1654                if (td === startTd) {
1655                  started = true;
1656                }
1657                if (started && td !== currentTd) {
1658                  td.classList.add("period");
1659                  otherBtn.setAttribute("aria-pressed", "true");
1660                }
1661                if (td === currentTd) {
1662                  started = false;
1663                }
1664              });
1665              startTd = null;
1666            }
1667          });
1668          // action
1669          actionBtns.forEach((action) => {
1670            accReset(action, btn);
1671          });
1672        }
1673      });
1674    });
1675  },
1676};
1677
1678/*** * krds_inPageNavigation * ***/
1679const krds_inPageNavigation = {
1680  quickIndicators: null,
1681  init() {
1682    this.quickIndicators = document.querySelectorAll(".krds-in-page-navigation-type .krds-in-page-navigation-area:not(.sample) .in-page-navigation-list");
1683
1684    if (!this.quickIndicators.length) return;
1685
1686    this.observeListChanges();
1687    this.setupAnchorScroll();
1688    this.updateActiveSection();
1689  },
1690  observeListChanges() {
1691    // in-page-navigation-list 변경 시 setupAnchorScroll 호출
1692    const quickList = document.querySelector(".krds-in-page-navigation-type .in-page-navigation-list");
1693    if (!quickList) return;
1694    const observer = new MutationObserver(() => {
1695      this.setupAnchorScroll();
1696    });
1697    observer.observe(quickList, {
1698      childList: true,
1699      subtree: true,
1700    });
1701  },
1702  setupAnchorScroll() {
1703    this.quickIndicators.forEach((indicator) => {
1704      const locationList = indicator.querySelectorAll("a");
1705      locationList.forEach((anchor) => {
1706        const target = document.querySelector(anchor.getAttribute("href"));
1707        if (target) {
1708          anchor.removeEventListener("click", this.applyScroll);
1709          anchor.removeEventListener("keydown", this.applyScroll);
1710          anchor.addEventListener("click", this.applyScroll.bind(this, target));
1711          anchor.addEventListener("keydown", (event) => {
1712            if (event.key === "Enter" || event.key === " ") {
1713              this.applyScroll(target, event);
1714            }
1715          });
1716        }
1717      });
1718    });
1719  },
1720  applyScroll(target, event) {
1721    event.preventDefault();
1722    const headerHeight = this.calculateHeaderHeight();
1723
1724    window.scrollTo({
1725      left: 0,
1726      top: target.getBoundingClientRect().top + window.scrollY - headerHeight,
1727      behavior: "smooth",
1728    });
1729
1730    // enter 초점 이동
1731    if (event.type === "keydown") {
1732      const focusable = target.querySelector(".sec-tit");
1733
1734      if (focusable) {
1735        focusable.setAttribute("tabindex", "-1");
1736        focusable.focus({ preventScroll: true });
1737      }
1738    }
1739  },
1740  calculateHeaderHeight() {
1741    const headerTop = document.querySelector("#krds-masthead")?.clientHeight || 0;
1742    const headerInner = document.querySelector("#krds-header .header-in")?.clientHeight || 0;
1743    return headerTop + headerInner;
1744  },
1745  updateActiveSection() {
1746    if (!this.quickIndicators) return;
1747
1748    const winHeight = window.innerHeight;
1749    let sectionArea = [];
1750    const activeTab = document.querySelector(".tab-conts:not(.sample).active");
1751
1752    // 탭이 아닐때와 탭일때 sectionArea 설정
1753    if (activeTab) {
1754      const id = activeTab.getAttribute("id");
1755      const dataTrue = activeTab.getAttribute("data-quick-nav");
1756      if (dataTrue === "true") {
1757        sectionArea = document.querySelectorAll(`#${id} .section-link`);
1758      }
1759    } else {
1760      sectionArea = document.querySelectorAll(".scroll-check .section-link");
1761    }
1762
1763    //페이지 스크롤 시 퀵 네비게이션 해당메뉴 active
1764    if (sectionArea.length > 0) {
1765      const topHeight = Math.ceil(winHeight / 5); // 윈도우의 20%
1766      const firstSecTop = sectionArea[0].offsetTop;
1767      const scrollBottom = window.scrollY + winHeight;
1768      const scrollHeight = document.body.scrollHeight;
1769      sectionArea.forEach((current) => {
1770        const sectionHeight = current.offsetHeight;
1771        const sectionTop = current.offsetTop - topHeight;
1772        const sectionId = current.getAttribute("id");
1773        const navLink = document.querySelector(`.krds-in-page-navigation-area a[href*=${sectionId}]`);
1774        const firstAnchor = document.querySelector(".krds-in-page-navigation-area .in-page-navigation-list li:first-of-type a");
1775        const lastAnchor = document.querySelector(".krds-in-page-navigation-area .in-page-navigation-list li:last-of-type a");
1776        if (scrollBottom >= scrollHeight) {
1777          // 스크롤이 페이지 끝에 도달했을 때
1778          this.setActiveIndicator(lastAnchor);
1779        }
1780        else if (window.scrollY <= firstSecTop) {
1781          // 스크롤이 첫번째 섹션보다 위에 있을때
1782          this.setActiveIndicator(firstAnchor);
1783        } else if (window.scrollY > sectionTop && window.scrollY <= sectionTop + sectionHeight) {
1784          // 현재 섹션에 있을 때
1785          this.setActiveIndicator(navLink);
1786        }
1787      });
1788    }
1789  },
1790  setActiveIndicator(anchor) {
1791    if (anchor) {
1792      this.quickIndicators.forEach((indicator) => {
1793        const locationList = indicator.querySelectorAll("a");
1794        locationList.forEach((anchor) => {
1795          anchor.classList.remove("active");
1796        });
1797      });
1798      anchor.classList.add("active");
1799    }
1800  },
1801};
1802
1803/*** * krds_helpPanel * ***/
1804const krds_helpPanel = {
1805  helpPanel: null,
1806  lastFocusedButton: null,
1807  executeButton: null,
1808  collapseButton: null,
1809  init() {
1810    this.helpPanel = document.querySelector(".krds-help-panel");
1811
1812    if (!this.helpPanel) return;
1813
1814    this.executeButton = document.querySelectorAll(".btn-help-exec");
1815    this.collapseButton = this.helpPanel.querySelector(".btn-help-panel.fold");
1816
1817    this.setupPadding();
1818    this.observeMastHead();
1819    this.setupHelpButtons();
1820    this.toggleScrollLock();
1821  },
1822  setupPadding() {
1823    const topBannerHeight = document.querySelector("#krds-masthead")?.offsetHeight;
1824    const headerHeight = document.querySelector("#krds-header .header-in")?.offsetHeight;
1825    const defaultPadding = topBannerHeight + headerHeight;
1826    const hiddenBannerPadding = headerHeight;
1827
1828    const expandBox = document.querySelector(".help-panel-wrap");
1829    const expandButton = document.querySelector(".btn-help-panel.expand");
1830
1831    const applyPadding = (padding) => {
1832      expandButton.style.marginTop = padding;
1833      if (windowSize.getWinSize() === "pc") {
1834        expandBox.style.paddingTop = padding;
1835        this.collapseButton.style.marginTop = padding;
1836      } else {
1837        expandBox.removeAttribute("style");
1838        this.collapseButton.removeAttribute("style");
1839      }
1840    };
1841
1842    // bn-hidden: 헤더 배너 숨김, scroll-down: 헤더 숨김
1843    if (document.body.classList.contains("bn-hidden")) {
1844      if (document.querySelector("#wrap").classList.contains("scroll-down")) {
1845        applyPadding("0");
1846      } else {
1847        applyPadding(`${hiddenBannerPadding}px`);
1848      }
1849    } else {
1850      applyPadding(`${defaultPadding}px`);
1851    }
1852  },
1853  observeMastHead() {
1854    const topBanner = document.querySelector("#krds-masthead");
1855    if (!topBanner) return;
1856    const body = document.body;
1857    const observer = new IntersectionObserver(
1858      (entries) => {
1859        entries.forEach((entry) => {
1860          body.classList.toggle("bn-hidden", !entry.isIntersecting);
1861        });
1862      },
1863      { root: null, threshold: 0 }
1864    );
1865    observer.observe(topBanner);
1866  },
1867  setupHelpButtons() {
1868    this.executeButton.forEach((btn) => {
1869      btn.addEventListener("click", () => {
1870        this.lastFocusedButton = btn;
1871        this.toggleHelpPanel("open", btn);
1872      });
1873      btn.setAttribute("aria-expanded", "false");
1874    });
1875    if (this.collapseButton) {
1876      this.collapseButton.addEventListener("click", () => {
1877        this.toggleHelpPanel("close");
1878      });
1879    }
1880  },
1881  toggleHelpPanel(action, triggerBtn) {
1882    const helpWrap = document.querySelector(".help-panel-wrap");
1883    const innerContainer = document.querySelector("#container > .inner");
1884
1885    if (!helpWrap || !innerContainer || !this.helpPanel) return;
1886
1887    if (action === "open") {
1888      if (windowSize.getWinSize() === "mob") {
1889        document.body.classList.add("scroll-no");
1890      }
1891
1892      this.helpPanel.classList.add("expand");
1893      helpWrap.setAttribute("tabindex", "0");
1894
1895      if (triggerBtn) {
1896        setTimeout(() => {
1897          helpWrap.focus();
1898        }, 50);
1899      }
1900
1901      // 도움패널은 페이지에 한개만 있고 컨트롤하는 버튼은 여러개일때
1902      this.executeButton.forEach((btn) => {
1903        btn.setAttribute("aria-expanded", "true");
1904      });
1905
1906      // inner가 flexible인 경우
1907      if (innerContainer.classList.contains("help-panel-flexible")) {
1908        innerContainer.classList.add("help-panel-expanded");
1909      }
1910    } else if (action === "close") {
1911      if (windowSize.getWinSize() === "mob") {
1912        document.body.classList.remove("scroll-no");
1913      }
1914
1915      this.helpPanel.classList.remove("expand");
1916      helpWrap.removeAttribute("tabindex");
1917
1918      if (this.lastFocusedButton) {
1919        this.lastFocusedButton.focus();
1920      } else {
1921        // 처음 부터 오픈일때 지정 버튼으로 포커스 이동
1922        document.querySelector(".btn-help-panel.expand").focus();
1923      }
1924
1925      // 도움패널은 페이지에 한개만 있고 컨트롤하는 버튼은 여러개일때
1926      this.executeButton.forEach((btn) => {
1927        btn.setAttribute("aria-expanded", "false");
1928      });
1929
1930      // inner가 flexible인 경우
1931      if (innerContainer.classList.contains("help-panel-flexible")) {
1932        innerContainer.classList.remove("help-panel-expanded");
1933      }
1934    }
1935  },
1936  toggleScrollLock() {
1937    setTimeout(() => {
1938      if (windowSize.getWinSize() === "mob" && this.helpPanel.classList.contains("expand")) {
1939        document.body.classList.add("scroll-no");
1940      } else {
1941        document.body.classList.remove("scroll-no");
1942      }
1943    }, 0);
1944  },
1945};
1946
1947/*** * krds_disclosure * ***/
1948const krds_disclosure = {
1949  disclosures: null,
1950  init() {
1951    this.disclosures = document.querySelectorAll(".krds-disclosure");
1952
1953    if (!this.disclosures.length) return;
1954
1955    this.setupDisclosure();
1956  },
1957  setupDisclosure() {
1958    this.disclosures.forEach((disclosure) => {
1959      const disclosureButton = disclosure.querySelector(".btn-conts-expand");
1960      const disclosureContent = disclosure.querySelector(".expand-wrap");
1961      const uniqueIdx = `disclosure-${Math.random().toString(36).substring(2, 9)}`;
1962
1963      // 예외 처리: disclosureButton 없이 active 상태를 직접 설정하여 확장 상태를 제어하는 경우
1964      if (!disclosureButton) return;
1965
1966      // aria 속성 설정
1967      disclosureButton.setAttribute("aria-expanded", "false");
1968      disclosureButton.setAttribute("aria-controls", uniqueIdx);
1969      disclosureContent.setAttribute("id", uniqueIdx);
1970      // disclosureContent.setAttribute("aria-hidden", "true"); // 임시: disclosure 내용이 일부만 노출되는 경우
1971      disclosureContent.setAttribute("inert", "");
1972      if (disclosure.classList.contains("active")) {
1973        disclosureButton.setAttribute("aria-expanded", "true");
1974        // disclosureContent.setAttribute("aria-hidden", "false"); // 임시: disclosure 내용이 일부만 노출되는 경우
1975        disclosureContent.removeAttribute("inert");
1976      }
1977
1978      disclosureButton.addEventListener("click", () => {
1979        this.toggleDisclosure(disclosure, disclosureButton, disclosureContent);
1980      });
1981    });
1982  },
1983  toggleDisclosure(disclosure, disclosureButton, disclosureContent) {
1984    const isExpanded = disclosureButton.getAttribute("aria-expanded") === "true";
1985
1986    disclosure.classList.toggle("active", !isExpanded);
1987    disclosureButton.setAttribute("aria-expanded", !isExpanded);
1988
1989    // disclosureContent.setAttribute("aria-hidden", isExpanded); // 임시: disclosure 내용이 일부만 노출되는 경우
1990    if (isExpanded) {
1991      disclosureContent.setAttribute("inert", "");
1992    } else {
1993      disclosureContent.removeAttribute("inert");
1994    }
1995  },
1996};
1997
1998/*** * krds_adjustContentScale * ***/
1999const krds_adjustContentScale = {
2000  body: document.body,
2001  scaleLevel: 1,
2002  minScale: 0.5,
2003  maxScale: 2,
2004  init() {
2005    const scaleButtons = document.querySelectorAll("[data-adjust] [data-adjust-scale]");
2006
2007    if (!scaleButtons.length) return;
2008
2009    // root 변수에서 스케일 값을 가져오기
2010    const root = document.querySelector(":root");
2011    const rootStyles = getComputedStyle(root);
2012    const zoomSmall = rootStyles.getPropertyValue("--krds-zoom-small").trim();
2013    const zoomMedium = rootStyles.getPropertyValue("--krds-zoom-medium").trim();
2014    const zoomLarge = rootStyles.getPropertyValue("--krds-zoom-large").trim();
2015    const zoomXlarge = rootStyles.getPropertyValue("--krds-zoom-xlarge").trim();
2016    const zoomXxlarge = rootStyles.getPropertyValue("--krds-zoom-xxlarge").trim();
2017
2018    scaleButtons.forEach((button) => {
2019      button.addEventListener("click", () => {
2020        const scale = button.getAttribute("data-adjust-scale");
2021        switch (scale) {
2022          case "sm":
2023            this.scaleValue(zoomSmall);
2024            break;
2025          case "md":
2026            const others = button.closest(".drop-menu").querySelectorAll(".item-link");
2027            const defaultSize = button.closest(".drop-menu").querySelector(".item-link.md");
2028            others.forEach((item) => {
2029              item.classList.remove("active");
2030              item.setAttribute("aria-selected", "false");
2031              item.querySelector(".sr-only").innerHTML = "";
2032            });
2033            defaultSize.classList.add("active");
2034            defaultSize.querySelector(".sr-only").innerHTML = "선택됨";
2035            this.scaleValue(zoomMedium);
2036            break;
2037          case "lg":
2038            this.scaleValue(zoomLarge);
2039            break;
2040          case "xlg":
2041            this.scaleValue(zoomXlarge);
2042            break;
2043          case "xxlg":
2044            this.scaleValue(zoomXxlarge);
2045            break;
2046          default:
2047            break;
2048        }
2049      });
2050    });
2051  },
2052  scaleValue(value) {
2053    this.scaleLevel = value;
2054    this.body.style.zoom = this.scaleLevel;
2055  },
2056  scaleDefault() {
2057    this.scaleLevel = 1;
2058    this.body.style.zoom = this.scaleLevel;
2059  },
2060  scaleMin() {
2061    this.scaleLevel = this.minScale;
2062    this.body.style.zoom = this.scaleLevel;
2063  },
2064  scaleMax() {
2065    this.scaleLevel = this.maxScale;
2066    this.body.style.zoom = this.scaleLevel;
2067  },
2068  scaleUp() {
2069    if (this.scaleLevel < this.maxScale) {
2070      this.scaleLevel += 0.1;
2071      if (this.scaleLevel > this.maxScale) this.scaleLevel = this.maxScale;
2072      this.body.style.zoom = this.scaleLevel;
2073    }
2074  },
2075  scaleDown() {
2076    if (this.scaleLevel > this.minScale) {
2077      this.scaleLevel -= 0.1;
2078      if (this.scaleLevel < this.minScale) this.scaleLevel = this.minScale;
2079      this.body.style.zoom = this.scaleLevel;
2080    }
2081  },
2082};
2083
2084/*** * krds_toggleSwitch * ***/
2085const krds_toggleSwitch = {
2086  init() {
2087    const toggleSwitch = document.querySelectorAll(".krds-form-toggle-switch");
2088
2089    if (!toggleSwitch.length) return;
2090
2091    toggleSwitch.forEach((toggle) => {
2092      const input = toggle.querySelector("input");
2093      if (!input) return;
2094      input.addEventListener("focus", () => {
2095        toggle.classList.add("focus");
2096      });
2097      input.addEventListener("focusout", () => {
2098        toggle.classList.remove("focus");
2099      });
2100    });
2101  },
2102};
2103
2104/*** * krds_infoList * ***/
2105const krds_infoList = {
2106  init() {
2107    const infoLists = document.querySelectorAll(".krds-info-list");
2108
2109    if (!infoLists.length) return;
2110
2111    infoLists.forEach((list) => {
2112      list.setAttribute("role", "list");
2113      const listItems = list.querySelectorAll("li");
2114      listItems.forEach((item) => {
2115        item.setAttribute("role", "listitem");
2116      });
2117    });
2118  },
2119};
2120
2121/*** * krds_dropEvent(gnb utils / page-title-wrap) * ***/
2122const krds_dropEvent = {
2123  dropButtons: null,
2124  init() {
2125    this.dropButtons = document.querySelectorAll(".krds-drop-wrap:not(.sample) .drop-btn");
2126
2127    if (!this.dropButtons.length) return;
2128
2129    this.setupEventListeners();
2130    this.setupFocusOutEvent();
2131  },
2132  setupEventListeners() {
2133    this.dropButtons.forEach((button) => {
2134      const menu = button.nextElementSibling;
2135      button.setAttribute("aria-expanded", "false");
2136
2137      button.addEventListener("click", () => {
2138        const isOpen = menu.style.display === "block";
2139        this.closeAllDropdowns();
2140        if (!isOpen) {
2141          this.openDropdown(button, menu);
2142        }
2143      });
2144
2145      this.setupMenuItems(menu);
2146    });
2147  },
2148  setupMenuItems(menu) {
2149    const items = menu.querySelectorAll(".item-link");
2150
2151    items.forEach((item) => {
2152      item.innerHTML += `<span class="sr-only"></span>`;
2153      if (item.classList.contains("active")) {
2154        item.querySelector(".sr-only").innerHTML = "선택됨";
2155      }
2156
2157      item.addEventListener("click", () => {
2158        this.activateMenuItem(item);
2159        this.closeAllDropdowns();
2160        const button = item.closest(".krds-drop-wrap").querySelector(".drop-btn");
2161        button?.focus();
2162      });
2163
2164      item.addEventListener("focus", () => {
2165        document.querySelectorAll(".krds-drop-wrap .drop-list .item-link").forEach((item) => {
2166          item.style.position = "relative";
2167          item.style.zIndex = "0";
2168        });
2169        item.style.zIndex = "1";
2170      });
2171    });
2172  },
2173  activateMenuItem(selectedItem) {
2174    const menu = selectedItem.closest(".drop-menu");
2175    const items = menu.querySelectorAll(".item-link");
2176
2177    items.forEach((item) => {
2178      item.classList.remove("active");
2179      item.setAttribute("aria-selected", "false");
2180      item.querySelector(".sr-only").innerText = "";
2181    });
2182
2183    selectedItem.classList.add("active");
2184    selectedItem.setAttribute("aria-selected", "true");
2185    selectedItem.querySelector(".sr-only").innerText = "선택됨";
2186  },
2187  openDropdown(button, menu) {
2188    menu.style.display = "block";
2189    button.classList.add("active");
2190    button.setAttribute("aria-expanded", "true");
2191
2192    // 여백에 따라 위치 조정
2193    const menuRect = menu.getBoundingClientRect();
2194    const windowWidth = window.innerWidth;
2195    if (menuRect.left < 0) {
2196      menu.closest(".krds-drop-wrap").classList.add("drop-left");
2197    } else if (windowWidth < menuRect.left + menuRect.width) {
2198      menu.closest(".krds-drop-wrap").classList.add("drop-right");
2199    }
2200
2201  },
2202  closeAllDropdowns() {
2203    document.querySelectorAll(".krds-drop-wrap .drop-menu").forEach((menu) => {
2204      menu.style.display = "none";
2205    });
2206    this.dropButtons.forEach((button) => {
2207      button.classList.remove("active");
2208      button.setAttribute("aria-expanded", "false");
2209    });
2210  },
2211  setupFocusOutEvent() {
2212    // ESC 닫기
2213    document.addEventListener("keydown", (event) => {
2214      if (event.key === "Escape" || event.key === "Esc") {
2215        this.closeAllDropdowns();
2216        event.target.closest(".krds-drop-wrap")?.querySelector(".drop-btn")?.focus();
2217      }
2218    });
2219
2220    // 드롭다운 외부 클릭 시 닫기
2221    document.addEventListener("click", (event) => {
2222      if (!event.target.closest(".krds-drop-wrap")) {
2223        this.closeAllDropdowns();
2224      }
2225    });
2226
2227    // 드롭다운 포커스 아웃 처리
2228    this.dropButtons.forEach((button) => {
2229      const menu = button.nextElementSibling;
2230
2231      if (!menu) return;
2232
2233      // 드롭다운 메뉴의 포커스 아웃 처리
2234      menu.addEventListener("focusout", (event) => {
2235        const isFocusInside = menu.contains(event.relatedTarget) || button.contains(event.relatedTarget);
2236        if (!isFocusInside) {
2237          this.closeAllDropdowns();
2238        }
2239      });
2240
2241      // 버튼의 포커스 아웃 처리
2242      button.addEventListener("focusout", (event) => {
2243        const isFocusInside = menu.contains(event.relatedTarget) || button.contains(event.relatedTarget);
2244        if (!isFocusInside) {
2245          this.closeAllDropdowns();
2246        }
2247      });
2248    });
2249  },
2250};
2251
2252/*** * krds_chkBox 박스형 체크박스 상태에 따른 디자인 변경 * ***/
2253const krds_chkBox = {
2254  init() {
2255    const box = document.querySelectorAll(".chk-group-wrap");
2256    box.forEach((e) => {
2257      const boxList = e.querySelectorAll("li");
2258      boxList.forEach((ele) => {
2259        ele.removeAttribute("class");
2260        const thisList = ele.closest("li");
2261        const checkbox = ele.querySelector("input[type=checkbox]");
2262        if (checkbox != null) {
2263          const is_disabled = checkbox.disabled;
2264          let is_checked = checkbox.checked;
2265
2266          if (is_disabled == true) {
2267            thisList.classList.add("disabled");
2268          } else {
2269            if (is_checked == true) {
2270              thisList.classList.add("checked");
2271            }
2272          }
2273
2274          checkbox.addEventListener("click", () => {
2275            if (is_checked == true) {
2276              thisList.classList.remove("checked");
2277              is_checked = false;
2278            } else {
2279              thisList.classList.add("checked");
2280              is_checked = true;
2281            }
2282          });
2283        }
2284
2285        const rdo = ele.querySelector("input[type=radio]");
2286        if (rdo != null) {
2287          const is_disabled = rdo.disabled;
2288          let is_checked = rdo.checked;
2289
2290          if (is_disabled == true) {
2291            thisList.classList.add("disabled");
2292          } else {
2293            if (is_checked == true) {
2294              thisList.classList.add("checked");
2295            }
2296          }
2297
2298          rdo.addEventListener("click", (e) => {
2299            const rdoGroup = rdo.closest(".chk-group-wrap");
2300            const rdoLi = rdoGroup.querySelectorAll("li");
2301            let is_checked2 = e.checked;
2302            rdoLi.forEach((ele) => {
2303              ele.classList.remove("checked");
2304              is_checked2 = false;
2305            });
2306            if (is_checked2 == true) {
2307              thisList.classList.remove("checked");
2308              is_checked2 = false;
2309            } else {
2310              thisList.classList.add("checked");
2311              is_checked2 = true;
2312            }
2313          });
2314        }
2315      });
2316    });
2317
2318    this.formChipFocus();
2319  },
2320  formChipFocus() {
2321    // form_chip 일때 포커스 처리
2322    const formChip = document.querySelectorAll(".krds-form-chip");
2323
2324    if (!formChip.length) return;
2325    
2326    formChip.forEach((chip) => {
2327      const input = chip.querySelector("input");
2328      if (!input) return;
2329      input.addEventListener("focus", () => {
2330        chip.classList.add("focus");
2331      });
2332      input.addEventListener("focusout", () => {
2333        chip.classList.remove("focus");
2334      });
2335    });
2336
2337  },
2338};
2339
2340/*** * krds_fileUpload 파일 업로드 : drag 임시 * ***/
2341const krds_fileUpload = {
2342  init() {
2343    const fileUploads = document.querySelectorAll(".file-upload");
2344    fileUploads.forEach((fileUpload) => {
2345      const inputFile = fileUpload.querySelector('input[type="file"]');
2346      const inputButton = fileUpload.querySelector("button");
2347
2348      inputButton.addEventListener("click", () => {
2349        inputFile.click();
2350      });
2351
2352      fileUpload.addEventListener("dragover", (e) => {
2353        fileUpload.classList.add("active");
2354        e.preventDefault();
2355      });
2356
2357      fileUpload.addEventListener("dragleave", (e) => {
2358        fileUpload.classList.remove("active");
2359        e.preventDefault();
2360      });
2361
2362      fileUpload.addEventListener("drop", (e) => {
2363        fileUpload.classList.remove("active");
2364        e.preventDefault();
2365        // const files = e.dataTransfer.files;
2366      });
2367    });
2368  },
2369};
2370
2371/*** * nuriToggleEvent 누리집 토글 이벤트 현재는 사용 안 함 * ***/
2372const nuriToggleEvent = {
2373  init() {
2374    const _toggleBtns = document.querySelectorAll("#krds-masthead .toggle-btn");
2375    _toggleBtns.forEach(($toggleBtn) => {
2376      $toggleBtn.addEventListener("click", ($btnAct) => {
2377        const $target = $btnAct.target.closest(".toggle-head");
2378        const $targetBody = $target.nextElementSibling;
2379        const _targetBodyH = $targetBody.querySelector(".inner").scrollHeight;
2380        const $srEl = $btnAct.target.querySelector(".sr-only");
2381
2382        if (!$target.classList.contains("active")) {
2383          $srEl.innerText = "닫힘";
2384          $target.classList.add("active");
2385          $targetBody.classList.add("active");
2386          $targetBody.style.height = `${_targetBodyH}px`;
2387          window.addEventListener("resize", () => {
2388            if ($targetBody.classList.contains("active")) {
2389              const _targetBodyH = $targetBody.querySelector(".inner").scrollHeight;
2390              $targetBody.style.height = `${_targetBodyH}px`;
2391            }
2392          });
2393        } else {
2394          $srEl.innerText = "열림";
2395          $target.classList.remove("active");
2396          $targetBody.classList.remove("active");
2397          $targetBody.style.height = "";
2398        }
2399      });
2400    });
2401  },
2402};
2403
2404// 초기 이벤트
2405window.addEventListener("DOMContentLoaded", () => {
2406  // 윈도우 사이즈 체크
2407  windowSize.setWinSize();
2408
2409  krds_mainMenuPC.init();
2410  krds_mainMenuMobile.init();
2411  krds_sideNavigation.init();
2412  krds_tab.init();
2413  krds_accordion.init('allopen');
2414  // 모든 아코디언이 닫아야 할 때 (allclose)
2415  //krds_accordion.init('allclos);
2416  // 모든 아코디언이 열어야 할 때 (allopen)
2417  // krds_accordion.init('allopen');
2418  // 첫번째만 열려야 할 때 (프론트)
2419  // krds_accordion.init('front');
2420
2421    // if (typeof pageFlag !== "undefined" && pageFlag === "faq") {
2422    //     krds_accordion.init('allclose');
2423    // } else {
2424    //     krds_accordion.init('front');
2425    // }
2426
2427  krds_modal.init();
2428  krds_contextualHelp.init();
2429  krds_tooltip.init();
2430  krds_disclosure.init();
2431  krds_dropEvent.init();
2432  krds_calendar.init();
2433  krds_inPageNavigation.init();
2434  krds_adjustContentScale.init();
2435  krds_toggleSwitch.init();
2436  krds_infoList.init();
2437  krds_chkBox.init();
2438  krds_fileUpload.init();
2439
2440  krds_helpPanel.init();
2441  if (windowSize.getWinSize() === "pc") {
2442    krds_helpPanel.toggleHelpPanel("open");
2443  }
2444});
2445
2446// 스크롤 이벤트
2447window.addEventListener("scroll", () => {
2448  scrollManager.updateScrollValues();
2449  scrollManager.handleScrollDirection();
2450
2451  krds_mainMenuPC.closeMainMenu();
2452  krds_inPageNavigation.updateActiveSection();
2453  krds_helpPanel.init();
2454});
2455
2456// 리사이즈 이벤트
2457window.addEventListener("resize", () => {
2458  windowSize.setWinSize();
2459  krds_helpPanel.init();
2460});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.