@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.
- package/dist/chunks/lottie-e273b936.js +17032 -0
- package/dist/esm/components/animation-background/canvas-controller.js +121 -1
- package/dist/esm/components/animation-background/controls.js +180 -1
- package/dist/esm/components/animation-background/graphite-controller.js +196 -1
- package/dist/esm/components/animation-background/graphite-presets.js +42 -1
- package/dist/esm/components/animation-background/graphite-shader.js +111 -1
- package/dist/esm/components/animation-background/layers.js +369 -1
- package/dist/esm/components/animation-background/lumina-presets.js +53 -1
- package/dist/esm/components/animation-background/lumina-shaders.js +181 -1
- package/dist/esm/components/animation-background/lumina-utils.js +60 -1
- package/dist/esm/components/animation-background/particle-defaults.js +36 -1
- package/dist/esm/components/animation-background/renderers.js +167 -1
- package/dist/esm/components/animation-background/shader-controller.js +154 -1
- package/dist/esm/components/color-picker-utils.js +134 -1
- package/dist/esm/components/date-picker-utils.js +140 -1
- package/dist/esm/components/db-accordion.js +304 -1
- package/dist/esm/components/db-ad-banner.js +455 -1
- package/dist/esm/components/db-alert.js +149 -1
- package/dist/esm/components/db-animation-background.js +486 -1
- package/dist/esm/components/db-animation-text.js +601 -1
- package/dist/esm/components/db-app-badge.js +88 -1
- package/dist/esm/components/db-audio-player.js +752 -1
- package/dist/esm/components/db-avatar.js +188 -1
- package/dist/esm/components/db-backdrop.js +139 -1
- package/dist/esm/components/db-badge.js +91 -1
- package/dist/esm/components/db-banner.js +188 -1
- package/dist/esm/components/db-bottom-navigation.js +146 -1
- package/dist/esm/components/db-bottom-sheet.js +481 -1
- package/dist/esm/components/db-breadcrumbs.js +329 -1
- package/dist/esm/components/db-button.js +316 -1
- package/dist/esm/components/db-calendar.js +675 -1
- package/dist/esm/components/db-card.js +265 -1
- package/dist/esm/components/db-carousel.js +264 -1
- package/dist/esm/components/db-checkbox.js +202 -1
- package/dist/esm/components/db-chip.js +104 -1
- package/dist/esm/components/db-code-block.js +304 -1
- package/dist/esm/components/db-color-picker.js +746 -1
- package/dist/esm/components/db-confirm.js +191 -1
- package/dist/esm/components/db-container.js +39 -1
- package/dist/esm/components/db-context-menu.js +265 -1
- package/dist/esm/components/db-countdown.js +146 -1
- package/dist/esm/components/db-cursor-follower.js +173 -1
- package/dist/esm/components/db-date-picker.js +837 -1
- package/dist/esm/components/db-divider.js +66 -1
- package/dist/esm/components/db-drawer.js +245 -1
- package/dist/esm/components/db-dropdown.js +493 -1
- package/dist/esm/components/db-dropzone.js +266 -1
- package/dist/esm/components/db-empty-state.js +97 -1
- package/dist/esm/components/db-file-uploader.js +256 -1
- package/dist/esm/components/db-floating-action-button.js +139 -1
- package/dist/esm/components/db-footer.js +385 -1
- package/dist/esm/components/db-form.js +566 -1
- package/dist/esm/components/db-gantt.js +882 -1
- package/dist/esm/components/db-gradient.js +133 -1
- package/dist/esm/components/db-grid-background.js +909 -1
- package/dist/esm/components/db-grid.js +73 -1
- package/dist/esm/components/db-icons.js +82 -1
- package/dist/esm/components/db-image-list.js +214 -1
- package/dist/esm/components/db-image-placeholder.js +96 -1
- package/dist/esm/components/db-image.js +289 -1
- package/dist/esm/components/db-indicator.js +517 -1
- package/dist/esm/components/db-input.js +420 -1
- package/dist/esm/components/db-kanban.js +538 -1
- package/dist/esm/components/db-label.js +66 -1
- package/dist/esm/components/db-lightbox.js +372 -1
- package/dist/esm/components/db-list.js +340 -1
- package/dist/esm/components/db-logo-svg.js +72 -1
- package/dist/esm/components/db-logo.js +106 -1
- package/dist/esm/components/db-lottie.js +109 -1
- package/dist/esm/components/db-markdown-editor.js +458 -1
- package/dist/esm/components/db-marquee.js +402 -1
- package/dist/esm/components/db-masonry.js +94 -1
- package/dist/esm/components/db-menu-item.js +211 -1
- package/dist/esm/components/db-modal.js +522 -1
- package/dist/esm/components/db-navbar.js +680 -1
- package/dist/esm/components/db-onboarding-modal.js +322 -1
- package/dist/esm/components/db-page-header.js +312 -1
- package/dist/esm/components/db-pagination.js +271 -1
- package/dist/esm/components/db-popover.js +580 -1
- package/dist/esm/components/db-progress-step.js +166 -1
- package/dist/esm/components/db-progress.js +171 -1
- package/dist/esm/components/db-progressbar.js +72 -1
- package/dist/esm/components/db-radio.js +172 -1
- package/dist/esm/components/db-random-gradient.js +121 -1
- package/dist/esm/components/db-range-slider.js +811 -1
- package/dist/esm/components/db-rating.js +325 -1
- package/dist/esm/components/db-reorder.js +286 -1
- package/dist/esm/components/db-resizable-panels.js +282 -1
- package/dist/esm/components/db-scroll-area.js +126 -1
- package/dist/esm/components/db-search-bar.js +668 -1
- package/dist/esm/components/db-section-app-download.js +101 -1
- package/dist/esm/components/db-section-contact.js +102 -1
- package/dist/esm/components/db-section-cta.js +150 -1
- package/dist/esm/components/db-section-faq.js +120 -1
- package/dist/esm/components/db-section-feature-grid.js +137 -1
- package/dist/esm/components/db-section-feature.js +179 -1
- package/dist/esm/components/db-section-hero.js +243 -1
- package/dist/esm/components/db-section-logo-cloud.js +138 -1
- package/dist/esm/components/db-section-pricing.js +166 -1
- package/dist/esm/components/db-section-stats.js +129 -1
- package/dist/esm/components/db-section-testimonials.js +252 -1
- package/dist/esm/components/db-section.js +385 -1
- package/dist/esm/components/db-segment-control.js +231 -1
- package/dist/esm/components/db-select.js +571 -1
- package/dist/esm/components/db-share-icons.js +40 -1
- package/dist/esm/components/db-share.js +489 -1
- package/dist/esm/components/db-sidebar.js +1068 -1
- package/dist/esm/components/db-skeleton.js +94 -1
- package/dist/esm/components/db-spinner.js +102 -1
- package/dist/esm/components/db-split-view.js +297 -1
- package/dist/esm/components/db-stack.js +46 -1
- package/dist/esm/components/db-stat.js +218 -1
- package/dist/esm/components/db-stepper.js +398 -1
- package/dist/esm/components/db-table.js +412 -1
- package/dist/esm/components/db-tabs.js +335 -1
- package/dist/esm/components/db-testimonial.js +162 -1
- package/dist/esm/components/db-textarea.js +233 -1
- package/dist/esm/components/db-time-picker.js +523 -1
- package/dist/esm/components/db-timeline.js +174 -1
- package/dist/esm/components/db-toast.js +158 -1
- package/dist/esm/components/db-toggle.js +162 -1
- package/dist/esm/components/db-toolbar.js +94 -1
- package/dist/esm/components/db-tooltip.js +440 -1
- package/dist/esm/components/db-top-banner.js +121 -1
- package/dist/esm/components/db-tutorial.js +324 -1
- package/dist/esm/components/db-video-player.js +503 -1
- package/dist/esm/components/db-youtube-player.js +194 -1
- package/dist/esm/components/highlight-code.js +65 -1
- package/dist/esm/components/input-validation.js +50 -1
- package/dist/esm/components/marketing-block-host-utils.js +9 -1
- package/dist/esm/components/number-scrub.js +117 -1
- package/dist/esm/components/random-gradient-palettes.js +67 -1
- package/dist/esm/components/section-block-header-utils.js +48 -1
- package/dist/esm/helpers/confirm.js +52 -1
- package/dist/esm/helpers/toast.js +73 -1
- package/dist/esm/index.js +111 -1
- package/dist/esm/node_modules/prismjs/components/prism-bash.js +235 -1
- package/dist/esm/node_modules/prismjs/components/prism-clike.js +31 -1
- package/dist/esm/node_modules/prismjs/components/prism-css.js +64 -1
- package/dist/esm/node_modules/prismjs/components/prism-javascript.js +172 -1
- package/dist/esm/node_modules/prismjs/components/prism-json.js +27 -1
- package/dist/esm/node_modules/prismjs/components/prism-jsx.js +143 -1
- package/dist/esm/node_modules/prismjs/components/prism-markup.js +186 -1
- package/dist/esm/node_modules/prismjs/components/prism-scss.js +81 -1
- package/dist/esm/node_modules/prismjs/components/prism-tsx.js +15 -1
- package/dist/esm/node_modules/prismjs/components/prism-typescript.js +60 -1
- package/dist/index.cjs +55414 -1
- package/dist/index.css +1 -1
- package/dist/index.esm.js +38284 -1
- package/dist/react/chunks/lottie-72a4c6d4.js +17032 -0
- package/dist/react/index.cjs +56503 -1
- package/dist/react/index.esm.js +39373 -1
- package/package.json +1 -1
- package/dist/chunks/lottie-2637aa0f.js +0 -1
- package/dist/react/chunks/lottie-14e7addb.js +0 -1
|
@@ -1 +1,882 @@
|
|
|
1
|
-
import{createDbIcon as e}from"./db-icons.js";const t=["day","week"],a=["#86efac","#fde047","#93c5fd","#c4b5fd","#f9a8d4","#5eead4","#fdba74","#a5b4fc"],s=["일","월","화","수","목","금","토"],r=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"];function parseDate(e){const[t,a,s]=e.split("-").map(Number);return new Date(t,a-1,s)}function toDateKey(e){return`${e.getFullYear()}-${String(e.getMonth()+1).padStart(2,"0")}-${String(e.getDate()).padStart(2,"0")}`}function addDays(e,t){const a=new Date(e);return a.setDate(a.getDate()+t),a}function daysBetween(e,t){const a=Date.UTC(e.getFullYear(),e.getMonth(),e.getDate()),s=Date.UTC(t.getFullYear(),t.getMonth(),t.getDate());return Math.round((s-a)/864e5)}function clampDate(e,t,a){return e<t?new Date(t):e>a?new Date(a):e}class DbGantt extends HTMLElement{constructor(){super(...arguments),this.collapsedGroups=new Set,this.dragState=null,this.reorderState=null,this.reorderDropIndex=null,this.suppressRender=!1,this.didDrag=!1,this.handlePointerMove=e=>{if(this.reorderState&&e.pointerId===this.reorderState.pointerId)return void this.updateReorderDropTarget(e.clientY);if(!this.dragState||e.pointerId!==this.dragState.pointerId)return;const a=this.getOption("scale",t,"day"),s=this.getNumber("cell-width","week"===a?72:44),{start:r,end:n}=this.getViewRange(),i=Math.round((e.clientX-this.dragState.startX)/s);0!==i&&(this.didDrag=!0);let d=new Date(this.dragState.originalStart),o=new Date(this.dragState.originalEnd);if("move"===this.dragState.mode?(d=addDays(this.dragState.originalStart,i),o=addDays(this.dragState.originalEnd,i)):"resize-start"===this.dragState.mode?(d=addDays(this.dragState.originalStart,i),d>o&&(d=new Date(o))):(o=addDays(this.dragState.originalEnd,i),o<d&&(o=new Date(d))),d=clampDate(d,r,n),o=clampDate(o,r,n),d>o)return;const l=this.items.find(e=>e.id===this.dragState.itemId);l&&this.applyBarGeometry(this.dragState.barEl,{...l,startDate:toDateKey(d),endDate:toDateKey(o)},r,s)},this.handlePointerUp=e=>{if(this.reorderState&&e.pointerId===this.reorderState.pointerId)return void this.finishReorderDrag(e);if(!this.dragState||e.pointerId!==this.dragState.pointerId)return;const a=this.getOption("scale",t,"day"),s=this.getNumber("cell-width","week"===a?72:44),{start:r,end:n}=this.getViewRange(),i=Math.round((e.clientX-this.dragState.startX)/s);let d=new Date(this.dragState.originalStart),o=new Date(this.dragState.originalEnd);const l=this.dragState.mode,c=this.dragState.itemId;"move"===l?(d=addDays(this.dragState.originalStart,i),o=addDays(this.dragState.originalEnd,i)):"resize-start"===l?(d=addDays(this.dragState.originalStart,i),d>o&&(d=new Date(o))):(o=addDays(this.dragState.originalEnd,i),o<d&&(o=new Date(d))),d=clampDate(d,r,n),o=clampDate(o,r,n),this.dragState.barEl.releasePointerCapture(e.pointerId),this.dragState.barEl.classList.remove("designbase-wc-gantt__bar--dragging"),this.dragState=null;const g=this.items.find(e=>e.id===c);if(!g)return;const p=toDateKey(d),h=toDateKey(o);p===g.startDate&&h===g.endDate||(this.updateItem(c,{startDate:p,endDate:h},"move"===l?"move":"resize"),this.render())}}static get observedAttributes(){return["groups","rows","items","start-date","end-date","today","scale","cell-width","show-weekends","show-today","sidebar-label","full-width","disabled","draggable","reorderable","locale"]}get groups(){return this.parseJson("groups",[])}set groups(e){this.setAttribute("groups","string"==typeof e?e:JSON.stringify(e))}get rows(){return this.parseJson("rows",[])}set rows(e){this.setAttribute("rows","string"==typeof e?e:JSON.stringify(e))}get items(){return this.parseJson("items",[])}set items(e){this.setAttribute("items","string"==typeof e?e:JSON.stringify(e))}connectedCallback(){this.setAttribute("role","region"),this.setAttribute("aria-label","프로젝트 타임라인"),this.addEventListener("pointermove",this.handlePointerMove),this.addEventListener("pointerup",this.handlePointerUp),this.addEventListener("pointercancel",this.handlePointerUp),this.render()}disconnectedCallback(){this.removeEventListener("pointermove",this.handlePointerMove),this.removeEventListener("pointerup",this.handlePointerUp),this.removeEventListener("pointercancel",this.handlePointerUp)}attributeChangedCallback(){!this.isConnected||this.suppressRender||this.dragState||this.reorderState||this.render()}parseJson(e,t){try{return JSON.parse(this.getAttribute(e)||"null")??t}catch{return t}}getBool(e,t=!1){if(!this.hasAttribute(e))return t;const a=this.getAttribute(e);return""===a||"true"===a}getNumber(e,t){const a=Number(this.getAttribute(e));return Number.isFinite(a)&&a>0?a:t}getOption(e,t,a){const s=this.getAttribute(e);return s&&t.includes(s)?s:a}getLocale(){return this.getAttribute("locale")||"ko-KR"}getWeekdays(){return this.getLocale().startsWith("ko")?s:r}getViewRange(){const e=this.getAttribute("start-date"),t=this.getAttribute("end-date");if(e&&t)return{start:parseDate(e),end:parseDate(t)};const a=this.items.flatMap(e=>[parseDate(e.startDate),parseDate(e.endDate)]);if(0===a.length){const e=new Date,t=new Date(e.getFullYear(),e.getMonth(),1);return{start:t,end:addDays(t,29)}}const s=new Date(Math.min(...a.map(e=>e.getTime()))),r=new Date(Math.max(...a.map(e=>e.getTime())));return{start:addDays(s,-2),end:addDays(r,2)}}getTodayDate(){const e=this.getAttribute("today");return e?parseDate(e):new Date}getDateColumns(e,t){const a=daysBetween(e,t)+1;return Array.from({length:a},(t,a)=>addDays(e,a))}getVisibleRows(){const e=this.groups,t=this.rows;if(0===e.length)return t.map(e=>({type:"row",row:e}));const a=[];for(const s of e)a.push({type:"group",group:s}),this.collapsedGroups.has(s.id)||t.filter(e=>e.groupId===s.id).forEach(e=>a.push({type:"row",row:e}));return t.filter(t=>!t.groupId||!e.some(e=>e.id===t.groupId)).forEach(e=>a.push({type:"row",row:e})),a}getMonthSegments(e){const t=[];let a="",s=0;for(const r of e){const e=`${r.getFullYear()}-${r.getMonth()}`;e!==a?(s>0&&t.push({label:a,span:s}),a=e,s=1):s+=1}return s>0&&t.push({label:a,span:s}),t.map(e=>{const[t,a]=e.label.split("-").map(Number);return{label:new Date(t,a,1).toLocaleDateString(this.getLocale(),{year:"numeric",month:"long"}),span:e.span}})}getInitials(e){return e.initials?e.initials:e.name.split(/\s+/).map(e=>e[0]).join("").slice(0,2).toUpperCase()}updateItem(e,t,a){const s=this.items.map(a=>a.id===e?{...a,...t}:a);this.emitItemsChange(s,a,e)}emitItemsChange(e,t,a){this.suppressRender=!0,this.items=e,this.suppressRender=!1;const s=e.find(e=>e.id===a);this.dispatchEvent(new CustomEvent("db-item-change",{detail:{item:s,items:e,action:t},bubbles:!0,composed:!0})),this.dispatchEvent(new CustomEvent("db-change",{detail:{groups:this.groups,rows:this.rows,items:e},bubbles:!0,composed:!0}))}applyBarGeometry(e,t,a,s){const r=parseDate(t.startDate),n=parseDate(t.endDate),i=daysBetween(a,r),d=daysBetween(r,n)+1;e.style.left=i*s+2+"px",e.style.width=`${Math.max(d*s-4,s-4)}px`}startBarDrag(e,t,a,s){!this.getBool("draggable",!0)||this.getBool("disabled")||t.disabled||(e.preventDefault(),e.stopPropagation(),this.didDrag=!1,s.setPointerCapture(e.pointerId),s.classList.add("designbase-wc-gantt__bar--dragging"),this.dragState={itemId:t.id,mode:a,pointerId:e.pointerId,startX:e.clientX,originalStart:parseDate(t.startDate),originalEnd:parseDate(t.endDate),barEl:s})}canReorder(){return this.getBool("reorderable",!0)&&!this.getBool("disabled")}createDragHandle(t,a,s){const r=document.createElement("span");return r.className="designbase-wc-gantt__drag-handle",r.setAttribute("role","button"),r.setAttribute("aria-label","group"===t?"그룹 순서 변경":"행 순서 변경"),r.append(e("grip-vertical",12)),this.canReorder()?(r.addEventListener("pointerdown",e=>{e.preventDefault(),e.stopPropagation(),this.startReorderDrag(e,t,a,s,r)}),r):r}getGroupIndex(e){return this.groups.findIndex(t=>t.id===e)}getRowScope(e){return this.rows.filter(t=>(t.groupId||"")===(e||""))}getRowIndexInScope(e,t){return this.getRowScope(t).findIndex(t=>t.id===e)}getReorderTargets(e,t){return Array.from(this.querySelectorAll("group"===e?".designbase-wc-gantt__label-cell--group":`.designbase-wc-gantt__label-cell--row[data-group-id="${t||""}"]`))}startReorderDrag(e,t,a,s,r){const n="group"===t?this.getGroupIndex(a):this.getRowIndexInScope(a,s);n<0||(r.setPointerCapture(e.pointerId),this.reorderState={kind:t,id:a,groupId:s,pointerId:e.pointerId,startY:e.clientY,fromIndex:n,captureEl:r},this.classList.add("designbase-wc-gantt--reordering"),this.updateReorderDropTarget(e.clientY))}updateReorderDropTarget(e){if(!this.reorderState)return;const t=this.getReorderTargets(this.reorderState.kind,this.reorderState.groupId);let a=null;t.forEach((t,s)=>{const r=t.getBoundingClientRect(),n=r.top+r.height/2,i=e>=r.top&&e<=r.bottom,d=e>n;t.classList.toggle("designbase-wc-gantt__label-cell--drag-over",i),t.classList.toggle("designbase-wc-gantt__label-cell--drag-over-before",i&&!d),t.classList.toggle("designbase-wc-gantt__label-cell--drag-over-after",i&&d),i&&(a=d?s+1:s)}),this.reorderDropIndex=a}clearReorderIndicators(){this.querySelectorAll(".designbase-wc-gantt__label-cell--drag-over").forEach(e=>{e.classList.remove("designbase-wc-gantt__label-cell--drag-over","designbase-wc-gantt__label-cell--drag-over-before","designbase-wc-gantt__label-cell--drag-over-after")})}finishReorderDrag(e){if(!this.reorderState)return;const{kind:t,id:a,groupId:s,fromIndex:r,captureEl:n}=this.reorderState;try{n.releasePointerCapture(this.reorderState.pointerId)}catch{}const i=this.reorderDropIndex;if(this.reorderState=null,this.reorderDropIndex=null,this.classList.remove("designbase-wc-gantt--reordering"),this.clearReorderIndicators(),null===i)return;const d=i>r?i-1:i;d<0||d===r||("group"===t?this.reorderGroups(r,d,a):this.reorderRows(r,d,a,s))}emitStructureChange(e,t,a,s){this.suppressRender=!0,this.groups=e,this.rows=t,this.suppressRender=!1,this.dispatchEvent(new CustomEvent(s,{detail:{...a,groups:e,rows:t},bubbles:!0,composed:!0})),this.dispatchEvent(new CustomEvent("db-change",{detail:{groups:e,rows:t,items:this.items},bubbles:!0,composed:!0})),this.render()}reorderGroups(e,t,a){const s=[...this.groups],[r]=s.splice(e,1);r&&(s.splice(t,0,r),this.emitStructureChange(s,this.rows,{groupId:a,fromIndex:e,toIndex:t},"db-group-reorder"))}reorderRows(e,t,a,s){const r=this.getRowScope(s),n=[...r],[i]=n.splice(e,1);if(!i)return;n.splice(t,0,i);const d=new Set(r.map(e=>e.id)),o=[];let l=!1;for(const e of this.rows)d.has(e.id)?l||(o.push(...n),l=!0):o.push(e);l||o.push(...n),this.emitStructureChange(this.groups,o,{rowId:a,groupId:s,fromIndex:e,toIndex:t},"db-row-reorder")}createGroupBadge(e){const t=document.createElement("db-badge");return t.className="designbase-wc-gantt__group-badge",t.setAttribute("badge-style","dot"),t.setAttribute("size","s"),t.setAttribute("variant","primary"),t.setAttribute("aria-hidden","true"),e&&t.style.setProperty("--db-gantt-badge-color",e),t}createGroupChevron(t){const a=document.createElement("span");return a.className=["designbase-wc-gantt__group-chevron",t?"designbase-wc-gantt__group-chevron--collapsed":""].filter(Boolean).join(" "),a.append(e("chevron-down",12)),a}createAssignee(e){const t=document.createElement("span");if(t.className="designbase-wc-gantt__assignee",t.title=e.name,e.avatar){const a=document.createElement("img");a.src=e.avatar,a.alt=e.name,t.appendChild(a)}else t.textContent=this.getInitials(e);return t}createStatusIcon(e){const t=document.createElement("span");return t.className=`designbase-wc-gantt__status designbase-wc-gantt__status--${e}`,t.setAttribute("aria-hidden","true"),t.textContent="completed"===e?"✓":"!",t}deriveSummaryColor(e,t){if(0===e.length)return t||a[0];const s=e.filter(e=>e.color);if(0===s.length)return t||a[0];return s.reduce((e,t)=>{const a=daysBetween(parseDate(e.startDate),parseDate(e.endDate));return daysBetween(parseDate(t.startDate),parseDate(t.endDate))>a?t:e}).color||t||a[0]}getGroupSummary(e,t){const a=this.rows.filter(t=>t.groupId===e),s=this.items.filter(e=>a.some(t=>t.id===e.rowId));if(0===s.length)return null;const r=s.map(e=>parseDate(e.startDate).getTime()),n=s.map(e=>parseDate(e.endDate).getTime()),i=s.map(e=>e.progress).filter(e=>"number"==typeof e);return{startDate:toDateKey(new Date(Math.min(...r))),endDate:toDateKey(new Date(Math.max(...n))),color:this.deriveSummaryColor(s,t),progress:i.length>0?i.reduce((e,t)=>e+t,0)/i.length:0,count:s.length}}applyBarTone(e,t){const a=function deriveBarTone(e){return{base:`color-mix(in srgb, ${e} 42%, white)`,progress:`color-mix(in srgb, ${e} 58%, white)`}}(t);e.style.setProperty("--db-gantt-bar-color",t),e.style.setProperty("--db-gantt-bar-bg",a.base),e.style.setProperty("--db-gantt-bar-progress-bg",a.progress)}createGroupSummaryBar(e,t,a,s){const r=clampDate(parseDate(e.startDate),t,a),n=clampDate(parseDate(e.endDate),t,a);if(n<t||r>a)return null;const i=daysBetween(t,r),d=daysBetween(r,n)+1,o=Math.min(100,Math.max(0,e.progress)),l=document.createElement("div");if(l.className="designbase-wc-gantt__group-summary",l.setAttribute("aria-hidden","true"),l.title=`${e.count}개 태스크`,this.applyBarTone(l,e.color),l.style.left=i*s+2+"px",l.style.width=`${Math.max(d*s-4,s-4)}px`,o>0){const e=document.createElement("span");e.className="designbase-wc-gantt__group-summary-progress",e.style.width=`${o}%`,l.appendChild(e)}return l}createBar(e,t,s,r){const n=e.color||a[r%a.length],i=document.createElement("div");i.className="designbase-wc-gantt__bar",i.setAttribute("role","button"),i.setAttribute("tabindex",e.disabled?"-1":"0"),i.setAttribute("aria-label",`${e.title}, ${e.startDate} – ${e.endDate}`),this.applyBarTone(i,n),e.disabled&&i.classList.add("designbase-wc-gantt__bar--disabled"),this.applyBarGeometry(i,e,t,s);const d="number"==typeof e.progress?Math.min(100,Math.max(0,e.progress)):0;if(d>0){const e=document.createElement("span");e.className="designbase-wc-gantt__bar-progress",e.style.width=`${d}%`,i.appendChild(e)}const o=document.createElement("div");o.className="designbase-wc-gantt__bar-body","delayed"!==e.status&&"at-risk"!==e.status||o.appendChild(this.createStatusIcon(e.status));const l=document.createElement("span");l.className="designbase-wc-gantt__bar-label",l.textContent=e.title,o.appendChild(l),e.assignee&&o.appendChild(this.createAssignee(e.assignee));const c=document.createElement("span");c.className="designbase-wc-gantt__bar-handle designbase-wc-gantt__bar-handle--start",c.setAttribute("aria-hidden","true");const g=document.createElement("span");return g.className="designbase-wc-gantt__bar-handle designbase-wc-gantt__bar-handle--end",g.setAttribute("aria-hidden","true"),i.append(c,o,g),e.disabled||!this.getBool("draggable",!0)||this.getBool("disabled")||(c.addEventListener("pointerdown",t=>this.startBarDrag(t,e,"resize-start",i)),g.addEventListener("pointerdown",t=>this.startBarDrag(t,e,"resize-end",i)),o.addEventListener("pointerdown",t=>{t.target.closest(".designbase-wc-gantt__bar-handle")||this.startBarDrag(t,e,"move",i)})),i.addEventListener("click",t=>{this.dragState||this.didDrag?this.didDrag=!1:(t.stopPropagation(),this.dispatchEvent(new CustomEvent("db-item-click",{detail:{item:e},bubbles:!0,composed:!0})))}),i}toggleGroup(e){this.collapsedGroups.has(e)?this.collapsedGroups.delete(e):this.collapsedGroups.add(e),this.dispatchEvent(new CustomEvent("db-group-toggle",{detail:{groupId:e,collapsed:this.collapsedGroups.has(e)},bubbles:!0,composed:!0})),this.render()}render(){const e=this.items,s=this.getOption("scale",t,"day"),r=this.getNumber("cell-width","week"===s?72:44),n=this.getBool("show-weekends",!0),i=this.getBool("show-today",!0),d=this.getAttribute("sidebar-label")||"할 일",o=this.getBool("disabled"),l=this.getBool("full-width",!0),c=this.getWeekdays(),{start:g,end:p}=this.getViewRange(),h=this.getDateColumns(g,p),u=toDateKey(this.getTodayDate()),m=h.findIndex(e=>toDateKey(e)===u),b=this.getVisibleRows(),w=h.length*r,D=document.createElement("div");D.className=["designbase-wc-gantt",l?"designbase-wc-gantt--full-width":"",o?"designbase-wc-gantt--disabled":"",this.getBool("draggable",!0)?"designbase-wc-gantt--draggable":""].filter(Boolean).join(" ");const y=document.createElement("div");y.className="designbase-wc-gantt__viewport";const _=document.createElement("div");_.className="designbase-wc-gantt__grid",_.style.setProperty("--db-gantt-chart-width",`${w}px`),_.style.setProperty("--db-gantt-cell-width",`${r}px`);const f=document.createElement("div");f.className="designbase-wc-gantt__corner",f.textContent=d,_.appendChild(f);const v=document.createElement("div");v.className="designbase-wc-gantt__date-header";const E=document.createElement("div");E.className="designbase-wc-gantt__month-row";for(const e of this.getMonthSegments(h)){const t=document.createElement("div");t.className="designbase-wc-gantt__month-cell",t.style.width=e.span*r+"px",t.textContent=e.label,E.appendChild(t)}v.appendChild(E);const S=document.createElement("div");S.className="designbase-wc-gantt__day-row";for(const e of h){const t=toDateKey(e)===u,a=0===e.getDay()||6===e.getDay(),s=document.createElement("div");s.className=["designbase-wc-gantt__day-cell",t&&i?"designbase-wc-gantt__day-cell--today":"",a&&n?"designbase-wc-gantt__day-cell--weekend":""].filter(Boolean).join(" "),s.style.width=`${r}px`;const d=document.createElement("span");d.className="designbase-wc-gantt__day-num",d.textContent=String(e.getDate());const o=document.createElement("span");o.className="designbase-wc-gantt__day-name",o.textContent=c[e.getDay()],s.append(d,o),S.appendChild(s)}v.appendChild(S),_.appendChild(v);const C=new Map;e.forEach((e,t)=>{const s=C.get(e.rowId)||[];s.push({...e,color:e.color||a[t%a.length]}),C.set(e.rowId,s)});let I=0;for(const e of b){if("group"===e.type){const{group:t}=e,a=this.collapsedGroups.has(t.id),s=document.createElement("button");s.type="button",s.className="designbase-wc-gantt__label-cell designbase-wc-gantt__label-cell--group",s.setAttribute("aria-expanded",String(!a)),s.dataset.groupId=t.id;const i=document.createElement("span");i.className="designbase-wc-gantt__group-title",i.textContent=t.title,s.append(this.createDragHandle("group",t.id),this.createGroupChevron(a),this.createGroupBadge(t.color),i),s.addEventListener("click",e=>{e.target.closest(".designbase-wc-gantt__drag-handle")||this.toggleGroup(t.id)}),_.appendChild(s);const d=document.createElement("div");if(d.className="designbase-wc-gantt__track-cell designbase-wc-gantt__track-cell--group",a){const e=this.getGroupSummary(t.id,t.color);if(e){const t=document.createElement("div");t.className="designbase-wc-gantt__grid-bg",h.forEach(e=>{const a=document.createElement("div"),s=0===e.getDay()||6===e.getDay();a.className=["designbase-wc-gantt__grid-cell",s&&n?"designbase-wc-gantt__grid-cell--weekend":""].filter(Boolean).join(" "),a.style.width=`${r}px`,t.appendChild(a)}),d.appendChild(t);const a=document.createElement("div");a.className="designbase-wc-gantt__group-summary-wrap";const s=this.createGroupSummaryBar(e,g,p,r);s&&a.appendChild(s),d.appendChild(a)}}_.appendChild(d);continue}const{row:t}=e,a=document.createElement("button");a.type="button",a.className="designbase-wc-gantt__label-cell designbase-wc-gantt__label-cell--row",a.dataset.groupId=t.groupId||"",a.dataset.rowId=t.id;const s=document.createElement("span");s.className="designbase-wc-gantt__row-title",s.textContent=t.label,a.append(this.createDragHandle("row",t.id,t.groupId),s),a.addEventListener("click",e=>{e.target.closest(".designbase-wc-gantt__drag-handle")||this.dispatchEvent(new CustomEvent("db-row-click",{detail:{row:t},bubbles:!0,composed:!0}))}),_.appendChild(a);const i=document.createElement("div");i.className="designbase-wc-gantt__track-cell";const d=document.createElement("div");d.className="designbase-wc-gantt__grid-bg",h.forEach(e=>{const t=document.createElement("div"),a=0===e.getDay()||6===e.getDay();t.className=["designbase-wc-gantt__grid-cell",a&&n?"designbase-wc-gantt__grid-cell--weekend":""].filter(Boolean).join(" "),t.style.width=`${r}px`,d.appendChild(t)}),i.appendChild(d);const o=document.createElement("div");o.className="designbase-wc-gantt__bars",(C.get(t.id)||[]).forEach(e=>{const t=clampDate(parseDate(e.startDate),g,p),a=clampDate(parseDate(e.endDate),g,p);a<g||t>p||o.appendChild(this.createBar({...e,startDate:toDateKey(t),endDate:toDateKey(a)},g,r,I++))}),i.appendChild(o),_.appendChild(i)}if(y.appendChild(_),i&&m>=0){const e=document.createElement("div");e.className="designbase-wc-gantt__today-line",e.style.left=`calc(var(--db-gantt-label-width) + ${m*r+r/2}px)`,_.appendChild(e)}D.appendChild(y),this.replaceChildren(D)}}customElements.get("db-gantt")||customElements.define("db-gantt",DbGantt);export{DbGantt};
|
|
1
|
+
import { createDbIcon } from './db-icons.js';
|
|
2
|
+
|
|
3
|
+
const SCALES = ['day', 'week'];
|
|
4
|
+
const BAR_COLORS = ['#86efac', '#fde047', '#93c5fd', '#c4b5fd', '#f9a8d4', '#5eead4', '#fdba74', '#a5b4fc'];
|
|
5
|
+
const WEEKDAY_KO = ['일', '월', '화', '수', '목', '금', '토'];
|
|
6
|
+
const WEEKDAY_EN = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
7
|
+
function parseDate(value) {
|
|
8
|
+
const [year, month, day] = value.split('-').map(Number);
|
|
9
|
+
return new Date(year, month - 1, day);
|
|
10
|
+
}
|
|
11
|
+
function toDateKey(date) {
|
|
12
|
+
const year = date.getFullYear();
|
|
13
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
14
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
15
|
+
return `${year}-${month}-${day}`;
|
|
16
|
+
}
|
|
17
|
+
function addDays(date, days) {
|
|
18
|
+
const next = new Date(date);
|
|
19
|
+
next.setDate(next.getDate() + days);
|
|
20
|
+
return next;
|
|
21
|
+
}
|
|
22
|
+
function daysBetween(start, end) {
|
|
23
|
+
const startUtc = Date.UTC(start.getFullYear(), start.getMonth(), start.getDate());
|
|
24
|
+
const endUtc = Date.UTC(end.getFullYear(), end.getMonth(), end.getDate());
|
|
25
|
+
return Math.round((endUtc - startUtc) / (1000 * 60 * 60 * 24));
|
|
26
|
+
}
|
|
27
|
+
function clampDate(date, min, max) {
|
|
28
|
+
if (date < min)
|
|
29
|
+
return new Date(min);
|
|
30
|
+
if (date > max)
|
|
31
|
+
return new Date(max);
|
|
32
|
+
return date;
|
|
33
|
+
}
|
|
34
|
+
function deriveBarTone(color) {
|
|
35
|
+
return {
|
|
36
|
+
/** 바 전체 배경 — 숫자를 올리면 더 진해짐 */
|
|
37
|
+
base: `color-mix(in srgb, ${color} 42%, white)`,
|
|
38
|
+
/** 진행 구간 — base보다 살짝만 진하게 (black 대신 white 믹스로 연하게) */
|
|
39
|
+
progress: `color-mix(in srgb, ${color} 58%, white)`,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
class DbGantt extends HTMLElement {
|
|
43
|
+
constructor() {
|
|
44
|
+
super(...arguments);
|
|
45
|
+
this.collapsedGroups = new Set();
|
|
46
|
+
this.dragState = null;
|
|
47
|
+
this.reorderState = null;
|
|
48
|
+
this.reorderDropIndex = null;
|
|
49
|
+
this.suppressRender = false;
|
|
50
|
+
this.didDrag = false;
|
|
51
|
+
this.handlePointerMove = (event) => {
|
|
52
|
+
if (this.reorderState && event.pointerId === this.reorderState.pointerId) {
|
|
53
|
+
this.updateReorderDropTarget(event.clientY);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (!this.dragState || event.pointerId !== this.dragState.pointerId)
|
|
57
|
+
return;
|
|
58
|
+
const scale = this.getOption('scale', SCALES, 'day');
|
|
59
|
+
const cellWidth = this.getNumber('cell-width', scale === 'week' ? 72 : 44);
|
|
60
|
+
const { start: viewStart, end: viewEnd } = this.getViewRange();
|
|
61
|
+
const dayDelta = Math.round((event.clientX - this.dragState.startX) / cellWidth);
|
|
62
|
+
if (dayDelta !== 0)
|
|
63
|
+
this.didDrag = true;
|
|
64
|
+
let nextStart = new Date(this.dragState.originalStart);
|
|
65
|
+
let nextEnd = new Date(this.dragState.originalEnd);
|
|
66
|
+
if (this.dragState.mode === 'move') {
|
|
67
|
+
nextStart = addDays(this.dragState.originalStart, dayDelta);
|
|
68
|
+
nextEnd = addDays(this.dragState.originalEnd, dayDelta);
|
|
69
|
+
}
|
|
70
|
+
else if (this.dragState.mode === 'resize-start') {
|
|
71
|
+
nextStart = addDays(this.dragState.originalStart, dayDelta);
|
|
72
|
+
if (nextStart > nextEnd)
|
|
73
|
+
nextStart = new Date(nextEnd);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
nextEnd = addDays(this.dragState.originalEnd, dayDelta);
|
|
77
|
+
if (nextEnd < nextStart)
|
|
78
|
+
nextEnd = new Date(nextStart);
|
|
79
|
+
}
|
|
80
|
+
nextStart = clampDate(nextStart, viewStart, viewEnd);
|
|
81
|
+
nextEnd = clampDate(nextEnd, viewStart, viewEnd);
|
|
82
|
+
if (nextStart > nextEnd)
|
|
83
|
+
return;
|
|
84
|
+
const item = this.items.find((entry) => entry.id === this.dragState.itemId);
|
|
85
|
+
if (!item)
|
|
86
|
+
return;
|
|
87
|
+
this.applyBarGeometry(this.dragState.barEl, { ...item, startDate: toDateKey(nextStart), endDate: toDateKey(nextEnd) }, viewStart, cellWidth);
|
|
88
|
+
};
|
|
89
|
+
this.handlePointerUp = (event) => {
|
|
90
|
+
if (this.reorderState && event.pointerId === this.reorderState.pointerId) {
|
|
91
|
+
this.finishReorderDrag(event);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (!this.dragState || event.pointerId !== this.dragState.pointerId)
|
|
95
|
+
return;
|
|
96
|
+
const scale = this.getOption('scale', SCALES, 'day');
|
|
97
|
+
const cellWidth = this.getNumber('cell-width', scale === 'week' ? 72 : 44);
|
|
98
|
+
const { start: viewStart, end: viewEnd } = this.getViewRange();
|
|
99
|
+
const dayDelta = Math.round((event.clientX - this.dragState.startX) / cellWidth);
|
|
100
|
+
let nextStart = new Date(this.dragState.originalStart);
|
|
101
|
+
let nextEnd = new Date(this.dragState.originalEnd);
|
|
102
|
+
const mode = this.dragState.mode;
|
|
103
|
+
const itemId = this.dragState.itemId;
|
|
104
|
+
if (mode === 'move') {
|
|
105
|
+
nextStart = addDays(this.dragState.originalStart, dayDelta);
|
|
106
|
+
nextEnd = addDays(this.dragState.originalEnd, dayDelta);
|
|
107
|
+
}
|
|
108
|
+
else if (mode === 'resize-start') {
|
|
109
|
+
nextStart = addDays(this.dragState.originalStart, dayDelta);
|
|
110
|
+
if (nextStart > nextEnd)
|
|
111
|
+
nextStart = new Date(nextEnd);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
nextEnd = addDays(this.dragState.originalEnd, dayDelta);
|
|
115
|
+
if (nextEnd < nextStart)
|
|
116
|
+
nextEnd = new Date(nextStart);
|
|
117
|
+
}
|
|
118
|
+
nextStart = clampDate(nextStart, viewStart, viewEnd);
|
|
119
|
+
nextEnd = clampDate(nextEnd, viewStart, viewEnd);
|
|
120
|
+
this.dragState.barEl.releasePointerCapture(event.pointerId);
|
|
121
|
+
this.dragState.barEl.classList.remove('designbase-wc-gantt__bar--dragging');
|
|
122
|
+
this.dragState = null;
|
|
123
|
+
const originalItem = this.items.find((entry) => entry.id === itemId);
|
|
124
|
+
if (!originalItem)
|
|
125
|
+
return;
|
|
126
|
+
const startKey = toDateKey(nextStart);
|
|
127
|
+
const endKey = toDateKey(nextEnd);
|
|
128
|
+
if (startKey === originalItem.startDate && endKey === originalItem.endDate)
|
|
129
|
+
return;
|
|
130
|
+
this.updateItem(itemId, { startDate: startKey, endDate: endKey }, mode === 'move' ? 'move' : 'resize');
|
|
131
|
+
this.render();
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
static get observedAttributes() {
|
|
135
|
+
return [
|
|
136
|
+
'groups',
|
|
137
|
+
'rows',
|
|
138
|
+
'items',
|
|
139
|
+
'start-date',
|
|
140
|
+
'end-date',
|
|
141
|
+
'today',
|
|
142
|
+
'scale',
|
|
143
|
+
'cell-width',
|
|
144
|
+
'show-weekends',
|
|
145
|
+
'show-today',
|
|
146
|
+
'sidebar-label',
|
|
147
|
+
'full-width',
|
|
148
|
+
'disabled',
|
|
149
|
+
'draggable',
|
|
150
|
+
'reorderable',
|
|
151
|
+
'locale',
|
|
152
|
+
];
|
|
153
|
+
}
|
|
154
|
+
get groups() {
|
|
155
|
+
return this.parseJson('groups', []);
|
|
156
|
+
}
|
|
157
|
+
set groups(value) {
|
|
158
|
+
this.setAttribute('groups', typeof value === 'string' ? value : JSON.stringify(value));
|
|
159
|
+
}
|
|
160
|
+
get rows() {
|
|
161
|
+
return this.parseJson('rows', []);
|
|
162
|
+
}
|
|
163
|
+
set rows(value) {
|
|
164
|
+
this.setAttribute('rows', typeof value === 'string' ? value : JSON.stringify(value));
|
|
165
|
+
}
|
|
166
|
+
get items() {
|
|
167
|
+
return this.parseJson('items', []);
|
|
168
|
+
}
|
|
169
|
+
set items(value) {
|
|
170
|
+
this.setAttribute('items', typeof value === 'string' ? value : JSON.stringify(value));
|
|
171
|
+
}
|
|
172
|
+
connectedCallback() {
|
|
173
|
+
this.setAttribute('role', 'region');
|
|
174
|
+
this.setAttribute('aria-label', '프로젝트 타임라인');
|
|
175
|
+
this.addEventListener('pointermove', this.handlePointerMove);
|
|
176
|
+
this.addEventListener('pointerup', this.handlePointerUp);
|
|
177
|
+
this.addEventListener('pointercancel', this.handlePointerUp);
|
|
178
|
+
this.render();
|
|
179
|
+
}
|
|
180
|
+
disconnectedCallback() {
|
|
181
|
+
this.removeEventListener('pointermove', this.handlePointerMove);
|
|
182
|
+
this.removeEventListener('pointerup', this.handlePointerUp);
|
|
183
|
+
this.removeEventListener('pointercancel', this.handlePointerUp);
|
|
184
|
+
}
|
|
185
|
+
attributeChangedCallback() {
|
|
186
|
+
if (this.isConnected && !this.suppressRender && !this.dragState && !this.reorderState) {
|
|
187
|
+
this.render();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
parseJson(name, fallback) {
|
|
191
|
+
try {
|
|
192
|
+
const parsed = JSON.parse(this.getAttribute(name) || 'null');
|
|
193
|
+
return parsed ?? fallback;
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
return fallback;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
getBool(name, fallback = false) {
|
|
200
|
+
if (!this.hasAttribute(name))
|
|
201
|
+
return fallback;
|
|
202
|
+
const value = this.getAttribute(name);
|
|
203
|
+
return value === '' || value === 'true';
|
|
204
|
+
}
|
|
205
|
+
getNumber(name, fallback) {
|
|
206
|
+
const value = Number(this.getAttribute(name));
|
|
207
|
+
return Number.isFinite(value) && value > 0 ? value : fallback;
|
|
208
|
+
}
|
|
209
|
+
getOption(name, values, fallback) {
|
|
210
|
+
const value = this.getAttribute(name);
|
|
211
|
+
return value && values.includes(value) ? value : fallback;
|
|
212
|
+
}
|
|
213
|
+
getLocale() {
|
|
214
|
+
return this.getAttribute('locale') || 'ko-KR';
|
|
215
|
+
}
|
|
216
|
+
getWeekdays() {
|
|
217
|
+
return this.getLocale().startsWith('ko') ? WEEKDAY_KO : WEEKDAY_EN;
|
|
218
|
+
}
|
|
219
|
+
getViewRange() {
|
|
220
|
+
const explicitStart = this.getAttribute('start-date');
|
|
221
|
+
const explicitEnd = this.getAttribute('end-date');
|
|
222
|
+
if (explicitStart && explicitEnd) {
|
|
223
|
+
return { start: parseDate(explicitStart), end: parseDate(explicitEnd) };
|
|
224
|
+
}
|
|
225
|
+
const dates = this.items.flatMap((item) => [parseDate(item.startDate), parseDate(item.endDate)]);
|
|
226
|
+
if (dates.length === 0) {
|
|
227
|
+
const today = new Date();
|
|
228
|
+
const start = new Date(today.getFullYear(), today.getMonth(), 1);
|
|
229
|
+
const end = addDays(start, 29);
|
|
230
|
+
return { start, end };
|
|
231
|
+
}
|
|
232
|
+
const min = new Date(Math.min(...dates.map((d) => d.getTime())));
|
|
233
|
+
const max = new Date(Math.max(...dates.map((d) => d.getTime())));
|
|
234
|
+
return { start: addDays(min, -2), end: addDays(max, 2) };
|
|
235
|
+
}
|
|
236
|
+
getTodayDate() {
|
|
237
|
+
const attr = this.getAttribute('today');
|
|
238
|
+
return attr ? parseDate(attr) : new Date();
|
|
239
|
+
}
|
|
240
|
+
getDateColumns(start, end) {
|
|
241
|
+
const total = daysBetween(start, end) + 1;
|
|
242
|
+
return Array.from({ length: total }, (_, index) => addDays(start, index));
|
|
243
|
+
}
|
|
244
|
+
getVisibleRows() {
|
|
245
|
+
const groups = this.groups;
|
|
246
|
+
const rows = this.rows;
|
|
247
|
+
if (groups.length === 0) {
|
|
248
|
+
return rows.map((row) => ({ type: 'row', row }));
|
|
249
|
+
}
|
|
250
|
+
const result = [];
|
|
251
|
+
for (const group of groups) {
|
|
252
|
+
result.push({ type: 'group', group });
|
|
253
|
+
if (this.collapsedGroups.has(group.id))
|
|
254
|
+
continue;
|
|
255
|
+
rows
|
|
256
|
+
.filter((row) => row.groupId === group.id)
|
|
257
|
+
.forEach((row) => result.push({ type: 'row', row }));
|
|
258
|
+
}
|
|
259
|
+
rows
|
|
260
|
+
.filter((row) => !row.groupId || !groups.some((group) => group.id === row.groupId))
|
|
261
|
+
.forEach((row) => result.push({ type: 'row', row }));
|
|
262
|
+
return result;
|
|
263
|
+
}
|
|
264
|
+
getMonthSegments(dates) {
|
|
265
|
+
const segments = [];
|
|
266
|
+
let currentKey = '';
|
|
267
|
+
let currentSpan = 0;
|
|
268
|
+
for (const date of dates) {
|
|
269
|
+
const key = `${date.getFullYear()}-${date.getMonth()}`;
|
|
270
|
+
if (key !== currentKey) {
|
|
271
|
+
if (currentSpan > 0) {
|
|
272
|
+
segments.push({ label: currentKey, span: currentSpan });
|
|
273
|
+
}
|
|
274
|
+
currentKey = key;
|
|
275
|
+
currentSpan = 1;
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
currentSpan += 1;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (currentSpan > 0) {
|
|
282
|
+
segments.push({ label: currentKey, span: currentSpan });
|
|
283
|
+
}
|
|
284
|
+
return segments.map((segment) => {
|
|
285
|
+
const [year, month] = segment.label.split('-').map(Number);
|
|
286
|
+
const label = new Date(year, month, 1).toLocaleDateString(this.getLocale(), {
|
|
287
|
+
year: 'numeric',
|
|
288
|
+
month: 'long',
|
|
289
|
+
});
|
|
290
|
+
return { label, span: segment.span };
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
getInitials(assignee) {
|
|
294
|
+
if (assignee.initials)
|
|
295
|
+
return assignee.initials;
|
|
296
|
+
return assignee.name
|
|
297
|
+
.split(/\s+/)
|
|
298
|
+
.map((part) => part[0])
|
|
299
|
+
.join('')
|
|
300
|
+
.slice(0, 2)
|
|
301
|
+
.toUpperCase();
|
|
302
|
+
}
|
|
303
|
+
updateItem(itemId, patch, action) {
|
|
304
|
+
const next = this.items.map((item) => item.id === itemId ? { ...item, ...patch } : item);
|
|
305
|
+
this.emitItemsChange(next, action, itemId);
|
|
306
|
+
}
|
|
307
|
+
emitItemsChange(items, action, itemId) {
|
|
308
|
+
this.suppressRender = true;
|
|
309
|
+
this.items = items;
|
|
310
|
+
this.suppressRender = false;
|
|
311
|
+
const item = items.find((entry) => entry.id === itemId);
|
|
312
|
+
this.dispatchEvent(new CustomEvent('db-item-change', {
|
|
313
|
+
detail: { item, items, action },
|
|
314
|
+
bubbles: true,
|
|
315
|
+
composed: true,
|
|
316
|
+
}));
|
|
317
|
+
this.dispatchEvent(new CustomEvent('db-change', {
|
|
318
|
+
detail: { groups: this.groups, rows: this.rows, items },
|
|
319
|
+
bubbles: true,
|
|
320
|
+
composed: true,
|
|
321
|
+
}));
|
|
322
|
+
}
|
|
323
|
+
applyBarGeometry(barEl, item, viewStart, cellWidth) {
|
|
324
|
+
const itemStart = parseDate(item.startDate);
|
|
325
|
+
const itemEnd = parseDate(item.endDate);
|
|
326
|
+
const offset = daysBetween(viewStart, itemStart);
|
|
327
|
+
const duration = daysBetween(itemStart, itemEnd) + 1;
|
|
328
|
+
const gap = 2;
|
|
329
|
+
barEl.style.left = `${offset * cellWidth + gap}px`;
|
|
330
|
+
barEl.style.width = `${Math.max(duration * cellWidth - gap * 2, cellWidth - gap * 2)}px`;
|
|
331
|
+
}
|
|
332
|
+
startBarDrag(event, item, mode, barEl) {
|
|
333
|
+
if (!this.getBool('draggable', true) || this.getBool('disabled') || item.disabled)
|
|
334
|
+
return;
|
|
335
|
+
event.preventDefault();
|
|
336
|
+
event.stopPropagation();
|
|
337
|
+
this.didDrag = false;
|
|
338
|
+
barEl.setPointerCapture(event.pointerId);
|
|
339
|
+
barEl.classList.add('designbase-wc-gantt__bar--dragging');
|
|
340
|
+
this.dragState = {
|
|
341
|
+
itemId: item.id,
|
|
342
|
+
mode,
|
|
343
|
+
pointerId: event.pointerId,
|
|
344
|
+
startX: event.clientX,
|
|
345
|
+
originalStart: parseDate(item.startDate),
|
|
346
|
+
originalEnd: parseDate(item.endDate),
|
|
347
|
+
barEl,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
canReorder() {
|
|
351
|
+
return this.getBool('reorderable', true) && !this.getBool('disabled');
|
|
352
|
+
}
|
|
353
|
+
createDragHandle(kind, id, groupId) {
|
|
354
|
+
const handle = document.createElement('span');
|
|
355
|
+
handle.className = 'designbase-wc-gantt__drag-handle';
|
|
356
|
+
handle.setAttribute('role', 'button');
|
|
357
|
+
handle.setAttribute('aria-label', kind === 'group' ? '그룹 순서 변경' : '행 순서 변경');
|
|
358
|
+
handle.append(createDbIcon('grip-vertical', 12));
|
|
359
|
+
if (!this.canReorder())
|
|
360
|
+
return handle;
|
|
361
|
+
handle.addEventListener('pointerdown', (event) => {
|
|
362
|
+
event.preventDefault();
|
|
363
|
+
event.stopPropagation();
|
|
364
|
+
this.startReorderDrag(event, kind, id, groupId, handle);
|
|
365
|
+
});
|
|
366
|
+
return handle;
|
|
367
|
+
}
|
|
368
|
+
getGroupIndex(groupId) {
|
|
369
|
+
return this.groups.findIndex((group) => group.id === groupId);
|
|
370
|
+
}
|
|
371
|
+
getRowScope(groupId) {
|
|
372
|
+
return this.rows.filter((row) => (row.groupId || '') === (groupId || ''));
|
|
373
|
+
}
|
|
374
|
+
getRowIndexInScope(rowId, groupId) {
|
|
375
|
+
return this.getRowScope(groupId).findIndex((row) => row.id === rowId);
|
|
376
|
+
}
|
|
377
|
+
getReorderTargets(kind, groupId) {
|
|
378
|
+
return Array.from(this.querySelectorAll(kind === 'group'
|
|
379
|
+
? '.designbase-wc-gantt__label-cell--group'
|
|
380
|
+
: `.designbase-wc-gantt__label-cell--row[data-group-id="${groupId || ''}"]`));
|
|
381
|
+
}
|
|
382
|
+
startReorderDrag(event, kind, id, groupId, handleEl) {
|
|
383
|
+
const fromIndex = kind === 'group' ? this.getGroupIndex(id) : this.getRowIndexInScope(id, groupId);
|
|
384
|
+
if (fromIndex < 0)
|
|
385
|
+
return;
|
|
386
|
+
handleEl.setPointerCapture(event.pointerId);
|
|
387
|
+
this.reorderState = {
|
|
388
|
+
kind,
|
|
389
|
+
id,
|
|
390
|
+
groupId,
|
|
391
|
+
pointerId: event.pointerId,
|
|
392
|
+
startY: event.clientY,
|
|
393
|
+
fromIndex,
|
|
394
|
+
captureEl: handleEl,
|
|
395
|
+
};
|
|
396
|
+
this.classList.add('designbase-wc-gantt--reordering');
|
|
397
|
+
this.updateReorderDropTarget(event.clientY);
|
|
398
|
+
}
|
|
399
|
+
updateReorderDropTarget(clientY) {
|
|
400
|
+
if (!this.reorderState)
|
|
401
|
+
return;
|
|
402
|
+
const targets = this.getReorderTargets(this.reorderState.kind, this.reorderState.groupId);
|
|
403
|
+
let nextDrop = null;
|
|
404
|
+
targets.forEach((el, index) => {
|
|
405
|
+
const rect = el.getBoundingClientRect();
|
|
406
|
+
const mid = rect.top + rect.height / 2;
|
|
407
|
+
const isOver = clientY >= rect.top && clientY <= rect.bottom;
|
|
408
|
+
const placeAfter = clientY > mid;
|
|
409
|
+
el.classList.toggle('designbase-wc-gantt__label-cell--drag-over', isOver);
|
|
410
|
+
el.classList.toggle('designbase-wc-gantt__label-cell--drag-over-before', isOver && !placeAfter);
|
|
411
|
+
el.classList.toggle('designbase-wc-gantt__label-cell--drag-over-after', isOver && placeAfter);
|
|
412
|
+
if (isOver) {
|
|
413
|
+
nextDrop = placeAfter ? index + 1 : index;
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
this.reorderDropIndex = nextDrop;
|
|
417
|
+
}
|
|
418
|
+
clearReorderIndicators() {
|
|
419
|
+
this.querySelectorAll('.designbase-wc-gantt__label-cell--drag-over').forEach((el) => {
|
|
420
|
+
el.classList.remove('designbase-wc-gantt__label-cell--drag-over', 'designbase-wc-gantt__label-cell--drag-over-before', 'designbase-wc-gantt__label-cell--drag-over-after');
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
finishReorderDrag(event) {
|
|
424
|
+
if (!this.reorderState)
|
|
425
|
+
return;
|
|
426
|
+
const { kind, id, groupId, fromIndex, captureEl } = this.reorderState;
|
|
427
|
+
try {
|
|
428
|
+
captureEl.releasePointerCapture(this.reorderState.pointerId);
|
|
429
|
+
}
|
|
430
|
+
catch {
|
|
431
|
+
/* ignore */
|
|
432
|
+
}
|
|
433
|
+
const dropIndex = this.reorderDropIndex;
|
|
434
|
+
this.reorderState = null;
|
|
435
|
+
this.reorderDropIndex = null;
|
|
436
|
+
this.classList.remove('designbase-wc-gantt--reordering');
|
|
437
|
+
this.clearReorderIndicators();
|
|
438
|
+
if (dropIndex === null)
|
|
439
|
+
return;
|
|
440
|
+
const adjustedDrop = dropIndex > fromIndex ? dropIndex - 1 : dropIndex;
|
|
441
|
+
if (adjustedDrop < 0 || adjustedDrop === fromIndex)
|
|
442
|
+
return;
|
|
443
|
+
if (kind === 'group') {
|
|
444
|
+
this.reorderGroups(fromIndex, adjustedDrop, id);
|
|
445
|
+
}
|
|
446
|
+
else {
|
|
447
|
+
this.reorderRows(fromIndex, adjustedDrop, id, groupId);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
emitStructureChange(groups, rows, detail, eventName) {
|
|
451
|
+
this.suppressRender = true;
|
|
452
|
+
this.groups = groups;
|
|
453
|
+
this.rows = rows;
|
|
454
|
+
this.suppressRender = false;
|
|
455
|
+
this.dispatchEvent(new CustomEvent(eventName, {
|
|
456
|
+
detail: { ...detail, groups, rows },
|
|
457
|
+
bubbles: true,
|
|
458
|
+
composed: true,
|
|
459
|
+
}));
|
|
460
|
+
this.dispatchEvent(new CustomEvent('db-change', {
|
|
461
|
+
detail: { groups, rows, items: this.items },
|
|
462
|
+
bubbles: true,
|
|
463
|
+
composed: true,
|
|
464
|
+
}));
|
|
465
|
+
this.render();
|
|
466
|
+
}
|
|
467
|
+
reorderGroups(fromIndex, toIndex, groupId) {
|
|
468
|
+
const next = [...this.groups];
|
|
469
|
+
const [moved] = next.splice(fromIndex, 1);
|
|
470
|
+
if (!moved)
|
|
471
|
+
return;
|
|
472
|
+
next.splice(toIndex, 0, moved);
|
|
473
|
+
this.emitStructureChange(next, this.rows, { groupId, fromIndex, toIndex }, 'db-group-reorder');
|
|
474
|
+
}
|
|
475
|
+
reorderRows(fromIndex, toIndex, rowId, groupId) {
|
|
476
|
+
const scope = this.getRowScope(groupId);
|
|
477
|
+
const nextScope = [...scope];
|
|
478
|
+
const [moved] = nextScope.splice(fromIndex, 1);
|
|
479
|
+
if (!moved)
|
|
480
|
+
return;
|
|
481
|
+
nextScope.splice(toIndex, 0, moved);
|
|
482
|
+
const scopeIds = new Set(scope.map((row) => row.id));
|
|
483
|
+
const nextRows = [];
|
|
484
|
+
let scopeInserted = false;
|
|
485
|
+
for (const row of this.rows) {
|
|
486
|
+
if (!scopeIds.has(row.id)) {
|
|
487
|
+
nextRows.push(row);
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
if (!scopeInserted) {
|
|
491
|
+
nextRows.push(...nextScope);
|
|
492
|
+
scopeInserted = true;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
if (!scopeInserted)
|
|
496
|
+
nextRows.push(...nextScope);
|
|
497
|
+
this.emitStructureChange(this.groups, nextRows, { rowId, groupId, fromIndex, toIndex }, 'db-row-reorder');
|
|
498
|
+
}
|
|
499
|
+
createGroupBadge(color) {
|
|
500
|
+
const badge = document.createElement('db-badge');
|
|
501
|
+
badge.className = 'designbase-wc-gantt__group-badge';
|
|
502
|
+
badge.setAttribute('badge-style', 'dot');
|
|
503
|
+
badge.setAttribute('size', 's');
|
|
504
|
+
badge.setAttribute('variant', 'primary');
|
|
505
|
+
badge.setAttribute('aria-hidden', 'true');
|
|
506
|
+
if (color)
|
|
507
|
+
badge.style.setProperty('--db-gantt-badge-color', color);
|
|
508
|
+
return badge;
|
|
509
|
+
}
|
|
510
|
+
createGroupChevron(collapsed) {
|
|
511
|
+
const chevron = document.createElement('span');
|
|
512
|
+
chevron.className = [
|
|
513
|
+
'designbase-wc-gantt__group-chevron',
|
|
514
|
+
collapsed ? 'designbase-wc-gantt__group-chevron--collapsed' : '',
|
|
515
|
+
]
|
|
516
|
+
.filter(Boolean)
|
|
517
|
+
.join(' ');
|
|
518
|
+
chevron.append(createDbIcon('chevron-down', 12));
|
|
519
|
+
return chevron;
|
|
520
|
+
}
|
|
521
|
+
createAssignee(assignee) {
|
|
522
|
+
const el = document.createElement('span');
|
|
523
|
+
el.className = 'designbase-wc-gantt__assignee';
|
|
524
|
+
el.title = assignee.name;
|
|
525
|
+
if (assignee.avatar) {
|
|
526
|
+
const img = document.createElement('img');
|
|
527
|
+
img.src = assignee.avatar;
|
|
528
|
+
img.alt = assignee.name;
|
|
529
|
+
el.appendChild(img);
|
|
530
|
+
}
|
|
531
|
+
else {
|
|
532
|
+
el.textContent = this.getInitials(assignee);
|
|
533
|
+
}
|
|
534
|
+
return el;
|
|
535
|
+
}
|
|
536
|
+
createStatusIcon(status) {
|
|
537
|
+
const el = document.createElement('span');
|
|
538
|
+
el.className = `designbase-wc-gantt__status designbase-wc-gantt__status--${status}`;
|
|
539
|
+
el.setAttribute('aria-hidden', 'true');
|
|
540
|
+
el.textContent = status === 'completed' ? '✓' : '!';
|
|
541
|
+
return el;
|
|
542
|
+
}
|
|
543
|
+
deriveSummaryColor(items, fallback) {
|
|
544
|
+
if (items.length === 0)
|
|
545
|
+
return fallback || BAR_COLORS[0];
|
|
546
|
+
const withColor = items.filter((item) => item.color);
|
|
547
|
+
if (withColor.length === 0)
|
|
548
|
+
return fallback || BAR_COLORS[0];
|
|
549
|
+
const longest = withColor.reduce((current, item) => {
|
|
550
|
+
const currentDays = daysBetween(parseDate(current.startDate), parseDate(current.endDate));
|
|
551
|
+
const itemDays = daysBetween(parseDate(item.startDate), parseDate(item.endDate));
|
|
552
|
+
return itemDays > currentDays ? item : current;
|
|
553
|
+
});
|
|
554
|
+
return longest.color || fallback || BAR_COLORS[0];
|
|
555
|
+
}
|
|
556
|
+
getGroupSummary(groupId, groupColor) {
|
|
557
|
+
const groupRows = this.rows.filter((row) => row.groupId === groupId);
|
|
558
|
+
const groupItems = this.items.filter((item) => groupRows.some((row) => row.id === item.rowId));
|
|
559
|
+
if (groupItems.length === 0)
|
|
560
|
+
return null;
|
|
561
|
+
const starts = groupItems.map((item) => parseDate(item.startDate).getTime());
|
|
562
|
+
const ends = groupItems.map((item) => parseDate(item.endDate).getTime());
|
|
563
|
+
const progressValues = groupItems
|
|
564
|
+
.map((item) => item.progress)
|
|
565
|
+
.filter((value) => typeof value === 'number');
|
|
566
|
+
return {
|
|
567
|
+
startDate: toDateKey(new Date(Math.min(...starts))),
|
|
568
|
+
endDate: toDateKey(new Date(Math.max(...ends))),
|
|
569
|
+
color: this.deriveSummaryColor(groupItems, groupColor),
|
|
570
|
+
progress: progressValues.length > 0
|
|
571
|
+
? progressValues.reduce((sum, value) => sum + value, 0) / progressValues.length
|
|
572
|
+
: 0,
|
|
573
|
+
count: groupItems.length,
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
applyBarTone(el, color) {
|
|
577
|
+
const tone = deriveBarTone(color);
|
|
578
|
+
el.style.setProperty('--db-gantt-bar-color', color);
|
|
579
|
+
el.style.setProperty('--db-gantt-bar-bg', tone.base);
|
|
580
|
+
el.style.setProperty('--db-gantt-bar-progress-bg', tone.progress);
|
|
581
|
+
}
|
|
582
|
+
createGroupSummaryBar(summary, viewStart, viewEnd, cellWidth) {
|
|
583
|
+
const clampedStart = clampDate(parseDate(summary.startDate), viewStart, viewEnd);
|
|
584
|
+
const clampedEnd = clampDate(parseDate(summary.endDate), viewStart, viewEnd);
|
|
585
|
+
if (clampedEnd < viewStart || clampedStart > viewEnd)
|
|
586
|
+
return null;
|
|
587
|
+
const offset = daysBetween(viewStart, clampedStart);
|
|
588
|
+
const duration = daysBetween(clampedStart, clampedEnd) + 1;
|
|
589
|
+
const gap = 2;
|
|
590
|
+
const progress = Math.min(100, Math.max(0, summary.progress));
|
|
591
|
+
const bar = document.createElement('div');
|
|
592
|
+
bar.className = 'designbase-wc-gantt__group-summary';
|
|
593
|
+
bar.setAttribute('aria-hidden', 'true');
|
|
594
|
+
bar.title = `${summary.count}개 태스크`;
|
|
595
|
+
this.applyBarTone(bar, summary.color);
|
|
596
|
+
bar.style.left = `${offset * cellWidth + gap}px`;
|
|
597
|
+
bar.style.width = `${Math.max(duration * cellWidth - gap * 2, cellWidth - gap * 2)}px`;
|
|
598
|
+
if (progress > 0) {
|
|
599
|
+
const progressEl = document.createElement('span');
|
|
600
|
+
progressEl.className = 'designbase-wc-gantt__group-summary-progress';
|
|
601
|
+
progressEl.style.width = `${progress}%`;
|
|
602
|
+
bar.appendChild(progressEl);
|
|
603
|
+
}
|
|
604
|
+
return bar;
|
|
605
|
+
}
|
|
606
|
+
createBar(item, viewStart, cellWidth, colorIndex) {
|
|
607
|
+
const barColor = item.color || BAR_COLORS[colorIndex % BAR_COLORS.length];
|
|
608
|
+
const bar = document.createElement('div');
|
|
609
|
+
bar.className = 'designbase-wc-gantt__bar';
|
|
610
|
+
bar.setAttribute('role', 'button');
|
|
611
|
+
bar.setAttribute('tabindex', item.disabled ? '-1' : '0');
|
|
612
|
+
bar.setAttribute('aria-label', `${item.title}, ${item.startDate} – ${item.endDate}`);
|
|
613
|
+
this.applyBarTone(bar, barColor);
|
|
614
|
+
if (item.disabled) {
|
|
615
|
+
bar.classList.add('designbase-wc-gantt__bar--disabled');
|
|
616
|
+
}
|
|
617
|
+
this.applyBarGeometry(bar, item, viewStart, cellWidth);
|
|
618
|
+
const progress = typeof item.progress === 'number' ? Math.min(100, Math.max(0, item.progress)) : 0;
|
|
619
|
+
if (progress > 0) {
|
|
620
|
+
const progressEl = document.createElement('span');
|
|
621
|
+
progressEl.className = 'designbase-wc-gantt__bar-progress';
|
|
622
|
+
progressEl.style.width = `${progress}%`;
|
|
623
|
+
bar.appendChild(progressEl);
|
|
624
|
+
}
|
|
625
|
+
const body = document.createElement('div');
|
|
626
|
+
body.className = 'designbase-wc-gantt__bar-body';
|
|
627
|
+
if (item.status === 'delayed' || item.status === 'at-risk') {
|
|
628
|
+
body.appendChild(this.createStatusIcon(item.status));
|
|
629
|
+
}
|
|
630
|
+
const label = document.createElement('span');
|
|
631
|
+
label.className = 'designbase-wc-gantt__bar-label';
|
|
632
|
+
label.textContent = item.title;
|
|
633
|
+
body.appendChild(label);
|
|
634
|
+
if (item.assignee) {
|
|
635
|
+
body.appendChild(this.createAssignee(item.assignee));
|
|
636
|
+
}
|
|
637
|
+
const resizeStart = document.createElement('span');
|
|
638
|
+
resizeStart.className = 'designbase-wc-gantt__bar-handle designbase-wc-gantt__bar-handle--start';
|
|
639
|
+
resizeStart.setAttribute('aria-hidden', 'true');
|
|
640
|
+
const resizeEnd = document.createElement('span');
|
|
641
|
+
resizeEnd.className = 'designbase-wc-gantt__bar-handle designbase-wc-gantt__bar-handle--end';
|
|
642
|
+
resizeEnd.setAttribute('aria-hidden', 'true');
|
|
643
|
+
bar.append(resizeStart, body, resizeEnd);
|
|
644
|
+
if (!item.disabled && this.getBool('draggable', true) && !this.getBool('disabled')) {
|
|
645
|
+
resizeStart.addEventListener('pointerdown', (event) => this.startBarDrag(event, item, 'resize-start', bar));
|
|
646
|
+
resizeEnd.addEventListener('pointerdown', (event) => this.startBarDrag(event, item, 'resize-end', bar));
|
|
647
|
+
body.addEventListener('pointerdown', (event) => {
|
|
648
|
+
if (event.target.closest('.designbase-wc-gantt__bar-handle'))
|
|
649
|
+
return;
|
|
650
|
+
this.startBarDrag(event, item, 'move', bar);
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
bar.addEventListener('click', (event) => {
|
|
654
|
+
if (this.dragState || this.didDrag) {
|
|
655
|
+
this.didDrag = false;
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
event.stopPropagation();
|
|
659
|
+
this.dispatchEvent(new CustomEvent('db-item-click', {
|
|
660
|
+
detail: { item },
|
|
661
|
+
bubbles: true,
|
|
662
|
+
composed: true,
|
|
663
|
+
}));
|
|
664
|
+
});
|
|
665
|
+
return bar;
|
|
666
|
+
}
|
|
667
|
+
toggleGroup(groupId) {
|
|
668
|
+
if (this.collapsedGroups.has(groupId)) {
|
|
669
|
+
this.collapsedGroups.delete(groupId);
|
|
670
|
+
}
|
|
671
|
+
else {
|
|
672
|
+
this.collapsedGroups.add(groupId);
|
|
673
|
+
}
|
|
674
|
+
this.dispatchEvent(new CustomEvent('db-group-toggle', {
|
|
675
|
+
detail: { groupId, collapsed: this.collapsedGroups.has(groupId) },
|
|
676
|
+
bubbles: true,
|
|
677
|
+
composed: true,
|
|
678
|
+
}));
|
|
679
|
+
this.render();
|
|
680
|
+
}
|
|
681
|
+
render() {
|
|
682
|
+
const items = this.items;
|
|
683
|
+
const scale = this.getOption('scale', SCALES, 'day');
|
|
684
|
+
const cellWidth = this.getNumber('cell-width', scale === 'week' ? 72 : 44);
|
|
685
|
+
const showWeekends = this.getBool('show-weekends', true);
|
|
686
|
+
const showToday = this.getBool('show-today', true);
|
|
687
|
+
const sidebarLabel = this.getAttribute('sidebar-label') || '할 일';
|
|
688
|
+
const disabled = this.getBool('disabled');
|
|
689
|
+
const fullWidth = this.getBool('full-width', true);
|
|
690
|
+
const weekdays = this.getWeekdays();
|
|
691
|
+
const { start: viewStart, end: viewEnd } = this.getViewRange();
|
|
692
|
+
const dates = this.getDateColumns(viewStart, viewEnd);
|
|
693
|
+
const today = this.getTodayDate();
|
|
694
|
+
const todayKey = toDateKey(today);
|
|
695
|
+
const todayOffset = dates.findIndex((date) => toDateKey(date) === todayKey);
|
|
696
|
+
const visibleRows = this.getVisibleRows();
|
|
697
|
+
const chartWidth = dates.length * cellWidth;
|
|
698
|
+
const root = document.createElement('div');
|
|
699
|
+
root.className = [
|
|
700
|
+
'designbase-wc-gantt',
|
|
701
|
+
fullWidth ? 'designbase-wc-gantt--full-width' : '',
|
|
702
|
+
disabled ? 'designbase-wc-gantt--disabled' : '',
|
|
703
|
+
this.getBool('draggable', true) ? 'designbase-wc-gantt--draggable' : '',
|
|
704
|
+
]
|
|
705
|
+
.filter(Boolean)
|
|
706
|
+
.join(' ');
|
|
707
|
+
const viewport = document.createElement('div');
|
|
708
|
+
viewport.className = 'designbase-wc-gantt__viewport';
|
|
709
|
+
const grid = document.createElement('div');
|
|
710
|
+
grid.className = 'designbase-wc-gantt__grid';
|
|
711
|
+
grid.style.setProperty('--db-gantt-chart-width', `${chartWidth}px`);
|
|
712
|
+
grid.style.setProperty('--db-gantt-cell-width', `${cellWidth}px`);
|
|
713
|
+
const corner = document.createElement('div');
|
|
714
|
+
corner.className = 'designbase-wc-gantt__corner';
|
|
715
|
+
corner.textContent = sidebarLabel;
|
|
716
|
+
grid.appendChild(corner);
|
|
717
|
+
const dateHeader = document.createElement('div');
|
|
718
|
+
dateHeader.className = 'designbase-wc-gantt__date-header';
|
|
719
|
+
const monthRow = document.createElement('div');
|
|
720
|
+
monthRow.className = 'designbase-wc-gantt__month-row';
|
|
721
|
+
for (const segment of this.getMonthSegments(dates)) {
|
|
722
|
+
const cell = document.createElement('div');
|
|
723
|
+
cell.className = 'designbase-wc-gantt__month-cell';
|
|
724
|
+
cell.style.width = `${segment.span * cellWidth}px`;
|
|
725
|
+
cell.textContent = segment.label;
|
|
726
|
+
monthRow.appendChild(cell);
|
|
727
|
+
}
|
|
728
|
+
dateHeader.appendChild(monthRow);
|
|
729
|
+
const dayRow = document.createElement('div');
|
|
730
|
+
dayRow.className = 'designbase-wc-gantt__day-row';
|
|
731
|
+
for (const date of dates) {
|
|
732
|
+
const key = toDateKey(date);
|
|
733
|
+
const isToday = key === todayKey;
|
|
734
|
+
const isWeekend = date.getDay() === 0 || date.getDay() === 6;
|
|
735
|
+
const cell = document.createElement('div');
|
|
736
|
+
cell.className = [
|
|
737
|
+
'designbase-wc-gantt__day-cell',
|
|
738
|
+
isToday && showToday ? 'designbase-wc-gantt__day-cell--today' : '',
|
|
739
|
+
isWeekend && showWeekends ? 'designbase-wc-gantt__day-cell--weekend' : '',
|
|
740
|
+
]
|
|
741
|
+
.filter(Boolean)
|
|
742
|
+
.join(' ');
|
|
743
|
+
cell.style.width = `${cellWidth}px`;
|
|
744
|
+
const dayNum = document.createElement('span');
|
|
745
|
+
dayNum.className = 'designbase-wc-gantt__day-num';
|
|
746
|
+
dayNum.textContent = String(date.getDate());
|
|
747
|
+
const dayName = document.createElement('span');
|
|
748
|
+
dayName.className = 'designbase-wc-gantt__day-name';
|
|
749
|
+
dayName.textContent = weekdays[date.getDay()];
|
|
750
|
+
cell.append(dayNum, dayName);
|
|
751
|
+
dayRow.appendChild(cell);
|
|
752
|
+
}
|
|
753
|
+
dateHeader.appendChild(dayRow);
|
|
754
|
+
grid.appendChild(dateHeader);
|
|
755
|
+
const itemsByRow = new Map();
|
|
756
|
+
items.forEach((item, index) => {
|
|
757
|
+
const list = itemsByRow.get(item.rowId) || [];
|
|
758
|
+
list.push({ ...item, color: item.color || BAR_COLORS[index % BAR_COLORS.length] });
|
|
759
|
+
itemsByRow.set(item.rowId, list);
|
|
760
|
+
});
|
|
761
|
+
let colorIndex = 0;
|
|
762
|
+
for (const entry of visibleRows) {
|
|
763
|
+
if (entry.type === 'group') {
|
|
764
|
+
const { group } = entry;
|
|
765
|
+
const collapsed = this.collapsedGroups.has(group.id);
|
|
766
|
+
const labelCell = document.createElement('button');
|
|
767
|
+
labelCell.type = 'button';
|
|
768
|
+
labelCell.className = 'designbase-wc-gantt__label-cell designbase-wc-gantt__label-cell--group';
|
|
769
|
+
labelCell.setAttribute('aria-expanded', String(!collapsed));
|
|
770
|
+
labelCell.dataset.groupId = group.id;
|
|
771
|
+
const title = document.createElement('span');
|
|
772
|
+
title.className = 'designbase-wc-gantt__group-title';
|
|
773
|
+
title.textContent = group.title;
|
|
774
|
+
labelCell.append(this.createDragHandle('group', group.id), this.createGroupChevron(collapsed), this.createGroupBadge(group.color), title);
|
|
775
|
+
labelCell.addEventListener('click', (event) => {
|
|
776
|
+
if (event.target.closest('.designbase-wc-gantt__drag-handle'))
|
|
777
|
+
return;
|
|
778
|
+
this.toggleGroup(group.id);
|
|
779
|
+
});
|
|
780
|
+
grid.appendChild(labelCell);
|
|
781
|
+
const trackCell = document.createElement('div');
|
|
782
|
+
trackCell.className =
|
|
783
|
+
'designbase-wc-gantt__track-cell designbase-wc-gantt__track-cell--group';
|
|
784
|
+
if (collapsed) {
|
|
785
|
+
const summary = this.getGroupSummary(group.id, group.color);
|
|
786
|
+
if (summary) {
|
|
787
|
+
const gridBg = document.createElement('div');
|
|
788
|
+
gridBg.className = 'designbase-wc-gantt__grid-bg';
|
|
789
|
+
dates.forEach((date) => {
|
|
790
|
+
const cell = document.createElement('div');
|
|
791
|
+
const isWeekend = date.getDay() === 0 || date.getDay() === 6;
|
|
792
|
+
cell.className = [
|
|
793
|
+
'designbase-wc-gantt__grid-cell',
|
|
794
|
+
isWeekend && showWeekends
|
|
795
|
+
? 'designbase-wc-gantt__grid-cell--weekend'
|
|
796
|
+
: '',
|
|
797
|
+
]
|
|
798
|
+
.filter(Boolean)
|
|
799
|
+
.join(' ');
|
|
800
|
+
cell.style.width = `${cellWidth}px`;
|
|
801
|
+
gridBg.appendChild(cell);
|
|
802
|
+
});
|
|
803
|
+
trackCell.appendChild(gridBg);
|
|
804
|
+
const summaryWrap = document.createElement('div');
|
|
805
|
+
summaryWrap.className = 'designbase-wc-gantt__group-summary-wrap';
|
|
806
|
+
const summaryBar = this.createGroupSummaryBar(summary, viewStart, viewEnd, cellWidth);
|
|
807
|
+
if (summaryBar)
|
|
808
|
+
summaryWrap.appendChild(summaryBar);
|
|
809
|
+
trackCell.appendChild(summaryWrap);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
grid.appendChild(trackCell);
|
|
813
|
+
continue;
|
|
814
|
+
}
|
|
815
|
+
const { row } = entry;
|
|
816
|
+
const labelCell = document.createElement('button');
|
|
817
|
+
labelCell.type = 'button';
|
|
818
|
+
labelCell.className = 'designbase-wc-gantt__label-cell designbase-wc-gantt__label-cell--row';
|
|
819
|
+
labelCell.dataset.groupId = row.groupId || '';
|
|
820
|
+
labelCell.dataset.rowId = row.id;
|
|
821
|
+
const rowTitle = document.createElement('span');
|
|
822
|
+
rowTitle.className = 'designbase-wc-gantt__row-title';
|
|
823
|
+
rowTitle.textContent = row.label;
|
|
824
|
+
labelCell.append(this.createDragHandle('row', row.id, row.groupId), rowTitle);
|
|
825
|
+
labelCell.addEventListener('click', (event) => {
|
|
826
|
+
if (event.target.closest('.designbase-wc-gantt__drag-handle'))
|
|
827
|
+
return;
|
|
828
|
+
this.dispatchEvent(new CustomEvent('db-row-click', {
|
|
829
|
+
detail: { row },
|
|
830
|
+
bubbles: true,
|
|
831
|
+
composed: true,
|
|
832
|
+
}));
|
|
833
|
+
});
|
|
834
|
+
grid.appendChild(labelCell);
|
|
835
|
+
const trackCell = document.createElement('div');
|
|
836
|
+
trackCell.className = 'designbase-wc-gantt__track-cell';
|
|
837
|
+
const gridBg = document.createElement('div');
|
|
838
|
+
gridBg.className = 'designbase-wc-gantt__grid-bg';
|
|
839
|
+
dates.forEach((date) => {
|
|
840
|
+
const cell = document.createElement('div');
|
|
841
|
+
const isWeekend = date.getDay() === 0 || date.getDay() === 6;
|
|
842
|
+
cell.className = [
|
|
843
|
+
'designbase-wc-gantt__grid-cell',
|
|
844
|
+
isWeekend && showWeekends ? 'designbase-wc-gantt__grid-cell--weekend' : '',
|
|
845
|
+
]
|
|
846
|
+
.filter(Boolean)
|
|
847
|
+
.join(' ');
|
|
848
|
+
cell.style.width = `${cellWidth}px`;
|
|
849
|
+
gridBg.appendChild(cell);
|
|
850
|
+
});
|
|
851
|
+
trackCell.appendChild(gridBg);
|
|
852
|
+
const bars = document.createElement('div');
|
|
853
|
+
bars.className = 'designbase-wc-gantt__bars';
|
|
854
|
+
(itemsByRow.get(row.id) || []).forEach((item) => {
|
|
855
|
+
const clampedStart = clampDate(parseDate(item.startDate), viewStart, viewEnd);
|
|
856
|
+
const clampedEnd = clampDate(parseDate(item.endDate), viewStart, viewEnd);
|
|
857
|
+
if (clampedEnd < viewStart || clampedStart > viewEnd)
|
|
858
|
+
return;
|
|
859
|
+
bars.appendChild(this.createBar({
|
|
860
|
+
...item,
|
|
861
|
+
startDate: toDateKey(clampedStart),
|
|
862
|
+
endDate: toDateKey(clampedEnd),
|
|
863
|
+
}, viewStart, cellWidth, colorIndex++));
|
|
864
|
+
});
|
|
865
|
+
trackCell.appendChild(bars);
|
|
866
|
+
grid.appendChild(trackCell);
|
|
867
|
+
}
|
|
868
|
+
viewport.appendChild(grid);
|
|
869
|
+
if (showToday && todayOffset >= 0) {
|
|
870
|
+
const todayLine = document.createElement('div');
|
|
871
|
+
todayLine.className = 'designbase-wc-gantt__today-line';
|
|
872
|
+
todayLine.style.left = `calc(var(--db-gantt-label-width) + ${todayOffset * cellWidth + cellWidth / 2}px)`;
|
|
873
|
+
grid.appendChild(todayLine);
|
|
874
|
+
}
|
|
875
|
+
root.appendChild(viewport);
|
|
876
|
+
this.replaceChildren(root);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
if (!customElements.get('db-gantt'))
|
|
880
|
+
customElements.define('db-gantt', DbGantt);
|
|
881
|
+
|
|
882
|
+
export { DbGantt };
|