1(() => { 2 const initUI = () => { 3 const ANI_DEFAUT = 'cubic-bezier(0.22, 1, 0.36, 1)'; 4 const EVENT_BINDED_CLASS = "binded"; 5 // Classes 6 class Dialog { 7 // Private members 8 #dialog = null; 9 #willShowCallbacks = []; 10 #didShowCallbacks = []; 11 #willCloseCallbacks = []; 12 #didCloseCallbacks = []; 13 #isModal = true; 14 // Public members 15 16 constructor(queryOrElement) { 17 let dialog = null; 18 if (queryOrElement instanceof Element && queryOrElement.tagName === 'DIALOG') { 19 dialog = queryOrElement; 20 } else { 21 dialog = document.querySelector(queryOrElement); 22 } 23 24 if (!(dialog instanceof Element) || dialog.tagName !== 'DIALOG') { 25 console.error('ë¤ì Selectorì í´ë¹íë Dialogê° ì¡´ì¬íì§ ììµëë¤: ' + queryOrElement); 26 return; 27 } 28 29 this.#dialog = dialog; 30 this.#init(); 31 } 32 33 // Private Methods 34 #init() { 35 // Closeable Event 36 const closeables = this.#dialog.querySelectorAll("*[closeable]"); 37 if (closeables !== null) { 38 closeables.forEach(closeableElement => closeableElement.addEventListener('click', () => this.close())); 39 } 40 // Dim Click 41 this.#dialog.addEventListener('click', (e) => { 42 e.target === this.#dialog && this.close(); 43 }); 44 // ESC Event 45 this.#dialog.addEventListener('cancel', (e) => { 46 e.preventDefault(); // 기존ì ë°ë¡ ë«íë ê¸°ë¥ ë기 47 if (this.#dialog.classList.contains('loading')) { 48 return; 49 } 50 this.close(); 51 }); 52 } 53 #isFunc(func) { 54 return func instanceof Function || console.error('í´ë¹ ì¸ì¤í´ì¤ë í¨ìê° ìëëë¤.'); 55 } 56 57 // Public Methods (Add CallBack Method) 58 addWillShowCallback(func) { 59 if (!this.#isFunc(func)) { return; } 60 this.#willShowCallbacks.push(func); 61 } 62 addDidShowCallback(func) { 63 if (!this.#isFunc(func)) { return; } 64 this.#didShowCallbacks.push(func); 65 } 66 addWillCloseCallback(func) { 67 if (!this.#isFunc(func)) { return; } 68 this.#willCloseCallbacks.push(func); 69 } 70 addDidCloseCallback(func) { 71 if (!this.#isFunc(func)) { return; } 72 this.#didCloseCallbacks.push(func); 73 } 74 onConfirmCallback(func) { 75 const confirmBtns = this.#dialog.querySelectorAll('button[confirm]'); 76 confirmBtns.forEach((confirmBtn) => { 77 confirmBtn.addEventListener('click', func); 78 confirmBtn.addEventListener('click', () => { 79 this.close(); 80 }); 81 }); 82 } 83 onCancelCallback(func) { 84 const cancelBtns = this.#dialog.querySelectorAll('button[cancel]'); 85 cancelBtns.forEach((cancelBtn) => { 86 cancelBtn.addEventListener('click', func); 87 }); 88 } 89 setIsModal(isModal) { 90 this.#isModal = isModal; 91 } 92 93 // Public Methods (Util) 94 show() { 95 // Run Will Show Callback Methods 96 this.#willShowCallbacks.forEach(cb => cb()); 97 document.body.classList.add('disable-scroll'); 98 this.#isModal ? this.#dialog.showModal() : this.#dialog.show() 99 100 // Focus ì´ê¸°í 101 this.#dialog.focus(); 102 this.#dialog.blur(); 103 const dialogAnimation = this.#dialog.animate( 104 [ 105 { transform: 'translateY(20px)', opacity: 0.5 }, 106 { transform: 'translateY(0)', opacity: 1 } 107 ], 108 { 109 duration: 500, 110 easing: ANI_DEFAUT, 111 fill: 'forwards' 112 } 113 ); 114 dialogAnimation.addEventListener('finish', () => { 115 // Run Did Show Callback Methods 116 this.#didShowCallbacks.forEach(cb => cb()); 117 }); 118 } 119 close() { 120 if (this.#dialog.classList.contains('closing')) { 121 return; 122 } 123 // Run Will Close Callback Methods 124 this.#dialog.classList.add('closing'); 125 this.#willCloseCallbacks.forEach(cb => cb()); 126 const dialogAnimation = this.#dialog.animate( 127 [ 128 { transform: 'translateY(0)', opacity: 1 }, 129 { transform: 'translateY(20px)', opacity: 0 } 130 ], 131 { 132 duration: 300, 133 easing: ANI_DEFAUT, 134 fill: 'forwards' 135 } 136 ); 137 dialogAnimation.addEventListener('finish', () => { 138 this.#dialog.close(); 139 this.#didCloseCallbacks.forEach(cb => cb()); 140 this.#dialog.classList.remove('closing'); 141 142 if (document.querySelector('dialog[open]') === null) { 143 document.body.classList.remove('disable-scroll'); 144 } 145 }); 146 } 147 } 148 // Init Methods 149 const initLayout = () => { 150 const header = document.querySelector(`.wrap .header:not(.${EVENT_BINDED_CLASS})`);
151 const footer = document.querySelector('.wrap .footer'); 152 const main = document.querySelector('.wrap .main'); 153 154 if (!(header instanceof Element) || !(footer instanceof Element) || !(main instanceof Element)) { 155 return; 156 } 157 158 header.classList.add(EVENT_BINDED_CLASS); 159 const setMainMinHeight = () => { 160 const minHeight = window.innerHeight - (header.clientHeight + footer.clientHeight); 161 162 main.style.minHeight = `${minHeight}px`; 163 } 164 setMainMinHeight(); 165 window.addEventListener('resize', setMainMinHeight); 166 } 167 const initMenu = () => { 168 const menuContainer = document.querySelector(`.menu-container:not(.${EVENT_BINDED_CLASS})`); 169 if (!(menuContainer instanceof Element)) { 170 return; 171 } 172 menuContainer.classList.add(EVENT_BINDED_CLASS); 173 const menuBtn = document.querySelector('.header .menu-btn'); 174 const closeBtn = document.querySelector('.menu-header .close-btn'); 175 const menuDim = menuContainer.querySelector('.sub-menu-bg'); 176 const depth1s = document.querySelectorAll('.menu .item .depth1:not(.binded)'); 177 178 function openMenu() { 179 document.body.classList.add('disable-scroll'); 180 menuContainer.classList.add('show'); 181 } 182 function closeMenu() { 183 document.body.classList.remove('disable-scroll'); 184 menuContainer.classList.remove('show'); 185 } 186 187 menuBtn.addEventListener('click', openMenu); // 모ë°ì¼ íë²ê±° ë©ë´ ì´ê¸° 188 closeBtn.addEventListener('click', closeMenu); // 모ë°ì¼ íë²ê±° ë©ë´ ë«ê¸° 189 menuDim.addEventListener('click', closeMenu); 190 191 // 모ë°ì¼ depth1 ë©ë´ ì¬ë«ì´ ì´ë²¤í¸ 192 depth1s.forEach((depth1) => { 193 depth1.classList.add('binded'); 194 depth1.addEventListener('click', () => { 195 if (!UI.isMobile()) { 196 return; 197 } 198 199 depth1.classList.toggle('open'); 200 }); 201 }); 202 } 203 const initInput = () => { 204 // Inputs 205 const inputs = [...document.querySelectorAll(`.input-wrap:not(.${EVENT_BINDED_CLASS})`)]; 206 207 inputs.forEach(input => { 208 // ì´ê¸° ì í 209 input.classList.add(EVENT_BINDED_CLASS); 210 211 const inputBtn = input.querySelector('.input-btn'); 212 const inputEl = input.querySelector('.input'); 213 const onFocus = () => { 214 input.classList.add('focus'); 215 }; 216 const onBlur = () => { 217 input.classList.remove('focus'); 218 } 219 220 if (!(inputEl instanceof Element)) { 221 return; 222 } 223 224 // ê³µíµ í¬ì»¤ì¤ ì´ë²¤í¸ 225 inputEl.addEventListener('focus', onFocus); 226 inputEl.addEventListener('blur', onBlur); 227 228 if (!(inputBtn instanceof Element)) { 229 return; 230 } 231 inputBtn.addEventListener('focus', onFocus); 232 inputBtn.addEventListener('blur', onBlur); 233 // ì¸í ì§ì°ê¸° ë²í¼ 234 if (inputBtn.classList.contains('type-reset')) { 235 const forInput = document.getElementById(inputBtn.dataset.for); 236 if (!(forInput instanceof Element)) { 237 console.error('data-forì ê°ì íì¸í´ì£¼ì¸ì'). 238 return; 239 } 240 241 // Input ì´ë²¤í¸ 242 forInput.addEventListener('focus', () => { 243 inputBtn.classList.remove('hide'); 244 }); 245 forInput.addEventListener('blur', (e) => { 246 if (e.relatedTarget && e.relatedTarget == inputBtn) { 247 return; 248 } 249 inputBtn.classList.add('hide'); 250 }); 251 252 // ë²í¼ ì´ë²¤í¸ 253 inputBtn.addEventListener('click', () => { 254 forInput.value = ''; 255 forInput.focus(); 256 }); 257 inputBtn.addEventListener('blur', () => { 258 inputBtn.classList.add('hide'); 259 }); 260 } 261 // ë¹ë°ë²í¸ ëíë´ê¸°/ì¨ê¸°ê¸° í ê¸ 262 else if (inputBtn.classList.contains('type-eye')) { 263 const forInput = document.getElementById(inputBtn.dataset.for); 264 const eyeIcon = inputBtn.querySelector('.icon.eye'); 265 const closedEyeIcon = inputBtn.querySelector('.icon.closed-eye'); 266 267 if (!(forInput instanceof Element) || 268 !(eyeIcon instanceof Element) || 269 !(closedEyeIcon instanceof Element)) { 270 console.error('ëíë´ê¸°/ì¨ê¸°ê¸° ììë¤ì íì¸í´ì£¼ì¸ì'). 271 return; 272 } 273 274 inputBtn.addEventListener('click', () => { 275 if (forInput.getAttribute('type') === 'password') { 276 forInput.setAttribute('type', 'text'); 277 eyeIcon.classList.add('hide'); 278 closedEyeIcon.classList.remove('hide'); 279 } else { 280 forInput.setAttribute('type', 'password'); 281 eyeIcon.classList.remove('hide'); 282 closedEyeIcon.classList.add('hide'); 283 } 284 });
285 } 286 // ë ì§ ì ë ¥ 287 // else if (inputBtn.classList.contains('type-calendar')) { 288 // const forInput = document.getElementById(inputBtn.dataset.for); 289 // if (!(forInput instanceof Element)) { 290 // console.error('data-forì ê°ì íì¸í´ì£¼ì¸ì'). 291 // return; 292 // } 293 294 // inputBtn.addEventListener('click', () => { 295 // calendar({ 296 // targetQuery: inputBtn.dataset.for, 297 // disableWeekend: forInput.dataset.disableWeekend, 298 // }); 299 // }) 300 // } 301 }); 302 303 // Select 304 const selects = [...document.querySelectorAll(`.select:not(.${EVENT_BINDED_CLASS})`)]; 305 selects.forEach(select => { 306 select.classList.add(EVENT_BINDED_CLASS); 307 const selectBtn = select.querySelector('.select-btn'); 308 const selectInput = select.querySelector('select'); 309 310 if (!(selectBtn instanceof Element)) { 311 return; 312 } 313 314 function openSelect() { 315 const fakeOptions = document.createElement('ul'); 316 fakeOptions.classList.add('options'); 317 fakeOptions.setAttribute('tabIndex', -1); 318 const options = select.querySelectorAll('select>option'); 319 320 options.forEach(option => { 321 const fakeOption = document.createElement('li'); 322 const fakeOptionBtn = document.createElement('button'); 323 324 if (option.selected) { 325 fakeOption.classList.add('active'); 326 } 327 fakeOptionBtn.classList.add('option-btn'); 328 fakeOptionBtn.textContent = option.textContent; 329 fakeOptionBtn.addEventListener('click', () => { 330 const activeFakeOption = fakeOptions.querySelector('.active'); 331 if (activeFakeOption instanceof Element) { 332 activeFakeOption.classList.remove('active'); 333 } 334 if (option.selected === false) { 335 fakeOption.classList.add('active'); 336 selectBtn.textContent = option.textContent; 337 option.selected = true; 338 selectInput.value = option.value; 339 selectInput.dispatchEvent(new Event('change')); 340 } 341 342 closeSelect(); 343 }); 344 fakeOptionBtn.addEventListener('focus', () => { 345 select.classList.add('focus'); 346 }); 347 fakeOptionBtn.addEventListener('blur', (e) => { 348 if (!select.classList.contains('open')) { 349 return; 350 } 351 if (e.relatedTarget && e.relatedTarget.classList.contains('option-btn')) { 352 return; 353 } 354 if (e.relatedTarget && e.relatedTarget == selectBtn) { 355 return; 356 } 357 closeSelect(); 358 }); 359 if (options.item(options.length - 1) === option) { 360 } 361 fakeOption.appendChild(fakeOptionBtn); 362 fakeOptions.append(fakeOption); 363 }); 364 365 select.appendChild(fakeOptions); 366 // ìì¹ê° íë©´ì ì ë°ì ì´ê³¼íë©´ ìë¡ ëíë´ê¸° 367 if (fakeOptions.getBoundingClientRect().top > window.innerHeight / 2) { 368 fakeOptions.classList.add('up'); 369 } 370 371 select.classList.add('open'); 372 select.classList.add('focus') 373 } 374 function closeSelect() { 375 select.classList.remove('open'); 376 select.classList.remove('focus'); 377 378 if (select.querySelector('.options') != null) { 379 select.querySelector('.options').remove(); 380 } 381 } 382 383 selectBtn.addEventListener('mousedown', () => { 384 if (select.classList.contains('open')) { 385 closeSelect(); 386 } else { 387 openSelect(); 388 } 389 }); 390 selectBtn.addEventListener('focus', () => { 391 select.classList.add('focus'); 392 if (!select.classList.contains('open')) { 393 openSelect(); 394 } 395 }); 396 selectBtn.addEventListener('blur', (e) => { 397 if (e.relatedTarget && e.relatedTarget.classList.contains('option-btn')) { 398 return; 399 } 400 if (e.relatedTarget && e.relatedTarget.classList.contains('options')) { 401 return; 402 } 403 closeSelect(); 404 }); 405 }); 406 } 407 const initTab = () => { 408 const tabWraps = [...document.querySelectorAll(`.tab-wrap:not(.${EVENT_BINDED_CLASS})`)]; 409 410 tabWraps.forEach(tabWrap => { 411 function getTabBtn() { 412 for (let i = 0; i < tabWrap.children.length; i++) { 413 const child = tabWrap.children[i]; 414 415 if (child.classList.contains('tab-btns')) { 416 return [...child.children]; 417 } 418 } 419 420 return undefined; 421 } 422 const tabBtns = getTabBtn(); 423 const tabs = tabWrap.querySelector('.tabs'); 424 let currenetTab = undefined; 425 426 window.addEventListener('resize', () => { 427 tabs.style.maxHeight = `${currenetTab.clientHeight}px`; 428 }); 429 430 // init 431 tabs.style.transform = 'translateX(0)'; 432 433 tabBtns.forEach((tabBtn, key) => { 434 if (key === 0) { 435 const tabId = tabBtn.dataset.for; 436 const tab = tabWrap.querySelector(`#${tabId}`); 437 tabs.style.maxHeight = `${tab.clientHeight}px`; 438 currenetTab = tab; 439 } 440 441 tabBtn.addEventListener('click', () => { 442 const tabId = tabBtn.dataset.for; 443 const tab = tabWrap.querySelector(`#${tabId}`); 444 const bar = tabWrap.querySelector('.bar'); 445 const persent = key * 100; 446 447 currenetTab = tab; 448 tabWrap.querySelector('.tab-btn.active').classList.remove('active'); 449 tabBtn.classList.add('active'); 450 // í í¬ê¸° ë³ê²½ 451 tabs.style.maxHeight = `${tab.clientHeight}px`; 452 window.dispatchEvent(new Event('resize'));
453 // í ì´ë 454 const tabAnimation = tabs.animate( 455 [ 456 { transform: tabWrap.style.transform }, 457 { transform: `translateX(-${persent}%)` } 458 ], 459 { 460 duration: 500, 461 easing: ANI_DEFAUT, 462 fill: 'forwards' 463 } 464 ); 465 // í ë²í¼ ì´ë 466 const barAnimation = bar.animate( 467 [ 468 { transform: tabWrap.style.transform }, 469 { transform: `translateX(${persent}%)` } 470 ], 471 { 472 duration: 500, 473 easing: ANI_DEFAUT, 474 fill: 'forwards' 475 } 476 ); 477 }); 478 }); 479 }); 480 } 481 const initSideIndex = () => { 482 const sideIdxs = [...document.querySelectorAll(`.side-idx:not(.${EVENT_BINDED_CLASS})`)]; 483 484 sideIdxs.forEach(sideIdx => { 485 const sideBtn = sideIdx.querySelector('.side-btn'); 486 487 sideBtn.addEventListener('click', () => { 488 sideIdx.classList.toggle('open'); 489 }); 490 sideIdx.classList.add(EVENT_BINDED_CLASS); 491 }); 492 } 493 const initShowable = () => { 494 const observer = new IntersectionObserver((entries, observer) => { 495 entries.forEach(entry => { 496 if (entry.isIntersecting) { 497 const delay = entry.target.dataset.delay ? entry.target.dataset.delay : 0; 498 setTimeout(() => { 499 entry.target.classList.add("visible"); 500 if (entry.target.showableCallBack instanceof Function) { 501 entry.target.showableCallBack(); 502 } 503 }, delay); 504 observer.unobserve(entry.target); 505 } 506 }); 507 }, { threshold: 0.2 }); // ììê° 20% ì´ì ë³´ì´ë©´ ì¤í 508 509 document.querySelectorAll(`.showable:not(.${EVENT_BINDED_CLASS})`).forEach(element => { 510 observer.observe(element); 511 element.classList.add(EVENT_BINDED_CLASS); 512 }); 513 } 514 const initParentToggle = () => { 515 const parents = [...document.querySelectorAll(`.parent:not(.${EVENT_BINDED_CLASS})`)]; 516 517 parents.forEach(parent => { 518 parent.addEventListener('click', (e) => { 519 parent.classList.toggle('active'); 520 }); 521 parent.classList.add(EVENT_BINDED_CLASS); 522 }); 523 } 524 // Run Init Methods 525 document.addEventListener('DOMContentLoaded', () => { 526 initLayout(); 527 initMenu(); 528 initInput(); 529 initTab(); 530 initSideIndex(); 531 initShowable(); 532 initParentToggle(); 533 }); 534 // ì¸ë¶ Methods 535 const alert = ({ 536 title = '', 537 msg = '', 538 confirmBtnText = 'íì¸', 539 onConfirm = () => {}, 540 } = {}) => { 541 const alertElement = document.createElement('dialog'); 542 const headerStr = title ? ` 543 <header class="dialog-header"> 544 <span class="dialog-tt">${title.replaceAll('\n', '<br>')}</span> 545 </header>` : ''; 546 const alertStr = ` 547 <div class="dialog"> 548 ${headerStr} 549 <div class="dialog-main"> 550 <p class="msg">${msg.replaceAll('\n', '<br>')}</p> 551 </div> 552 <footer class="dialog-footer"> 553 <button class="btn size-md-fix" type="button" confirm>${confirmBtnText}</button> 554 </footer> 555 </div> 556 `; 557 alertElement.classList.add('type-alert'); 558 alertElement.innerHTML = alertStr; 559 const alert = new Dialog(alertElement); 560 alert.onConfirmCallback(onConfirm); 561 alert.addDidCloseCallback(() => { 562 alertElement.remove(); 563 }); 564 document.body.appendChild(alertElement); 565 alert.show(); 566 } 567 const confirm = ({ 568 title = '', 569 msg = '', 570 cancelBtnText = 'ì·¨ì', 571 confirmBtnText = 'íì¸', 572 onCancel = () => {}, 573 onConfirm = () => {}, 574 } = {}) => { 575 const confirmElement = document.createElement('dialog'); 576 const headerStr = title ? ` 577 <header class="dialog-header"> 578 <span class="dialog-tt">${title.replaceAll('\n', '<br>')}</span> 579 </header>` : ''; 580 const confirmStr = ` 581 <div class="dialog"> 582 ${headerStr} 583 <div class="dialog-main"> 584 <p class="msg">${msg.replaceAll('\n', '<br>')}</p> 585 </div> 586 <footer class="dialog-footer"> 587 <button class="btn size-md-fix theme-gray" type="button" closeable cancel>${cancelBtnText}</button> 588 <button class="btn size-md-fix" type="button" confirm>${confirmBtnText}</button> 589 </footer> 590 </div> 591 `; 592 confirmElement.classList.add('type-alert');
593 confirmElement.innerHTML = confirmStr; 594 const confirm = new Dialog(confirmElement); 595 596 confirm.onConfirmCallback(onConfirm); 597 confirm.onCancelCallback(onCancel); 598 confirm.addDidCloseCallback(() => { 599 confirmElement.remove(); 600 }); 601 602 document.body.appendChild(confirmElement); 603 confirm.show(); 604 } 605 const calendar = ({ 606 targetQuery = '', 607 min = '1900-01-01', 608 max = '2099-12-31', 609 disableWeekend = false, 610 getHolyDays = () => {return []}, 611 } = {}) => { 612 // Validation 613 const input = document.querySelector(targetQuery); 614 if (input === null || !(input instanceof Element)) { 615 console.error(`targetQuery: ${targetQuery}ì í´ë¹íë ìì를 ì°¾ì ì ììµëë¤.`); 616 return; 617 } 618 if (!(input instanceof HTMLInputElement)) { 619 console.error(`targetQuery: ${targetQuery}ì í´ë¹íë ììë Input ììê° ìëëë¤.`); 620 return; 621 } 622 623 // Calendar Element Create & Append 624 const calendarDialogEl = document.createElement('dialog'); 625 calendarDialogEl.classList.add('type-calendar'); 626 calendarDialogEl.innerHTML = ` 627 <div class="dialog"> 628 <header class="dialog-header"> 629 <span class="dialog-tt"> 630 <button class="schedule-btn prev" type="button"> 631 <i class="icon arrow-left-s"></i> 632 </button> 633 <span class="month-str">01ì</span> 634 <button class="schedule-btn next" type="button"> 635 <i class="icon arrow-right-s"></i> 636 </button> 637 </span> 638 <div class="select calendar-year"> 639 <button class="select-btn" type="button">2024</button> 640 <select class="blind" tabindex="-1"> 641 </select> 642 </div> 643 <button class="btn theme-transparent type-icon" type="button" closeable=""> 644 <i class="icon cross"><i class="blind">Dialog ë«ê¸°</i></i> 645 </button> 646 </header> 647 <div class="dialog-main"> 648 <div class="calendar-wrap"> 649 <div class="weekend"> 650 <span>ì¼</span> 651 <span>ì</span> 652 <span>í</span> 653 <span>ì</span> 654 <span>목</span> 655 <span>ê¸</span> 656 <span>í </span> 657 </div> 658 <div class="days"> 659 </div> 660 </div> 661 </div> 662 </div>`; 663 const dialog = new Dialog(calendarDialogEl); 664 const prevBtn = calendarDialogEl.querySelector('.schedule-btn.prev'); 665 const nextBtn = calendarDialogEl.querySelector('.schedule-btn.next'); 666 667 let minDate = new Date(min); 668 let maxDate = new Date(max); 669 let inputDate = new Date(input.value); 670 let currentDate = new Date(); 671 672 if (minDate > maxDate) { 673 minDate = new Date('1900-01-01'); 674 maxDate = new Date('2099-12-31'); 675 console.warn(`ìµìë ì§(${minDate})ë³´ë¤ ìµëë ì§(${maxDate})ê° í´ ì ììµëë¤. í´ë¹ ê°ë¤ì´ 기본ê°ì¼ë¡ ë³ê²½ë©ëë¤.`); 676 } 677 if (isNaN(inputDate.getTime())) { 678 console.warn(`Inputê°: ${input.value} ë ì í¨íì§ ìì ê°ì ëë¤. íì¬ ë ì§ë¡ ê°ì´ ë³ê²½ë©ëë¤.`); 679 inputDate = new Date(); 680 } 681 currentDate.setTime(inputDate.getTime()); 682 minDate.setHours(0, 0, 0, 0); 683 maxDate.setHours(0, 0, 0, 0); 684 inputDate.setHours(0, 0, 0, 0); 685 currentDate.setHours(0, 0, 0, 0); 686 if (currentDate < minDate || currentDate > maxDate) { 687 console.warn(`${input.value} ë ì í¨íì§ ìì ê°ì ëë¤. íì¬ ë ì§ë¡ ê°ì´ ë³ê²½ë©ëë¤.`); 688 inputDate = new Date(); 689 inputDate.setHours(0, 0, 0, 0); 690 } 691 692 function zf(n) { 693 return `0${n}`.slice(-2); 694 } 695 function generateCalendar(year, month) { 696 let firstDay = new Date(year, month - 1, 1); // í´ë¹ ë¬ì 첫 ë²ì§¸ ë 697 let lastDay = new Date(year, month, 0); // í´ë¹ ë¬ì ë§ì§ë§ ë 698 699 let prevLastDay = new Date(year, month - 1, 0).getDate(); // ì´ì ë¬ì ë§ì§ë§ ë 700 let nextStartDay = 1; // ë¤ì ë¬ ìì ë ì§ 701 702 let startWeekday = firstDay.getDay(); // 첫ë ì ìì¼ (0: ì¼ìì¼, 6: í ìì¼) 703 let totalDays = lastDay.getDate(); // ì´ë² ë¬ ì´ ì¼ì 704 705 let calendar = []; 706 707 // ì´ì ë¬ì ì¬ë¶ ë ì§ ì±ì°ê¸° 708 for (let i = startWeekday - 1; i >= 0; i--) { 709 calendar.push({ 710 year: month - 1 === 0 ? year - 1 : year, 711 month: month - 1 === 0 ? 12 : month - 1, 712 day: prevLastDay - i,
713 currentMonth: false 714 }); 715 } 716 717 // ì´ë² ë¬ ë ì§ ì±ì°ê¸° 718 for (let day = 1; day <= totalDays; day++) { 719 const dayDate = new Date(); 720 dayDate.setHours(0, 0, 0, 0); 721 dayDate.setFullYear(year, month-1, day); 722 723 calendar.push({ 724 year, 725 month, 726 day, 727 currentMonth: !(dayDate < minDate || dayDate > maxDate), 728 }); 729 } 730 731 // ë¤ì ë¬ì ì¬ë¶ ë ì§ ì±ì°ê¸° 732 let remaining = (7 - (calendar.length % 7)) % 7; // ë¨ì 칸 ì ê³ì° 733 for (let i = 0; i < remaining; i++) { 734 calendar.push({ 735 year: month + 1 === 13 ? year + 1 : year, 736 month: month + 1 === 13 ? 1 : month + 1, 737 day: nextStartDay++, 738 currentMonth: false 739 }); 740 } 741 742 return calendar; 743 } 744 function renderCalendar(year, month) { 745 currentDate.setFullYear(year); 746 currentDate.setMonth(month-1); 747 // Days 748 const days = generateCalendar(year, month); 749 const daysWrap = calendarDialogEl.querySelector('.days'); 750 daysWrap.innerHTML = ''; 751 752 days.forEach((day) => { 753 const span = document.createElement('span'); 754 span.textContent = day.day; 755 span.id = `D${day.year}${zf(day.month)}${zf(day.day)}`; 756 span.dataset.date = `${day.year}-${zf(day.month)}-${zf(day.day)}`; 757 if (!day.currentMonth) { 758 span.classList.add('disable'); 759 } else { 760 // ì£¼ë§ ë¹íì±í && 주ë§ì¸ ê²½ì° 761 const nowDate = new Date(span.dataset.date); 762 if (disableWeekend && (nowDate.getDay() === 0 || nowDate.getDay() === 6)) { 763 span.onclick = function (e) { 764 alert({ 765 msg: 'í´ë¹ ìì¼ì ì íí ì ììµëë¤.' 766 });
767 }; 768 } else { 769 span.tabIndex = 0; 770 span.onclick = function (e) { 771 input.value = span.dataset.date; 772 dialog.close(); 773 }; 774 } 775 } 776 777 daysWrap.appendChild(span); 778 }); 779 780 // Month 781 const monthStr = calendarDialogEl.querySelector('.month-str'); 782 monthStr.textContent = `${zf(month)}ì`; 783 784 // ì´ì ë¬/ë¤ìë¬ íì±í&ë¹íì±í 785 const tempDate = new Date(); 786 tempDate.setHours(0, 0, 0, 0); 787 tempDate.setFullYear(year); 788 tempDate.setMonth(month-1); 789 tempDate.setDate(1); 790 791 prevBtn.removeAttribute('disabled'); 792 nextBtn.removeAttribute('disabled'); 793 if (tempDate <= minDate) { 794 prevBtn.setAttribute('disabled', true); 795 } 796 tempDate.setMonth(month); 797 if (tempDate > maxDate) { 798 nextBtn.setAttribute('disabled', true); 799 } 800 801 // Select 802 const selectWrap = calendarDialogEl.querySelector('.calendar-year'); 803 const select = selectWrap.querySelector('select'); 804 const SELECTABLE_YEAR_COUNT = 10; 805 806 select.innerHTML = ''; 807 for (let i = 0; i < SELECTABLE_YEAR_COUNT; i++) { 808 const option = document.createElement('option'); 809 option.textContent = year - SELECTABLE_YEAR_COUNT/2 + i; 810 option.value = option.textContent; 811 812 if (Number(option.value) === Number(year)) { 813 selectWrap.querySelector('.select-btn').textContent = year; 814 option.selected = true; 815 } 816 817 if (Number(option.textContent) >= minDate.getFullYear() && 818 Number(option.textContent) <= maxDate.getFullYear()) { 819 select.appendChild(option); 820 } 821 } 822 select.onchange = function(e) { 823 // Select ë ë ì íì í´ë¹ ë ëë¡ ë¬ë ¥ ì¬ë ëë§ 824 renderCalendar(e.target.value, month); 825 } 826 initInput(); 827 828 // ì¶ê°ë¡ ë ì§ë³ ìì± ì ì 829 // 1. ê³µí´ì¼ ì¶ê° 830 const holyDays = [...getHolyDays(year, month)]; 831 holyDays.forEach((holyDay) => { 832 const holyDayEl = document.getElementById(`D${holyDay.replaceAll('.', '').replaceAll('-', '')}`); 833 if (holyDayEl instanceof Element) { 834 holyDayEl.classList.add('holyday'); 835 holyDayEl.onclick = function (e) { 836 alert({ 837 msg: 'í´ë¹ ìì¼ì ì íí ì ììµëë¤.' 838 }); 839 }; 840 } 841 }); 842 // 2. ë¹ì¼ ë ì§ ì¶ê° 843 const newDate = new Date(); 844 const nowDayEl = document.getElementById(`D${newDate.getFullYear()}${zf(newDate.getMonth()+1)}${zf(newDate.getDate())}`); 845 if (nowDayEl !== null) { 846 nowDayEl.classList.add('now'); 847 } 848 // 3. íì±í ë ì§ Active 849 const currentDayEl = document.getElementById(`D${inputDate.getFullYear()}${zf(inputDate.getMonth()+1)}${zf(inputDate.getDate())}`); 850 if (currentDayEl !== null && 851 !currentDayEl.classList.contains('disable') && 852 !currentDayEl.classList.contains('holyday')) { 853 854 if (disableWeekend && 855 (inputDate.getDay() === 0 || inputDate.getDay() === 6)) { 856 return; 857 } 858 currentDayEl.classList.add('active'); 859 } 860 } 861 862 // Init UI, Event 863 dialog.addWillShowCallback(() => { 864 // ìµì´ 1í ë ëë§ 865 document.body.appendChild(calendarDialogEl); 866 renderCalendar(inputDate.getFullYear(), inputDate.getMonth()+1); 867 }); 868 dialog.addDidCloseCallback(() => { 869 calendarDialogEl.remove(); 870 }); 871 prevBtn.addEventListener('click', function () { 872 currentDate.setMonth(currentDate.getMonth() - 1); 873 renderCalendar(currentDate.getFullYear(), currentDate.getMonth()+1); 874 }); 875 nextBtn.addEventListener('click', function () { 876 currentDate.setMonth(currentDate.getMonth() + 1); 877 renderCalendar(currentDate.getFullYear(), currentDate.getMonth()+1); 878 }); 879 880 return dialog; 881 } 882 const loading = (target) => { 883 if ((target instanceof Element)) { 884 const loadingElement = document.createElement('div'); 885 const parent = target; 886 let originParentPosition = ''; 887 888 if (parent.style.position) { 889 originParentPosition = parent.style.position; 890 } 891 parent.style.position = 'relative'; 892 loadingElement.classList.add('loading'); 893 loadingElement.innerHTML = ` 894 <p class="blind">ë¡ë©ì¤...</p> 895 <i class="icon loading-gif"></i> 896 `; 897 898 parent.appendChild(loadingElement); 899 900 return { 901 close: () => { 902 if (originParentPosition !== '') { 903 parent.style.position = originParentPosition; 904 } else { 905 parent.style.position = ''; 906 } 907 loadingElement.remove(); 908 } 909 } 910 } else { 911 const loadingElement = document.createElement('dialog'); 912 const parent = document.body; 913 loadingElement.classList.add('loading'); 914 loadingElement.innerHTML = ` 915 <p class="blind">ë¡ë©ì¤...</p> 916 <i class="icon loading-gif"></i> 917 `; 918 919 const loading = new Dialog(loadingElement); 920 loading.addDidCloseCallback(() => { 921 loadingElement.remove(); 922 }); 923 924 parent.appendChild(loadingElement); 925 loading.show(); 926 927 return loading; 928 } 929 } 930 931 // UI.~ ë¡ ì ê·¼íëë¡ ì¤ì 932 const global = { 933 Dialog: Dialog, 934 alert: alert, 935 confirm: confirm, 936 loading: loading, 937 calendar: calendar, 938 isMobile: () => { 939 const MOBILE_WIDTH = 768; // 768px ë¶í° 모ë°ì¼ë¡ íë¨ 940 return window.innerWidth <= MOBILE_WIDTH; 941 }, 942 isPC: () => { 943 const userAgent = navigator.userAgent.toLowerCase(); 944 945 // 모ë°ì¼ ë° íë¸ë¦¿ 기기 User-Agent í¨í´ ëª©ë¡ 946 const mobileRegex = /(iphone|ipod|ipad|android|blackberry|bb10|windows phone|mobile|tablet)/; 947 // íë¸ë¦¿ ì¢ ë¥ì¸ì§ íì¸ 948 const isIPad = (userAgent.includes("ipad")) || 949 (userAgent.includes("macintosh") && "ontouchend" in document && navigator.maxTouchPoints > 0); 950 // ìëë¡ì´ë í ë¸ë¦¿ ì¢ ë¥ íì¸ 951 const isAndroidTablet = userAgent.includes("android") && !userAgent.includes("mobile"); 952 953 // 기본 User-Agent ê²ì¬ (모ë°ì¼, íë¸ë¦¿ì´ë©´ false) 954 if (mobileRegex.test(userAgent)) { 955 return false; 956 } 957 // íë¸ë¦¿ì PCê° ìë
958 if (isIPad || isAndroidTablet) { 959 return false; 960 } 961 // User-Agent Client Hints ì§ì ì¬ë¶ íì¸ 962 if (navigator.userAgentData && navigator.userAgentData.platform) { 963 const platform = navigator.userAgentData.platform.toLowerCase(); 964 965 // ì¼ë°ì ì¼ë¡ PC íë«í¼ì¼ë¡ ë¶ë¥ëë ê° íì¸ 966 const pcPlatforms = ["windows", "macos", "linux", "chrome os"]; 967 968 return pcPlatforms.includes(platform); 969 } 970 971 // íë©´ í¬ê¸°ë¥¼ ì¶ê°ì ì¼ë¡ ê²ì¬ 972 if (global.isMobile()) { 973 return false; 974 } 975 976 return true; // 기본ì ì¼ë¡ PCë¡ ê°ì£¼ 977 } 978 } 979 window.UI = global; 980 } 981 initUI(); 982})();
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.