@designbasekorea/ui-wc 0.5.5 → 0.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/dist/chunks/lottie-e273b936.js +17032 -0
  2. package/dist/esm/components/animation-background/canvas-controller.js +121 -1
  3. package/dist/esm/components/animation-background/controls.js +180 -1
  4. package/dist/esm/components/animation-background/graphite-controller.js +196 -1
  5. package/dist/esm/components/animation-background/graphite-presets.js +42 -1
  6. package/dist/esm/components/animation-background/graphite-shader.js +111 -1
  7. package/dist/esm/components/animation-background/layers.js +369 -1
  8. package/dist/esm/components/animation-background/lumina-presets.js +53 -1
  9. package/dist/esm/components/animation-background/lumina-shaders.js +181 -1
  10. package/dist/esm/components/animation-background/lumina-utils.js +60 -1
  11. package/dist/esm/components/animation-background/particle-defaults.js +36 -1
  12. package/dist/esm/components/animation-background/renderers.js +167 -1
  13. package/dist/esm/components/animation-background/shader-controller.js +154 -1
  14. package/dist/esm/components/color-picker-utils.js +134 -1
  15. package/dist/esm/components/date-picker-utils.js +140 -1
  16. package/dist/esm/components/db-accordion.js +304 -1
  17. package/dist/esm/components/db-ad-banner.js +455 -1
  18. package/dist/esm/components/db-alert.js +149 -1
  19. package/dist/esm/components/db-animation-background.js +486 -1
  20. package/dist/esm/components/db-animation-text.js +601 -1
  21. package/dist/esm/components/db-app-badge.js +88 -1
  22. package/dist/esm/components/db-audio-player.js +752 -1
  23. package/dist/esm/components/db-avatar.js +188 -1
  24. package/dist/esm/components/db-backdrop.js +139 -1
  25. package/dist/esm/components/db-badge.js +91 -1
  26. package/dist/esm/components/db-banner.js +188 -1
  27. package/dist/esm/components/db-bottom-navigation.js +146 -1
  28. package/dist/esm/components/db-bottom-sheet.js +481 -1
  29. package/dist/esm/components/db-breadcrumbs.js +329 -1
  30. package/dist/esm/components/db-button.js +316 -1
  31. package/dist/esm/components/db-calendar.js +675 -1
  32. package/dist/esm/components/db-card.js +265 -1
  33. package/dist/esm/components/db-carousel.js +264 -1
  34. package/dist/esm/components/db-checkbox.js +202 -1
  35. package/dist/esm/components/db-chip.js +104 -1
  36. package/dist/esm/components/db-code-block.js +304 -1
  37. package/dist/esm/components/db-color-picker.js +746 -1
  38. package/dist/esm/components/db-confirm.js +191 -1
  39. package/dist/esm/components/db-container.js +39 -1
  40. package/dist/esm/components/db-context-menu.js +265 -1
  41. package/dist/esm/components/db-countdown.js +146 -1
  42. package/dist/esm/components/db-cursor-follower.js +173 -1
  43. package/dist/esm/components/db-date-picker.js +837 -1
  44. package/dist/esm/components/db-divider.js +66 -1
  45. package/dist/esm/components/db-drawer.js +245 -1
  46. package/dist/esm/components/db-dropdown.js +493 -1
  47. package/dist/esm/components/db-dropzone.js +266 -1
  48. package/dist/esm/components/db-empty-state.js +97 -1
  49. package/dist/esm/components/db-file-uploader.js +256 -1
  50. package/dist/esm/components/db-floating-action-button.js +139 -1
  51. package/dist/esm/components/db-footer.js +385 -1
  52. package/dist/esm/components/db-form.js +566 -1
  53. package/dist/esm/components/db-gantt.js +882 -1
  54. package/dist/esm/components/db-gradient.js +133 -1
  55. package/dist/esm/components/db-grid-background.js +909 -1
  56. package/dist/esm/components/db-grid.js +73 -1
  57. package/dist/esm/components/db-icons.js +82 -1
  58. package/dist/esm/components/db-image-list.js +214 -1
  59. package/dist/esm/components/db-image-placeholder.js +96 -1
  60. package/dist/esm/components/db-image.js +289 -1
  61. package/dist/esm/components/db-indicator.js +517 -1
  62. package/dist/esm/components/db-input.js +420 -1
  63. package/dist/esm/components/db-kanban.js +538 -1
  64. package/dist/esm/components/db-label.js +66 -1
  65. package/dist/esm/components/db-lightbox.js +372 -1
  66. package/dist/esm/components/db-list.js +340 -1
  67. package/dist/esm/components/db-logo-svg.js +72 -1
  68. package/dist/esm/components/db-logo.js +106 -1
  69. package/dist/esm/components/db-lottie.js +109 -1
  70. package/dist/esm/components/db-markdown-editor.js +458 -1
  71. package/dist/esm/components/db-marquee.js +402 -1
  72. package/dist/esm/components/db-masonry.js +94 -1
  73. package/dist/esm/components/db-menu-item.js +211 -1
  74. package/dist/esm/components/db-modal.js +522 -1
  75. package/dist/esm/components/db-navbar.js +680 -1
  76. package/dist/esm/components/db-onboarding-modal.js +322 -1
  77. package/dist/esm/components/db-page-header.js +312 -1
  78. package/dist/esm/components/db-pagination.js +271 -1
  79. package/dist/esm/components/db-popover.js +580 -1
  80. package/dist/esm/components/db-progress-step.js +166 -1
  81. package/dist/esm/components/db-progress.js +171 -1
  82. package/dist/esm/components/db-progressbar.js +72 -1
  83. package/dist/esm/components/db-radio.js +172 -1
  84. package/dist/esm/components/db-random-gradient.js +121 -1
  85. package/dist/esm/components/db-range-slider.js +811 -1
  86. package/dist/esm/components/db-rating.js +325 -1
  87. package/dist/esm/components/db-reorder.js +286 -1
  88. package/dist/esm/components/db-resizable-panels.js +282 -1
  89. package/dist/esm/components/db-scroll-area.js +126 -1
  90. package/dist/esm/components/db-search-bar.js +668 -1
  91. package/dist/esm/components/db-section-app-download.js +101 -1
  92. package/dist/esm/components/db-section-contact.js +102 -1
  93. package/dist/esm/components/db-section-cta.js +150 -1
  94. package/dist/esm/components/db-section-faq.js +120 -1
  95. package/dist/esm/components/db-section-feature-grid.js +137 -1
  96. package/dist/esm/components/db-section-feature.js +179 -1
  97. package/dist/esm/components/db-section-hero.js +243 -1
  98. package/dist/esm/components/db-section-logo-cloud.js +138 -1
  99. package/dist/esm/components/db-section-pricing.js +166 -1
  100. package/dist/esm/components/db-section-stats.js +129 -1
  101. package/dist/esm/components/db-section-testimonials.js +252 -1
  102. package/dist/esm/components/db-section.js +385 -1
  103. package/dist/esm/components/db-segment-control.js +231 -1
  104. package/dist/esm/components/db-select.js +571 -1
  105. package/dist/esm/components/db-share-icons.js +40 -1
  106. package/dist/esm/components/db-share.js +489 -1
  107. package/dist/esm/components/db-sidebar.js +1068 -1
  108. package/dist/esm/components/db-skeleton.js +94 -1
  109. package/dist/esm/components/db-spinner.js +102 -1
  110. package/dist/esm/components/db-split-view.js +297 -1
  111. package/dist/esm/components/db-stack.js +46 -1
  112. package/dist/esm/components/db-stat.js +218 -1
  113. package/dist/esm/components/db-stepper.js +398 -1
  114. package/dist/esm/components/db-table.js +412 -1
  115. package/dist/esm/components/db-tabs.js +335 -1
  116. package/dist/esm/components/db-testimonial.js +162 -1
  117. package/dist/esm/components/db-textarea.js +233 -1
  118. package/dist/esm/components/db-time-picker.js +523 -1
  119. package/dist/esm/components/db-timeline.js +174 -1
  120. package/dist/esm/components/db-toast.js +158 -1
  121. package/dist/esm/components/db-toggle.js +162 -1
  122. package/dist/esm/components/db-toolbar.js +94 -1
  123. package/dist/esm/components/db-tooltip.js +440 -1
  124. package/dist/esm/components/db-top-banner.js +121 -1
  125. package/dist/esm/components/db-tutorial.js +324 -1
  126. package/dist/esm/components/db-video-player.js +503 -1
  127. package/dist/esm/components/db-youtube-player.js +194 -1
  128. package/dist/esm/components/highlight-code.js +65 -1
  129. package/dist/esm/components/input-validation.js +50 -1
  130. package/dist/esm/components/marketing-block-host-utils.js +9 -1
  131. package/dist/esm/components/number-scrub.js +117 -1
  132. package/dist/esm/components/random-gradient-palettes.js +67 -1
  133. package/dist/esm/components/section-block-header-utils.js +48 -1
  134. package/dist/esm/helpers/confirm.js +52 -1
  135. package/dist/esm/helpers/toast.js +73 -1
  136. package/dist/esm/index.js +111 -1
  137. package/dist/esm/node_modules/prismjs/components/prism-bash.js +235 -1
  138. package/dist/esm/node_modules/prismjs/components/prism-clike.js +31 -1
  139. package/dist/esm/node_modules/prismjs/components/prism-css.js +64 -1
  140. package/dist/esm/node_modules/prismjs/components/prism-javascript.js +172 -1
  141. package/dist/esm/node_modules/prismjs/components/prism-json.js +27 -1
  142. package/dist/esm/node_modules/prismjs/components/prism-jsx.js +143 -1
  143. package/dist/esm/node_modules/prismjs/components/prism-markup.js +186 -1
  144. package/dist/esm/node_modules/prismjs/components/prism-scss.js +81 -1
  145. package/dist/esm/node_modules/prismjs/components/prism-tsx.js +15 -1
  146. package/dist/esm/node_modules/prismjs/components/prism-typescript.js +60 -1
  147. package/dist/index.cjs +55414 -1
  148. package/dist/index.css +1 -1
  149. package/dist/index.esm.js +38284 -1
  150. package/dist/react/chunks/lottie-72a4c6d4.js +17032 -0
  151. package/dist/react/index.cjs +56503 -1
  152. package/dist/react/index.esm.js +39373 -1
  153. package/package.json +1 -1
  154. package/dist/chunks/lottie-2637aa0f.js +0 -1
  155. package/dist/react/chunks/lottie-14e7addb.js +0 -1
@@ -1 +1,675 @@
1
- import{createDbIcon as e,getDbComponentIconSize as t}from"./db-icons.js";import{parseISODate as a,toISODate as n}from"./date-picker-utils.js";import"./db-modal.js";import"./db-input.js";import"./db-select.js";import"./db-date-picker.js";import"./db-time-picker.js";import"./db-button.js";const s="designbase-wc-calendar",i=[{value:"meeting",label:"회의"},{value:"task",label:"업무"},{value:"reminder",label:"알림"},{value:"birthday",label:"생일"},{value:"holiday",label:"휴일"},{value:"custom",label:"기타"}],d=[{value:"#17a2b8",label:"청록"},{value:"#0d6efd",label:"파랑"},{value:"#198754",label:"초록"},{value:"#ffc107",label:"노랑"},{value:"#dc3545",label:"빨강"},{value:"#6f42c1",label:"보라"}];class DbCalendar extends HTMLElement{constructor(){super(...arguments),this.built=!1,this.currentView="month",this.selectedDate=new Date,this._events=[],this.showEventModal=!1,this.selectedEvent=null,this.isEditing=!1,this.draggedEvent=null,this.dragOverDate=null,this.selectedDateRange={start:null,end:null},this.eventForm={title:"",description:"",startTime:"09:00",endTime:"10:00",type:"meeting",color:"#17a2b8",isAllDay:!1,location:""}}static get observedAttributes(){return["view","current-date","events","locale","show-weekends","highlight-today","show-outside-days"]}connectedCallback(){this.built||this.build(),this.readAttributes(),this.render()}attributeChangedCallback(){this.built&&(this.readAttributes(),this.render())}get locale(){return this.getAttribute("locale")||"ko-KR"}get showWeekends(){return"false"!==this.getAttribute("show-weekends")}get highlightToday(){return"false"!==this.getAttribute("highlight-today")}get showOutsideDays(){return"false"!==this.getAttribute("show-outside-days")}get events(){return this._events}set events(e){"string"!=typeof e?(this._events=Array.isArray(e)?e:[],this.setAttribute("events",JSON.stringify(this._events))):this.setAttribute("events",e)}build(){this.built=!0,this.headerEl=document.createElement("div"),this.headerEl.className=`${s}__header`,this.bodyEl=document.createElement("div"),this.bodyEl.className=`${s}__body`,this.modalEl=document.createElement("db-modal"),this.modalEl.setAttribute("size","m"),this.modalEl.addEventListener("db-close",()=>this.closeModal())}readAttributes(){this.currentView="month";const e=a(this.getAttribute("current-date"));e&&(this.selectedDate=e);const t=this.getAttribute("events");if(t)try{const e=JSON.parse(t);this._events=Array.isArray(e)?e:[]}catch{this._events=[]}else this._events=[]}isSameDate(e,t){return e.getFullYear()===t.getFullYear()&&e.getMonth()===t.getMonth()&&e.getDate()===t.getDate()}eventsForDate(e){return this._events.filter(t=>{const n=a(t.date);return!!n&&this.isSameDate(n,e)})}emit(e,t){this.dispatchEvent(new CustomEvent(e,{detail:t,bubbles:!0,composed:!0}))}handleViewChange(e){this.currentView="month",this.emit("db-view-change",{view:e}),this.render()}shiftMonth(e){const t=new Date(this.selectedDate);t.setMonth(t.getMonth()+e),this.selectedDate=t,this.emit("db-month-change",{date:n(t)}),this.render()}shiftYear(e){const t=new Date(this.selectedDate);t.setFullYear(t.getFullYear()+e),this.selectedDate=t,this.emit("db-month-change",{date:n(t)}),this.render()}handleDateClick(e){this.selectedDate=e,this.emit("db-date-click",{date:n(e)}),!this.selectedDateRange.start||this.selectedDateRange.start&&this.selectedDateRange.end?this.selectedDateRange={start:e,end:null}:this.selectedDateRange.start&&!this.selectedDateRange.end&&(e<this.selectedDateRange.start?this.selectedDateRange={start:e,end:this.selectedDateRange.start}:this.selectedDateRange={start:this.selectedDateRange.start,end:e}),this.openAddModal(e)}handleDragStart(e,t){this.draggedEvent=e,t.dataTransfer?.setData("text/plain",e.id),t.dataTransfer?.setData("application/json",JSON.stringify(e)),t.dataTransfer&&(t.dataTransfer.effectAllowed="move")}handleDragOver(e,t){t.preventDefault(),t.dataTransfer&&(t.dataTransfer.dropEffect="move"),this.dragOverDate=e,this.render()}handleDragLeave(e){const t=e.relatedTarget,a=e.currentTarget;a&&t&&a.contains(t)||(this.dragOverDate=null,this.render())}handleDrop(e,t){if(t.preventDefault(),!this.draggedEvent)return;const a={...this.draggedEvent,date:n(e)};this.emit("db-event-edit",{event:a}),this.draggedEvent=null,this.dragOverDate=null,this.render()}handleDragEnd(){this.draggedEvent=null,this.dragOverDate=null,this.render()}handleEventClick(e,t){t.stopPropagation(),this.selectedEvent=e,this.isEditing=!0;const n=a(e.date);n&&(this.selectedDate=n),this.eventForm={title:e.title,description:e.description||"",startTime:e.startTime||"09:00",endTime:e.endTime||"10:00",type:e.type,color:e.color||"#17a2b8",isAllDay:e.isAllDay||!1,location:e.location||""},this.emit("db-event-click",{event:e}),this.showEventModal=!0,this.render()}openAddModal(e){this.selectedDate=e,this.selectedEvent=null,this.isEditing=!1,this.eventForm={title:"",description:"",startTime:"09:00",endTime:"10:00",type:"meeting",color:"#17a2b8",isAllDay:!1,location:""},this.showEventModal=!0,this.render()}closeModal(){this.showEventModal=!1,this.selectedEvent=null,this.isEditing=!1,this.render()}saveEvent(){if(!this.eventForm.title.trim())return void window.alert("제목을 입력해주세요.");const e={title:this.eventForm.title.trim(),description:this.eventForm.description,date:n(this.selectedDate),startTime:this.eventForm.startTime,endTime:this.eventForm.endTime,type:this.eventForm.type,color:this.eventForm.color,isAllDay:this.eventForm.isAllDay,location:this.eventForm.location};if(this.isEditing&&this.selectedEvent){const t={...this.selectedEvent,...e};this.emit("db-event-edit",{event:t})}else this.emit("db-event-add",{event:e});this.closeModal()}deleteEvent(){this.selectedEvent&&window.confirm("정말로 이 일정을 삭제하시겠습니까?")&&(this.emit("db-event-delete",{eventId:this.selectedEvent.id}),this.closeModal())}renderHeader(){if(!this.headerEl)return;this.headerEl.replaceChildren();const a=document.createElement("div");a.className=`${s}__navigation`;const n=t("m"),i=document.createElement("button");i.type="button",i.className=`${s}__nav-button`,i.setAttribute("aria-label","이전"),i.appendChild(e("chevron-left",n)),i.addEventListener("click",()=>{this.shiftMonth(-1)});const d=document.createElement("div");d.className=`${s}__title`,d.textContent=this.selectedDate.toLocaleDateString(this.locale,{year:"numeric",month:"long"});const l=document.createElement("button");l.type="button",l.className=`${s}__nav-button`,l.setAttribute("aria-label","다음"),l.appendChild(e("chevron-right",n)),l.addEventListener("click",()=>{this.shiftMonth(1)}),a.append(i,d,l),this.headerEl.appendChild(a)}renderMonthView(){const e=document.createElement("div");e.className=`${s}__month-view`;const t=document.createElement("div");t.className=`${s}__weekdays`,["일","월","화","수","목","금","토"].forEach(e=>{const a=document.createElement("div");a.className=`${s}__weekday`,a.textContent=e,t.appendChild(a)}),e.appendChild(t);const a=document.createElement("div");a.className=`${s}__days`;const n=this.selectedDate.getFullYear(),i=this.selectedDate.getMonth(),d=new Date(n,i,1),l=new Date(d);l.setDate(l.getDate()-d.getDay());const r=new Date;for(let e=0;e<42;e+=1){const t=new Date(l);t.setDate(l.getDate()+e);const n=t.getMonth()===i;if(!this.showOutsideDays&&!n)continue;const d=0===t.getDay()||6===t.getDay();if(!this.showWeekends&&d)continue;const o=document.createElement("div"),c=this.selectedDateRange.start&&this.selectedDateRange.end&&t>=this.selectedDateRange.start&&t<=this.selectedDateRange.end,h=this.selectedDateRange.start&&t.toDateString()===this.selectedDateRange.start.toDateString(),m=this.selectedDateRange.end&&t.toDateString()===this.selectedDateRange.end.toDateString(),u=Boolean(c&&!h&&!m);o.className=[`${s}__day`,n&&`${s}__day--current-month`,this.highlightToday&&t.toDateString()===r.toDateString()&&`${s}__day--today`,this.isSameDate(t,this.selectedDate)&&`${s}__day--selected`,h&&`${s}__day--selected--start`,m&&`${s}__day--selected--end`,u&&`${s}__day--selected--middle`,!this.showWeekends&&d&&`${s}__day--weekend`].filter(Boolean).join(" "),o.addEventListener("click",()=>this.handleDateClick(t));const v=document.createElement("span");v.className=`${s}__day-number`,v.textContent=String(t.getDate()),o.appendChild(v);const p=this.eventsForDate(t);if(p.length){const e=document.createElement("div");if(e.className=`${s}__day-events`,p.slice(0,2).forEach(t=>{const a=document.createElement("div");a.className=[`${s}__event-bar`,`${s}__event-bar--${t.type}`].filter(Boolean).join(" "),t.color&&(a.style.backgroundColor=t.color),this.draggedEvent?.id===t.id&&a.classList.add(`${s}__event-bar--dragging`),a.title=t.title,a.draggable=!0;const n=document.createElement("span");n.className=`${s}__event-title`,n.textContent=t.title,a.appendChild(n),a.addEventListener("click",e=>this.handleEventClick(t,e)),a.addEventListener("dragstart",e=>this.handleDragStart(t,e)),a.addEventListener("dragend",()=>this.handleDragEnd()),e.appendChild(a)}),p.length>2){const t=document.createElement("div");t.className=`${s}__more-events`,t.textContent=`+${p.length-2} more`,e.appendChild(t)}o.appendChild(e)}const g=document.createElement("div");g.className=[`${s}__drop-zone`,this.dragOverDate&&this.isSameDate(this.dragOverDate,t)&&`${s}__drop-zone--active`].filter(Boolean).join(" "),g.addEventListener("dragover",e=>this.handleDragOver(t,e)),g.addEventListener("dragleave",e=>this.handleDragLeave(e)),g.addEventListener("drop",e=>this.handleDrop(t,e)),o.appendChild(g),a.appendChild(o)}return e.appendChild(a),e}renderYearView(){const e=document.createElement("div");e.className=`${s}__year-view`;const t=this.selectedDate.getFullYear();for(let n=0;n<12;n+=1){const i=new Date(t,n,1),d=this._events.filter(e=>{const s=a(e.date);return s&&s.getFullYear()===t&&s.getMonth()===n}),l=document.createElement("div");l.className=`${s}__year-month`,l.addEventListener("click",()=>{this.selectedDate=i,this.handleViewChange("month")});const r=document.createElement("h3");if(r.className=`${s}__year-month-title`,r.textContent=i.toLocaleDateString(this.locale,{month:"long"}),l.appendChild(r),d.length){const e=document.createElement("div");if(e.className=`${s}__year-month-events`,d.slice(0,4).forEach(t=>{const a=document.createElement("span");a.className=[`${s}__year-event`,`${s}__year-event--${t.type}`].join(" "),t.color&&(a.style.backgroundColor=t.color),e.appendChild(a)}),d.length>4){const t=document.createElement("span");t.className=`${s}__year-more-events`,t.textContent=`+${d.length-4} more`,e.appendChild(t)}l.appendChild(e)}e.appendChild(l)}return e}renderWeekView(){const e=document.createElement("div");e.className=`${s}__week-view`;const t=new Date,a=new Date(this.selectedDate);a.setDate(this.selectedDate.getDate()-this.selectedDate.getDay());const n=document.createElement("div");n.className=`${s}__week-header`;const i=document.createElement("div");i.className=`${s}__time-column-header`,n.appendChild(i);for(let e=0;e<7;e+=1){const i=new Date(a);i.setDate(a.getDate()+e);const d=document.createElement("div");d.className=[`${s}__week-day-header`,i.toDateString()===t.toDateString()&&`${s}__week-day-header--today`].filter(Boolean).join(" ");const l=document.createElement("span");l.className=`${s}__week-day-name`,l.textContent=i.toLocaleDateString(this.locale,{weekday:"short"});const r=document.createElement("span");r.className=`${s}__week-day-number`,r.textContent=String(i.getDate()),d.append(l,r),n.appendChild(d)}e.appendChild(n);const d=document.createElement("div");d.className=`${s}__week-grid`;const l=document.createElement("div");l.className=`${s}__week-time-column`;for(let e=6;e<=22;e+=1){const t=document.createElement("div");t.className=`${s}__time-slot`;const a=document.createElement("span");a.className=`${s}__time-label`,a.textContent=`${String(e).padStart(2,"0")}:00`,t.appendChild(a),l.appendChild(t)}d.appendChild(l);const r=document.createElement("div");r.className=`${s}__week-columns`;for(let e=0;e<7;e+=1){const t=new Date(a);t.setDate(a.getDate()+e);const n=document.createElement("div");n.className=`${s}__week-column`;for(let e=6;e<=22;e+=1){const a=document.createElement("div");a.className=`${s}__week-cell`,a.title=`${t.toLocaleDateString()} ${String(e).padStart(2,"0")}:00`,a.addEventListener("click",()=>{this.selectedDate=new Date(t),this.eventForm.startTime=`${String(e).padStart(2,"0")}:00`,this.eventForm.endTime=`${String(e+1).padStart(2,"0")}:00`,this.openAddModal(this.selectedDate)}),n.appendChild(a)}this.eventsForDate(t).forEach(e=>{if(!e.startTime)return;const[t,a]=e.startTime.split(":").map(Number),i=.8*(60*(t-6)+(a||0)),d=(e.endTime||e.startTime).split(":").map(Number),l=60*(d[0]||t)+(d[1]||0),r=60*t+(a||0),o=Math.max(20,48*(l-r)/60),c=document.createElement("div");c.className=[`${s}__week-event`,`${s}__week-event--${e.type}`].filter(Boolean).join(" "),c.textContent=e.title,c.title=e.title,c.style.backgroundColor=e.color||"#17a2b8",c.style.top=`${i}px`,c.style.height=`${o}px`,c.addEventListener("click",t=>this.handleEventClick(e,t)),n.appendChild(c)}),r.appendChild(n)}return d.appendChild(r),e.appendChild(d),e}renderModal(){if(!this.modalEl)return;if(this.modalEl.replaceChildren(),!this.showEventModal)return void this.modalEl.removeAttribute("open");this.modalEl.setAttribute("open","true"),this.modalEl.setAttribute("title",this.isEditing?"일정 편집":"새 일정 추가");const e=document.createElement("div");e.className=`${s}__event-form`;const t=document.createElement("db-input");t.setAttribute("label","제목 *"),t.setAttribute("placeholder","일정 제목을 입력하세요"),t.setAttribute("full-width",""),t.setAttribute("value",this.eventForm.title),t.addEventListener("db-input",e=>{this.eventForm.title=e.detail.value});const l=document.createElement("db-input");l.setAttribute("label","설명"),l.setAttribute("placeholder","일정 설명을 입력하세요"),l.setAttribute("full-width",""),l.setAttribute("value",this.eventForm.description),l.addEventListener("db-input",e=>{this.eventForm.description=e.detail.value});const r=document.createElement("db-date-picker");r.setAttribute("label","날짜"),r.setAttribute("full-width",""),r.setAttribute("value",n(this.selectedDate)),r.addEventListener("db-change",e=>{const t=a(e.detail.value);t&&(this.selectedDate=t)});const o=document.createElement("db-select");o.setAttribute("label","일정 타입"),o.setAttribute("full-width",""),o.setAttribute("options",JSON.stringify(i)),o.setAttribute("value",this.eventForm.type),o.addEventListener("db-change",e=>{this.eventForm.type=e.detail.value});const c=document.createElement("db-select");c.setAttribute("label","색상"),c.setAttribute("full-width",""),c.setAttribute("options",JSON.stringify(d)),c.setAttribute("value",this.eventForm.color),c.addEventListener("db-change",e=>{this.eventForm.color=e.detail.value});const h=document.createElement("db-time-picker");h.setAttribute("label","시작 시간"),h.setAttribute("full-width",""),h.setAttribute("value",this.eventForm.startTime),h.addEventListener("db-change",e=>{this.eventForm.startTime=e.detail.value});const m=document.createElement("db-time-picker");m.setAttribute("label","종료 시간"),m.setAttribute("full-width",""),m.setAttribute("value",this.eventForm.endTime),m.addEventListener("db-change",e=>{this.eventForm.endTime=e.detail.value}),e.append(t,l,r,o,c,h,m);const u=document.createElement("div");u.className="designbase-wc-modal__body",u.setAttribute("data-modal-part","body"),u.appendChild(e);const v=document.createElement("div");v.className="designbase-wc-modal__footer",v.setAttribute("data-modal-part","footer");const p=document.createElement("div");if(p.className=`${s}__modal-actions`,this.isEditing){const e=document.createElement("db-button");e.setAttribute("variant","danger"),e.textContent="삭제",e.addEventListener("click",()=>this.deleteEvent()),p.appendChild(e)}const g=document.createElement("div");g.className=`${s}__modal-buttons`;const b=document.createElement("db-button");b.setAttribute("variant","secondary"),b.textContent="취소",b.addEventListener("click",()=>this.closeModal());const E=document.createElement("db-button");E.setAttribute("variant","primary"),E.textContent=this.isEditing?"수정":"저장",E.addEventListener("click",()=>this.saveEvent()),g.append(b,E),p.appendChild(g),v.appendChild(p),this.modalEl.append(u,v)}render(){this.className=s,this.replaceChildren(),this.headerEl&&this.bodyEl&&(this.renderHeader(),this.bodyEl.replaceChildren(),this.bodyEl.appendChild(this.renderMonthView()),this.append(this.headerEl,this.bodyEl),this.renderModal(),this.showEventModal&&this.modalEl&&this.appendChild(this.modalEl))}}customElements.get("db-calendar")||customElements.define("db-calendar",DbCalendar);export{DbCalendar};
1
+ import { createDbIcon, getDbComponentIconSize } from './db-icons.js';
2
+ import { parseISODate, toISODate } from './date-picker-utils.js';
3
+ import './db-modal.js';
4
+ import './db-input.js';
5
+ import './db-select.js';
6
+ import './db-date-picker.js';
7
+ import './db-time-picker.js';
8
+ import './db-button.js';
9
+
10
+ const CLASS = 'designbase-wc-calendar';
11
+ const EVENT_TYPE_OPTIONS = [
12
+ { value: 'meeting', label: '회의' },
13
+ { value: 'task', label: '업무' },
14
+ { value: 'reminder', label: '알림' },
15
+ { value: 'birthday', label: '생일' },
16
+ { value: 'holiday', label: '휴일' },
17
+ { value: 'custom', label: '기타' },
18
+ ];
19
+ const COLOR_OPTIONS = [
20
+ { value: '#17a2b8', label: '청록' },
21
+ { value: '#0d6efd', label: '파랑' },
22
+ { value: '#198754', label: '초록' },
23
+ { value: '#ffc107', label: '노랑' },
24
+ { value: '#dc3545', label: '빨강' },
25
+ { value: '#6f42c1', label: '보라' },
26
+ ];
27
+ class DbCalendar extends HTMLElement {
28
+ constructor() {
29
+ super(...arguments);
30
+ this.built = false;
31
+ this.currentView = 'month';
32
+ this.selectedDate = new Date();
33
+ this._events = [];
34
+ this.showEventModal = false;
35
+ this.selectedEvent = null;
36
+ this.isEditing = false;
37
+ this.draggedEvent = null;
38
+ this.dragOverDate = null;
39
+ this.selectedDateRange = { start: null, end: null };
40
+ this.eventForm = {
41
+ title: '',
42
+ description: '',
43
+ startTime: '09:00',
44
+ endTime: '10:00',
45
+ type: 'meeting',
46
+ color: '#17a2b8',
47
+ isAllDay: false,
48
+ location: '',
49
+ };
50
+ }
51
+ static get observedAttributes() {
52
+ return [
53
+ 'view',
54
+ 'current-date',
55
+ 'events',
56
+ 'locale',
57
+ 'show-weekends',
58
+ 'highlight-today',
59
+ 'show-outside-days',
60
+ ];
61
+ }
62
+ connectedCallback() {
63
+ if (!this.built)
64
+ this.build();
65
+ this.readAttributes();
66
+ this.render();
67
+ }
68
+ attributeChangedCallback() {
69
+ if (!this.built)
70
+ return;
71
+ this.readAttributes();
72
+ this.render();
73
+ }
74
+ get locale() {
75
+ return this.getAttribute('locale') || 'ko-KR';
76
+ }
77
+ get showWeekends() {
78
+ return this.getAttribute('show-weekends') !== 'false';
79
+ }
80
+ get highlightToday() {
81
+ return this.getAttribute('highlight-today') !== 'false';
82
+ }
83
+ get showOutsideDays() {
84
+ return this.getAttribute('show-outside-days') !== 'false';
85
+ }
86
+ get events() {
87
+ return this._events;
88
+ }
89
+ set events(value) {
90
+ if (typeof value === 'string') {
91
+ this.setAttribute('events', value);
92
+ return;
93
+ }
94
+ this._events = Array.isArray(value) ? value : [];
95
+ this.setAttribute('events', JSON.stringify(this._events));
96
+ }
97
+ build() {
98
+ this.built = true;
99
+ this.headerEl = document.createElement('div');
100
+ this.headerEl.className = `${CLASS}__header`;
101
+ this.bodyEl = document.createElement('div');
102
+ this.bodyEl.className = `${CLASS}__body`;
103
+ this.modalEl = document.createElement('db-modal');
104
+ this.modalEl.setAttribute('size', 'm');
105
+ this.modalEl.addEventListener('db-close', () => this.closeModal());
106
+ }
107
+ readAttributes() {
108
+ this.currentView = 'month';
109
+ const current = parseISODate(this.getAttribute('current-date'));
110
+ if (current)
111
+ this.selectedDate = current;
112
+ const rawEvents = this.getAttribute('events');
113
+ if (rawEvents) {
114
+ try {
115
+ const parsed = JSON.parse(rawEvents);
116
+ this._events = Array.isArray(parsed) ? parsed : [];
117
+ }
118
+ catch {
119
+ this._events = [];
120
+ }
121
+ }
122
+ else {
123
+ this._events = [];
124
+ }
125
+ }
126
+ isSameDate(a, b) {
127
+ return (a.getFullYear() === b.getFullYear() &&
128
+ a.getMonth() === b.getMonth() &&
129
+ a.getDate() === b.getDate());
130
+ }
131
+ eventsForDate(date) {
132
+ return this._events.filter((event) => {
133
+ const eventDate = parseISODate(event.date);
134
+ return eventDate ? this.isSameDate(eventDate, date) : false;
135
+ });
136
+ }
137
+ emit(name, detail) {
138
+ this.dispatchEvent(new CustomEvent(name, { detail, bubbles: true, composed: true }));
139
+ }
140
+ handleViewChange(view) {
141
+ this.currentView = 'month';
142
+ this.emit('db-view-change', { view });
143
+ this.render();
144
+ }
145
+ shiftMonth(delta) {
146
+ const next = new Date(this.selectedDate);
147
+ next.setMonth(next.getMonth() + delta);
148
+ this.selectedDate = next;
149
+ this.emit('db-month-change', { date: toISODate(next) });
150
+ this.render();
151
+ }
152
+ shiftYear(delta) {
153
+ const next = new Date(this.selectedDate);
154
+ next.setFullYear(next.getFullYear() + delta);
155
+ this.selectedDate = next;
156
+ this.emit('db-month-change', { date: toISODate(next) });
157
+ this.render();
158
+ }
159
+ handleDateClick(date) {
160
+ this.selectedDate = date;
161
+ this.emit('db-date-click', { date: toISODate(date) });
162
+ if (!this.selectedDateRange.start || (this.selectedDateRange.start && this.selectedDateRange.end)) {
163
+ this.selectedDateRange = { start: date, end: null };
164
+ }
165
+ else if (this.selectedDateRange.start && !this.selectedDateRange.end) {
166
+ if (date < this.selectedDateRange.start) {
167
+ this.selectedDateRange = { start: date, end: this.selectedDateRange.start };
168
+ }
169
+ else {
170
+ this.selectedDateRange = { start: this.selectedDateRange.start, end: date };
171
+ }
172
+ }
173
+ this.openAddModal(date);
174
+ }
175
+ handleDragStart(event, dragEvent) {
176
+ this.draggedEvent = event;
177
+ dragEvent.dataTransfer?.setData('text/plain', event.id);
178
+ dragEvent.dataTransfer?.setData('application/json', JSON.stringify(event));
179
+ if (dragEvent.dataTransfer)
180
+ dragEvent.dataTransfer.effectAllowed = 'move';
181
+ }
182
+ handleDragOver(date, dragEvent) {
183
+ dragEvent.preventDefault();
184
+ if (dragEvent.dataTransfer)
185
+ dragEvent.dataTransfer.dropEffect = 'move';
186
+ this.dragOverDate = date;
187
+ this.render();
188
+ }
189
+ handleDragLeave(dragEvent) {
190
+ const related = dragEvent.relatedTarget;
191
+ const current = dragEvent.currentTarget;
192
+ if (current && related && current.contains(related))
193
+ return;
194
+ this.dragOverDate = null;
195
+ this.render();
196
+ }
197
+ handleDrop(date, dragEvent) {
198
+ dragEvent.preventDefault();
199
+ if (!this.draggedEvent)
200
+ return;
201
+ const updated = { ...this.draggedEvent, date: toISODate(date) };
202
+ this.emit('db-event-edit', { event: updated });
203
+ this.draggedEvent = null;
204
+ this.dragOverDate = null;
205
+ this.render();
206
+ }
207
+ handleDragEnd() {
208
+ this.draggedEvent = null;
209
+ this.dragOverDate = null;
210
+ this.render();
211
+ }
212
+ handleEventClick(event, e) {
213
+ e.stopPropagation();
214
+ this.selectedEvent = event;
215
+ this.isEditing = true;
216
+ const eventDate = parseISODate(event.date);
217
+ if (eventDate)
218
+ this.selectedDate = eventDate;
219
+ this.eventForm = {
220
+ title: event.title,
221
+ description: event.description || '',
222
+ startTime: event.startTime || '09:00',
223
+ endTime: event.endTime || '10:00',
224
+ type: event.type,
225
+ color: event.color || '#17a2b8',
226
+ isAllDay: event.isAllDay || false,
227
+ location: event.location || '',
228
+ };
229
+ this.emit('db-event-click', { event });
230
+ this.showEventModal = true;
231
+ this.render();
232
+ }
233
+ openAddModal(date) {
234
+ this.selectedDate = date;
235
+ this.selectedEvent = null;
236
+ this.isEditing = false;
237
+ this.eventForm = {
238
+ title: '',
239
+ description: '',
240
+ startTime: '09:00',
241
+ endTime: '10:00',
242
+ type: 'meeting',
243
+ color: '#17a2b8',
244
+ isAllDay: false,
245
+ location: '',
246
+ };
247
+ this.showEventModal = true;
248
+ this.render();
249
+ }
250
+ closeModal() {
251
+ this.showEventModal = false;
252
+ this.selectedEvent = null;
253
+ this.isEditing = false;
254
+ this.render();
255
+ }
256
+ saveEvent() {
257
+ if (!this.eventForm.title.trim()) {
258
+ window.alert('제목을 입력해주세요.');
259
+ return;
260
+ }
261
+ const payload = {
262
+ title: this.eventForm.title.trim(),
263
+ description: this.eventForm.description,
264
+ date: toISODate(this.selectedDate),
265
+ startTime: this.eventForm.startTime,
266
+ endTime: this.eventForm.endTime,
267
+ type: this.eventForm.type,
268
+ color: this.eventForm.color,
269
+ isAllDay: this.eventForm.isAllDay,
270
+ location: this.eventForm.location,
271
+ };
272
+ if (this.isEditing && this.selectedEvent) {
273
+ const updated = { ...this.selectedEvent, ...payload };
274
+ this.emit('db-event-edit', { event: updated });
275
+ }
276
+ else {
277
+ this.emit('db-event-add', { event: payload });
278
+ }
279
+ this.closeModal();
280
+ }
281
+ deleteEvent() {
282
+ if (!this.selectedEvent)
283
+ return;
284
+ if (!window.confirm('정말로 이 일정을 삭제하시겠습니까?'))
285
+ return;
286
+ this.emit('db-event-delete', { eventId: this.selectedEvent.id });
287
+ this.closeModal();
288
+ }
289
+ renderHeader() {
290
+ if (!this.headerEl)
291
+ return;
292
+ this.headerEl.replaceChildren();
293
+ const nav = document.createElement('div');
294
+ nav.className = `${CLASS}__navigation`;
295
+ const iconSize = getDbComponentIconSize('m');
296
+ const prev = document.createElement('button');
297
+ prev.type = 'button';
298
+ prev.className = `${CLASS}__nav-button`;
299
+ prev.setAttribute('aria-label', '이전');
300
+ prev.appendChild(createDbIcon('chevron-left', iconSize));
301
+ prev.addEventListener('click', () => {
302
+ this.shiftMonth(-1);
303
+ });
304
+ const title = document.createElement('div');
305
+ title.className = `${CLASS}__title`;
306
+ title.textContent = this.selectedDate.toLocaleDateString(this.locale, {
307
+ year: 'numeric',
308
+ month: 'long',
309
+ });
310
+ const next = document.createElement('button');
311
+ next.type = 'button';
312
+ next.className = `${CLASS}__nav-button`;
313
+ next.setAttribute('aria-label', '다음');
314
+ next.appendChild(createDbIcon('chevron-right', iconSize));
315
+ next.addEventListener('click', () => {
316
+ this.shiftMonth(1);
317
+ });
318
+ nav.append(prev, title, next);
319
+ this.headerEl.appendChild(nav);
320
+ }
321
+ renderMonthView() {
322
+ const wrap = document.createElement('div');
323
+ wrap.className = `${CLASS}__month-view`;
324
+ const weekdays = document.createElement('div');
325
+ weekdays.className = `${CLASS}__weekdays`;
326
+ ['일', '월', '화', '수', '목', '금', '토'].forEach((day) => {
327
+ const cell = document.createElement('div');
328
+ cell.className = `${CLASS}__weekday`;
329
+ cell.textContent = day;
330
+ weekdays.appendChild(cell);
331
+ });
332
+ wrap.appendChild(weekdays);
333
+ const grid = document.createElement('div');
334
+ grid.className = `${CLASS}__days`;
335
+ const year = this.selectedDate.getFullYear();
336
+ const month = this.selectedDate.getMonth();
337
+ const firstDay = new Date(year, month, 1);
338
+ const startDate = new Date(firstDay);
339
+ startDate.setDate(startDate.getDate() - firstDay.getDay());
340
+ const today = new Date();
341
+ for (let i = 0; i < 42; i += 1) {
342
+ const date = new Date(startDate);
343
+ date.setDate(startDate.getDate() + i);
344
+ const isCurrentMonth = date.getMonth() === month;
345
+ if (!this.showOutsideDays && !isCurrentMonth)
346
+ continue;
347
+ const isWeekend = date.getDay() === 0 || date.getDay() === 6;
348
+ if (!this.showWeekends && isWeekend)
349
+ continue;
350
+ const dayEl = document.createElement('div');
351
+ const isInRange = this.selectedDateRange.start &&
352
+ this.selectedDateRange.end &&
353
+ date >= this.selectedDateRange.start &&
354
+ date <= this.selectedDateRange.end;
355
+ const isRangeStart = this.selectedDateRange.start &&
356
+ date.toDateString() === this.selectedDateRange.start.toDateString();
357
+ const isRangeEnd = this.selectedDateRange.end &&
358
+ date.toDateString() === this.selectedDateRange.end.toDateString();
359
+ const isRangeMiddle = Boolean(isInRange && !isRangeStart && !isRangeEnd);
360
+ dayEl.className = [
361
+ `${CLASS}__day`,
362
+ isCurrentMonth && `${CLASS}__day--current-month`,
363
+ this.highlightToday && date.toDateString() === today.toDateString() && `${CLASS}__day--today`,
364
+ this.isSameDate(date, this.selectedDate) && `${CLASS}__day--selected`,
365
+ isRangeStart && `${CLASS}__day--selected--start`,
366
+ isRangeEnd && `${CLASS}__day--selected--end`,
367
+ isRangeMiddle && `${CLASS}__day--selected--middle`,
368
+ !this.showWeekends && isWeekend && `${CLASS}__day--weekend`,
369
+ ]
370
+ .filter(Boolean)
371
+ .join(' ');
372
+ dayEl.addEventListener('click', () => this.handleDateClick(date));
373
+ const num = document.createElement('span');
374
+ num.className = `${CLASS}__day-number`;
375
+ num.textContent = String(date.getDate());
376
+ dayEl.appendChild(num);
377
+ const dayEvents = this.eventsForDate(date);
378
+ if (dayEvents.length) {
379
+ const eventsWrap = document.createElement('div');
380
+ eventsWrap.className = `${CLASS}__day-events`;
381
+ dayEvents.slice(0, 2).forEach((event) => {
382
+ const bar = document.createElement('div');
383
+ bar.className = [`${CLASS}__event-bar`, `${CLASS}__event-bar--${event.type}`]
384
+ .filter(Boolean)
385
+ .join(' ');
386
+ if (event.color)
387
+ bar.style.backgroundColor = event.color;
388
+ if (this.draggedEvent?.id === event.id)
389
+ bar.classList.add(`${CLASS}__event-bar--dragging`);
390
+ bar.title = event.title;
391
+ bar.draggable = true;
392
+ const barTitle = document.createElement('span');
393
+ barTitle.className = `${CLASS}__event-title`;
394
+ barTitle.textContent = event.title;
395
+ bar.appendChild(barTitle);
396
+ bar.addEventListener('click', (e) => this.handleEventClick(event, e));
397
+ bar.addEventListener('dragstart', (e) => this.handleDragStart(event, e));
398
+ bar.addEventListener('dragend', () => this.handleDragEnd());
399
+ eventsWrap.appendChild(bar);
400
+ });
401
+ if (dayEvents.length > 2) {
402
+ const more = document.createElement('div');
403
+ more.className = `${CLASS}__more-events`;
404
+ more.textContent = `+${dayEvents.length - 2} more`;
405
+ eventsWrap.appendChild(more);
406
+ }
407
+ dayEl.appendChild(eventsWrap);
408
+ }
409
+ const dropZone = document.createElement('div');
410
+ dropZone.className = [
411
+ `${CLASS}__drop-zone`,
412
+ this.dragOverDate && this.isSameDate(this.dragOverDate, date) && `${CLASS}__drop-zone--active`,
413
+ ]
414
+ .filter(Boolean)
415
+ .join(' ');
416
+ dropZone.addEventListener('dragover', (e) => this.handleDragOver(date, e));
417
+ dropZone.addEventListener('dragleave', (e) => this.handleDragLeave(e));
418
+ dropZone.addEventListener('drop', (e) => this.handleDrop(date, e));
419
+ dayEl.appendChild(dropZone);
420
+ grid.appendChild(dayEl);
421
+ }
422
+ wrap.appendChild(grid);
423
+ return wrap;
424
+ }
425
+ renderYearView() {
426
+ const wrap = document.createElement('div');
427
+ wrap.className = `${CLASS}__year-view`;
428
+ const year = this.selectedDate.getFullYear();
429
+ for (let month = 0; month < 12; month += 1) {
430
+ const monthDate = new Date(year, month, 1);
431
+ const monthEvents = this._events.filter((event) => {
432
+ const d = parseISODate(event.date);
433
+ return d && d.getFullYear() === year && d.getMonth() === month;
434
+ });
435
+ const cell = document.createElement('div');
436
+ cell.className = `${CLASS}__year-month`;
437
+ cell.addEventListener('click', () => {
438
+ this.selectedDate = monthDate;
439
+ this.handleViewChange('month');
440
+ });
441
+ const label = document.createElement('h3');
442
+ label.className = `${CLASS}__year-month-title`;
443
+ label.textContent = monthDate.toLocaleDateString(this.locale, { month: 'long' });
444
+ cell.appendChild(label);
445
+ if (monthEvents.length) {
446
+ const dots = document.createElement('div');
447
+ dots.className = `${CLASS}__year-month-events`;
448
+ monthEvents.slice(0, 4).forEach((event) => {
449
+ const dot = document.createElement('span');
450
+ dot.className = [`${CLASS}__year-event`, `${CLASS}__year-event--${event.type}`].join(' ');
451
+ if (event.color)
452
+ dot.style.backgroundColor = event.color;
453
+ dots.appendChild(dot);
454
+ });
455
+ if (monthEvents.length > 4) {
456
+ const more = document.createElement('span');
457
+ more.className = `${CLASS}__year-more-events`;
458
+ more.textContent = `+${monthEvents.length - 4} more`;
459
+ dots.appendChild(more);
460
+ }
461
+ cell.appendChild(dots);
462
+ }
463
+ wrap.appendChild(cell);
464
+ }
465
+ return wrap;
466
+ }
467
+ renderWeekView() {
468
+ const wrap = document.createElement('div');
469
+ wrap.className = `${CLASS}__week-view`;
470
+ const today = new Date();
471
+ const startOfWeek = new Date(this.selectedDate);
472
+ startOfWeek.setDate(this.selectedDate.getDate() - this.selectedDate.getDay());
473
+ const header = document.createElement('div');
474
+ header.className = `${CLASS}__week-header`;
475
+ const timeSpacer = document.createElement('div');
476
+ timeSpacer.className = `${CLASS}__time-column-header`;
477
+ header.appendChild(timeSpacer);
478
+ for (let i = 0; i < 7; i += 1) {
479
+ const date = new Date(startOfWeek);
480
+ date.setDate(startOfWeek.getDate() + i);
481
+ const dayHeader = document.createElement('div');
482
+ dayHeader.className = [
483
+ `${CLASS}__week-day-header`,
484
+ date.toDateString() === today.toDateString() && `${CLASS}__week-day-header--today`,
485
+ ]
486
+ .filter(Boolean)
487
+ .join(' ');
488
+ const name = document.createElement('span');
489
+ name.className = `${CLASS}__week-day-name`;
490
+ name.textContent = date.toLocaleDateString(this.locale, { weekday: 'short' });
491
+ const number = document.createElement('span');
492
+ number.className = `${CLASS}__week-day-number`;
493
+ number.textContent = String(date.getDate());
494
+ dayHeader.append(name, number);
495
+ header.appendChild(dayHeader);
496
+ }
497
+ wrap.appendChild(header);
498
+ const grid = document.createElement('div');
499
+ grid.className = `${CLASS}__week-grid`;
500
+ const timeCol = document.createElement('div');
501
+ timeCol.className = `${CLASS}__week-time-column`;
502
+ for (let hour = 6; hour <= 22; hour += 1) {
503
+ const slot = document.createElement('div');
504
+ slot.className = `${CLASS}__time-slot`;
505
+ const label = document.createElement('span');
506
+ label.className = `${CLASS}__time-label`;
507
+ label.textContent = `${String(hour).padStart(2, '0')}:00`;
508
+ slot.appendChild(label);
509
+ timeCol.appendChild(slot);
510
+ }
511
+ grid.appendChild(timeCol);
512
+ const daysCol = document.createElement('div');
513
+ daysCol.className = `${CLASS}__week-columns`;
514
+ for (let i = 0; i < 7; i += 1) {
515
+ const date = new Date(startOfWeek);
516
+ date.setDate(startOfWeek.getDate() + i);
517
+ const dayCol = document.createElement('div');
518
+ dayCol.className = `${CLASS}__week-column`;
519
+ for (let hour = 6; hour <= 22; hour += 1) {
520
+ const cell = document.createElement('div');
521
+ cell.className = `${CLASS}__week-cell`;
522
+ cell.title = `${date.toLocaleDateString()} ${String(hour).padStart(2, '0')}:00`;
523
+ cell.addEventListener('click', () => {
524
+ this.selectedDate = new Date(date);
525
+ this.eventForm.startTime = `${String(hour).padStart(2, '0')}:00`;
526
+ this.eventForm.endTime = `${String(hour + 1).padStart(2, '0')}:00`;
527
+ this.openAddModal(this.selectedDate);
528
+ });
529
+ dayCol.appendChild(cell);
530
+ }
531
+ this.eventsForDate(date).forEach((event) => {
532
+ if (!event.startTime)
533
+ return;
534
+ const [h, m] = event.startTime.split(':').map(Number);
535
+ const top = ((h - 6) * 60 + (m || 0)) * (48 / 60);
536
+ const endParts = (event.endTime || event.startTime).split(':').map(Number);
537
+ const endMinutes = (endParts[0] || h) * 60 + (endParts[1] || 0);
538
+ const startMinutes = h * 60 + (m || 0);
539
+ const height = Math.max(20, ((endMinutes - startMinutes) * 48) / 60);
540
+ const eventEl = document.createElement('div');
541
+ eventEl.className = [`${CLASS}__week-event`, `${CLASS}__week-event--${event.type}`]
542
+ .filter(Boolean)
543
+ .join(' ');
544
+ eventEl.textContent = event.title;
545
+ eventEl.title = event.title;
546
+ eventEl.style.backgroundColor = event.color || '#17a2b8';
547
+ eventEl.style.top = `${top}px`;
548
+ eventEl.style.height = `${height}px`;
549
+ eventEl.addEventListener('click', (e) => this.handleEventClick(event, e));
550
+ dayCol.appendChild(eventEl);
551
+ });
552
+ daysCol.appendChild(dayCol);
553
+ }
554
+ grid.appendChild(daysCol);
555
+ wrap.appendChild(grid);
556
+ return wrap;
557
+ }
558
+ renderModal() {
559
+ if (!this.modalEl)
560
+ return;
561
+ this.modalEl.replaceChildren();
562
+ if (!this.showEventModal) {
563
+ this.modalEl.removeAttribute('open');
564
+ return;
565
+ }
566
+ this.modalEl.setAttribute('open', 'true');
567
+ this.modalEl.setAttribute('title', this.isEditing ? '일정 편집' : '새 일정 추가');
568
+ const form = document.createElement('div');
569
+ form.className = `${CLASS}__event-form`;
570
+ const titleInput = document.createElement('db-input');
571
+ titleInput.setAttribute('label', '제목 *');
572
+ titleInput.setAttribute('placeholder', '일정 제목을 입력하세요');
573
+ titleInput.setAttribute('full-width', '');
574
+ titleInput.setAttribute('value', this.eventForm.title);
575
+ titleInput.addEventListener('db-input', ((e) => {
576
+ this.eventForm.title = e.detail.value;
577
+ }));
578
+ const descInput = document.createElement('db-input');
579
+ descInput.setAttribute('label', '설명');
580
+ descInput.setAttribute('placeholder', '일정 설명을 입력하세요');
581
+ descInput.setAttribute('full-width', '');
582
+ descInput.setAttribute('value', this.eventForm.description);
583
+ descInput.addEventListener('db-input', ((e) => {
584
+ this.eventForm.description = e.detail.value;
585
+ }));
586
+ const datePicker = document.createElement('db-date-picker');
587
+ datePicker.setAttribute('label', '날짜');
588
+ datePicker.setAttribute('full-width', '');
589
+ datePicker.setAttribute('value', toISODate(this.selectedDate));
590
+ datePicker.addEventListener('db-change', ((e) => {
591
+ const parsed = parseISODate(e.detail.value);
592
+ if (parsed)
593
+ this.selectedDate = parsed;
594
+ }));
595
+ const typeSelect = document.createElement('db-select');
596
+ typeSelect.setAttribute('label', '일정 타입');
597
+ typeSelect.setAttribute('full-width', '');
598
+ typeSelect.setAttribute('options', JSON.stringify(EVENT_TYPE_OPTIONS));
599
+ typeSelect.setAttribute('value', this.eventForm.type);
600
+ typeSelect.addEventListener('db-change', ((e) => {
601
+ this.eventForm.type = e.detail.value;
602
+ }));
603
+ const colorSelect = document.createElement('db-select');
604
+ colorSelect.setAttribute('label', '색상');
605
+ colorSelect.setAttribute('full-width', '');
606
+ colorSelect.setAttribute('options', JSON.stringify(COLOR_OPTIONS));
607
+ colorSelect.setAttribute('value', this.eventForm.color);
608
+ colorSelect.addEventListener('db-change', ((e) => {
609
+ this.eventForm.color = e.detail.value;
610
+ }));
611
+ const startPicker = document.createElement('db-time-picker');
612
+ startPicker.setAttribute('label', '시작 시간');
613
+ startPicker.setAttribute('full-width', '');
614
+ startPicker.setAttribute('value', this.eventForm.startTime);
615
+ startPicker.addEventListener('db-change', ((e) => {
616
+ this.eventForm.startTime = e.detail.value;
617
+ }));
618
+ const endPicker = document.createElement('db-time-picker');
619
+ endPicker.setAttribute('label', '종료 시간');
620
+ endPicker.setAttribute('full-width', '');
621
+ endPicker.setAttribute('value', this.eventForm.endTime);
622
+ endPicker.addEventListener('db-change', ((e) => {
623
+ this.eventForm.endTime = e.detail.value;
624
+ }));
625
+ form.append(titleInput, descInput, datePicker, typeSelect, colorSelect, startPicker, endPicker);
626
+ const body = document.createElement('div');
627
+ body.className = 'designbase-wc-modal__body';
628
+ body.setAttribute('data-modal-part', 'body');
629
+ body.appendChild(form);
630
+ const footer = document.createElement('div');
631
+ footer.className = 'designbase-wc-modal__footer';
632
+ footer.setAttribute('data-modal-part', 'footer');
633
+ const actions = document.createElement('div');
634
+ actions.className = `${CLASS}__modal-actions`;
635
+ if (this.isEditing) {
636
+ const deleteBtn = document.createElement('db-button');
637
+ deleteBtn.setAttribute('variant', 'danger');
638
+ deleteBtn.textContent = '삭제';
639
+ deleteBtn.addEventListener('click', () => this.deleteEvent());
640
+ actions.appendChild(deleteBtn);
641
+ }
642
+ const buttons = document.createElement('div');
643
+ buttons.className = `${CLASS}__modal-buttons`;
644
+ const cancelBtn = document.createElement('db-button');
645
+ cancelBtn.setAttribute('variant', 'secondary');
646
+ cancelBtn.textContent = '취소';
647
+ cancelBtn.addEventListener('click', () => this.closeModal());
648
+ const saveBtn = document.createElement('db-button');
649
+ saveBtn.setAttribute('variant', 'primary');
650
+ saveBtn.textContent = this.isEditing ? '수정' : '저장';
651
+ saveBtn.addEventListener('click', () => this.saveEvent());
652
+ buttons.append(cancelBtn, saveBtn);
653
+ actions.appendChild(buttons);
654
+ footer.appendChild(actions);
655
+ this.modalEl.append(body, footer);
656
+ }
657
+ render() {
658
+ this.className = CLASS;
659
+ this.replaceChildren();
660
+ if (!this.headerEl || !this.bodyEl)
661
+ return;
662
+ this.renderHeader();
663
+ this.bodyEl.replaceChildren();
664
+ this.bodyEl.appendChild(this.renderMonthView());
665
+ this.append(this.headerEl, this.bodyEl);
666
+ this.renderModal();
667
+ if (this.showEventModal && this.modalEl)
668
+ this.appendChild(this.modalEl);
669
+ }
670
+ }
671
+ if (!customElements.get('db-calendar')) {
672
+ customElements.define('db-calendar', DbCalendar);
673
+ }
674
+
675
+ export { DbCalendar };